팝업시 Flutter 네비게이터가 상태를 다시로드하도록 강제


109

나는 하나가 StatefulWidget다른 저를 탐색 버튼과 떨림에 StatefulWidget사용 Navigator.push(). 두 번째 위젯에서는 전역 상태 (일부 사용자 기본 설정)를 변경하고 있습니다. 두 번째 위젯에서 첫 번째 위젯으로 돌아 오면 첫 번째 위젯을 사용 Navigator.pop()하는 것은 이전 상태이지만 강제로 다시로드하고 싶습니다. 이 작업을 수행하는 방법을 아십니까? 한 가지 아이디어가 있지만 추악 해 보입니다.

  1. 두 번째 위젯을 제거하려면 팝업 (현재 위젯)
  2. 첫 번째 위젯 (이전 위젯)을 제거하려면 다시 팝업
  3. 첫 번째 위젯 푸시 (강제 다시 그리기)

1
대답 없음, 일반적인 의견 : 제 경우에는이 문제를 찾고있는 문제는 방금 전에 다른 페이지에서 작성한 업데이트 된 pref를 얻을 수있는 shared_preferences 동기화 메서드를 사용하면 해결 될 것입니다. . : \ .then (...)을 사용해도 항상 보류중인 쓰기 업데이트 된 데이터가 표시되는 것은 아닙니다.
ChrisH

팝의 새 페이지에서 값을 반환하고 내 문제를 해결했습니다. flutter.dev/docs/cookbook/navigation/returning-data
Joe M

답변:


85

여기서 할 수있는 일이 몇 가지 있습니다. @Mahi의 대답은 정확하지만 조금 더 간결 할 수 있으며 실제로 OP가 묻는 것처럼 showDialog보다 푸시를 사용합니다. 다음을 사용하는 예입니다 Navigator.push.

import 'package:flutter/material.dart';

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Container(
      color: Colors.green,
      child: new Column(
        children: <Widget>[
          new RaisedButton(
            onPressed: () => Navigator.pop(context),
            child: new Text("back"),
          ),
        ],
      ),
    );
  }
}

class FirstPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => new FirstPageState();
}

class FirstPageState extends State<FirstPage> {

  Color color = Colors.white;

  @override
  Widget build(BuildContext context) {
    return new Container(
      color: color,
      child: new Column(
        children: <Widget>[
          new RaisedButton(
              child: new Text("next"),
              onPressed: () {
                Navigator
                    .push(
                  context,
                  new MaterialPageRoute(builder: (context) => new SecondPage()),
                )
                    .then((value) {
                  setState(() {
                    color = color == Colors.white ? Colors.grey : Colors.white;
                  });
                });
              }),
        ],
      ),
    );
  }
}

void main() => runApp(
      new MaterialApp(
        builder: (context, child) => new SafeArea(child: child),
        home: new FirstPage(),
      ),
    );

그러나 사용 사례에 잘 맞는 다른 방법이 있습니다. 을 global첫 페이지의 빌드에 영향을주는 것으로 사용하는 경우 InheritedWidget 을 사용하여 전역 사용자 환경 설정을 정의 할 수 있으며 변경 될 때마다 FirstPage가 다시 빌드됩니다. 이는 아래에 표시된 것처럼 상태 비 저장 위젯에서도 작동합니다 (그러나 상태 저장 위젯에서도 작동해야 함).

flutter에서 inheritedWidget의 예는 앱의 테마입니다.하지만 여기 에서처럼 직접 빌드하는 대신 위젯 내에서 정의합니다.

import 'package:flutter/material.dart';
import 'package:meta/meta.dart';

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Container(
      color: Colors.green,
      child: new Column(
        children: <Widget>[
          new RaisedButton(
            onPressed: () {
              ColorDefinition.of(context).toggleColor();
              Navigator.pop(context);
            },
            child: new Text("back"),
          ),
        ],
      ),
    );
  }
}

class ColorDefinition extends InheritedWidget {
  ColorDefinition({
    Key key,
    @required Widget child,
  }): super(key: key, child: child);

  Color color = Colors.white;

  static ColorDefinition of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(ColorDefinition);
  }

  void toggleColor() {
    color = color == Colors.white ? Colors.grey : Colors.white;
    print("color set to $color");
  }

  @override
  bool updateShouldNotify(ColorDefinition oldWidget) =>
      color != oldWidget.color;
}

class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var color = ColorDefinition.of(context).color;

    return new Container(
      color: color,
      child: new Column(
        children: <Widget>[
          new RaisedButton(
              child: new Text("next"),
              onPressed: () {
                Navigator.push(
                  context,
                  new MaterialPageRoute(builder: (context) => new SecondPage()),
                );
              }),
        ],
      ),
    );
  }
}

void main() => runApp(
      new MaterialApp(
        builder: (context, child) => new SafeArea(
              child: new ColorDefinition(child: child),
            ),
        home: new FirstPage(),
      ),
    );

상속 된 위젯을 사용하는 경우 푸시 한 페이지의 팝업을 지켜 보는 것에 대해 걱정할 필요가 없습니다. 기본 사용 사례에서는 작동하지만 더 복잡한 시나리오에서는 문제가 발생할 수 있습니다.


훌륭합니다. 첫 번째 경우는 완벽하게 작동했습니다 (두 번째 경우는 더 복잡한 상황에 적합합니다). 두 번째 페이지에서 OnWillPopUp을 사용하는 것은 분명하지 않았습니다. 전혀 작동하지도 않았습니다.
cdsaenz 19

내 목록 항목을 업데이트하려면 어떻게해야하나요? 내 첫 번째 페이지에는 목록 항목이 있고 두 번째 페이지에는 항목 세부 정보가 포함되어 있다고 가정 해 보겠습니다. 항목의 값을 업데이트 중이며 목록 항목에서 다시 업데이트하고 싶습니다. 그것을 달성하는 방법?
Aanal Mehta 19 년

@AanalMehta 두 페이지 모두에서 사용되는 백엔드 저장소 (예 : sqflite 테이블 또는 메모리 내 목록)가 있고. 이전에 setState에서 변경 한 증분 카운터를 사용했습니다. 가장 깨끗한 솔루션은 아니지만 작동합니다.) 또는 .then 함수의 원래 페이지로 변경 사항을 다시 전달하고 목록을 수정 한 다음 그런 다음 다시 작성합니다 (하지만 목록을 변경해도 새로 고침이 트리거되지 않으므로 증분 카운터를 다시 사용하십시오).
rmtmckenzie 19 년

@AanalMehta 그러나 그것이 도움이되지 않는다면, 더 관련이있을 수있는 다른 답변을 찾거나 (목록 등에 대해 몇 가지를 봤다고 확신합니다) 새로운 질문을하는 것이 좋습니다.
rmtmckenzie 19 년

@rmtmckenzie 실제로 위의 한 가지 해결책을 시도했습니다. 변경 사항을 .then에 적용했지만 반영되지 않습니다. 이제 공급자를 사용할 수있는 옵션이 하나 뿐인 것 같습니다. 어쨌든 도와 주셔서 감사합니다.
Aanal Mehta

20

데이터를 전달하는 두 가지가 있습니다.

  • 첫 페이지에서 두 번째

    첫 페이지에서 사용

    // sending "Foo" from 1st
    Navigator.push(context, MaterialPageRoute(builder: (_) => Page2("Foo")));
    

    두 번째 페이지에서 사용하십시오.

    class Page2 extends StatelessWidget {
      final String string;
    
      Page2(this.string); // receiving "Foo" in 2nd
    
      ...
    }
    

  • 두 번째 페이지에서 첫 번째 페이지

    두 번째 페이지에서 사용

    // sending "Bar" from 2nd
    Navigator.pop(context, "Bar");
    

    첫 페이지에서 사용하십시오. 이전에 사용했던 것과 동일하지만 약간의 수정이 있습니다.

    // receiving "Bar" in 1st
    String received = await Navigator.push(context, MaterialPageRoute(builder: (_) => Page2("Foo")));
    

Navigator.popUntil 메서드를 사용하여 경로 C에서 팝업 할 때 경로 A를 어떻게 다시로드 할 수 있습니까?
Vinoth Vino

1
@VinothVino 아직 그렇게하는 직접적인 방법은 없습니다. 일종의 해결 방법이 필요합니다.
CopsOnRoad

활동 TabBar의 @CopsOnRoad는 대화 상자에서 두 번째 탭을 호출하려면 Navigator.pushreplacement에서 두 번째 탭을 클릭하여 두 번째 탭으로 리디렉션합니다.
sj

13

쉬운 트릭을 사용하는 것입니다 Navigator.pushReplacement을 방법을

페이지 1

Navigator.pushReplacement(
  context,
  MaterialPageRoute(
    builder: (context) => Page2(),
  ),
);

2 쪽

Navigator.pushReplacement(
  context,
  MaterialPageRoute(
    builder: (context) => Page1(),
  ),
);

이렇게하면 네비게이터 스택이 손실됩니다. 뒤로 버튼으로 화면을 띄우는 것은 어떻습니까?
encubos

11

pushReplacement를 사용하고 새 경로를 지정할 수 있습니다.


1
그러나 네비게이터 스택이 손실됩니다. 안드로이드 뒤로 버튼을 사용하여 화면을 띄우면 어떨까요?
encubos

6

이 작업은 정말 좋았습니다. 저는 flutter 페이지에서이 문서를 받았습니다 : flutter doc

첫 페이지에서 탐색을 제어하는 ​​방법을 정의했습니다.

_navigateAndDisplaySelection(BuildContext context) async {
    final result = await Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => AddDirectionPage()),
    );

    //below you can get your result and update the view with setState
    //changing the value if you want, i just wanted know if i have to  
    //update, and if is true, reload state

    if (result) {
      setState(() {});
    }
  }

따라서 잉크 병의 작업 메서드에서 호출하지만 버튼에서도 호출 할 수 있습니다.

onTap: () {
   _navigateAndDisplaySelection(context);
},

마지막으로 두 번째 페이지에서 무언가를 반환합니다 (부울을 반환했습니다. 원하는 것을 반환 할 수 있습니다).

onTap: () {
  Navigator.pop(context, true);
}

1
await Navigator....pop에서 결과 값을 생략 할 수도 있습니다 .
0llie

5

나를 위해 이것은 작동하는 것 같습니다.

Navigator.of(context).pushNamed("/myRoute").then((value) => setState(() {}));

그런 다음 간단히 Navigator.pop()아이 를 불러주세요 .


4

두 번째 화면 (비동기 기능 내부)으로 밀고있는 곳에 이것을 놓으십시오.

Function f;
f= await Navigator.pushNamed(context, 'ScreenName');
f();

당신이 터지는 곳에 이것을 두십시오

Navigator.pop(context, () {
 setState(() {});
});

setState내에서 호출되는 pop데이터를 업데이트하기 위해 폐쇄.


2
setState인수로 정확히 어디에 전달 합니까? 기본적으로 setState클로저 내부를 호출 합니다. 인수로 전달하지 않습니다.
Volkan Güven 20.04.13

이것이 내가 찾던 정확한 대답입니다. 기본적으로 Navigator.pop.
David Chopin

3

내 솔루션은 SecondPage에 함수 매개 변수를 추가 한 다음 FirstPage에서 수행중인 다시로드 함수를받은 다음 Navigator.pop (context) 줄 이전에 함수를 실행했습니다.

첫 페이지

refresh() {
setState(() {
//all the reload processes
});
}

그런 다음 다음 페이지로 이동합니다 ...

Navigator.push(context, new MaterialPageRoute(builder: (context) => new SecondPage(refresh)),);

SecondPage

final Function refresh;
SecondPage(this.refresh); //constructor

네비게이터 팝 라인 앞에서

widget.refresh(); // just refresh() if its statelesswidget
Navigator.pop(context);

이전 페이지에서 다시로드해야하는 모든 항목은 팝업 후에 업데이트되어야합니다.


1

dynamic result컨텍스트를 팝할 때 a를 다시 전달한 다음 setState((){})값이true 하여 상태를 그대로 둘 수 있습니다.

참조 용으로 코드 스 니펫을 붙여 넣었습니다.

handleClear() async {
    try {
      var delete = await deleteLoanWarning(
        context,
        'Clear Notifications?',
        'Are you sure you want to clear notifications. This action cannot be undone',
      );
      if (delete.toString() == 'true') {
        //call setState here to rebuild your state.

      }
    } catch (error) {
      print('error clearing notifications' + error.toString());
             }
  }



Future<bool> deleteLoanWarning(BuildContext context, String title, String msg) async {

  return await showDialog<bool>(
        context: context,
        child: new AlertDialog(
          title: new Text(
            title,
            style: new TextStyle(fontWeight: fontWeight, color: CustomColors.continueButton),
            textAlign: TextAlign.center,
          ),
          content: new Text(
            msg,
            textAlign: TextAlign.justify,
          ),
          actions: <Widget>[
            new Container(
              decoration: boxDecoration(),
              child: new MaterialButton(
                child: new Text('NO',),
                onPressed: () {
                  Navigator.of(context).pop(false);
                },
              ),
            ),
            new Container(
              decoration: boxDecoration(),
              child: new MaterialButton(
                child: new Text('YES', ),
                onPressed: () {
                  Navigator.of(context).pop(true);
                },
              ),
            ),
          ],
        ),
      ) ??
      false;
}

감사합니다, Mahi


두 번째 부분을 수행하는 방법을 잘 모르겠습니다. 첫 번째는 단순히 Navigator.pop(context, true), 맞습니까? 하지만이 true값을 어떻게 얻을 수 있습니까? 사용 BuildContext?
bartektartanus

나를 위해 일하지 않습니다. 나는 푸시의 결과를 사용 setState를 호출하고있어setState() called after dispose(): This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback. The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. ....
bartektartanus

흠, 더 이상 활성화되지 않은 위젯이나 폐기 된 위젯을 업데이트하는 것을 피할 수있는 방법을 if(!mounted) return;호출 할 때마다 사용했던 것이 흥미 롭습니다 setState((){});.
Mahi

) 오류하지만 또한 내가 예상대로 작동하지됩니다
bartektartanus

@bartektartanus와 같은 문제가 있습니다. ! mounted before setState를 추가하면 내 상태가 설정되지 않습니다. 간단 해 보이지만 몇 시간이 지나도 이해하지 못했습니다.
Swift

1

내 상태 비 저장 위젯 중 하나를 강제로 다시 빌드해야합니다. 상태 저장을 사용하고 싶지 않았습니다. 이 솔루션을 찾았습니다.

await Navigator.of(context).pushNamed(...);
ModalRoute.of(enclosingWidgetContext);

컨텍스트 및 enclosingWidgetContext는 동일하거나 다른 컨텍스트 일 ​​수 있습니다. 예를 들어 StreamBuilder 내부에서 푸시하면 달라집니다.

여기서는 ModalRoute로 아무것도하지 않습니다. 구독하는 행위만으로도 강제로 재 구축 할 수 있습니다.


1

경고 대화 상자를 사용하는 경우 대화 상자가 닫힐 때 완료되는 Future를 사용할 수 있습니다. 미래가 완료된 후 위젯이 상태를 다시로드하도록 할 수 있습니다.

첫 페이지

onPressed: () async {
    await showDialog(
       context: context,
       builder: (BuildContext context) {
            return AlertDialog(
                 ....
            );
       }
    );
    setState(() {});
}

경고 대화 상자에서

Navigator.of(context).pop();

0

오늘 나는 같은 상황에 직면했지만 훨씬 더 쉽게 해결할 수 있었고 첫 번째 상태 저장 클래스에서 사용 된 전역 변수를 정의했고 두 번째 상태 저장 위젯으로 이동할 때 전역 값을 업데이트하도록했습니다. 첫 번째 위젯을 자동으로 업데이트하는 변수입니다. 여기에 예가 있습니다.

import 'package:flutter/material.dart';
int count = 0 ;

class FirstPage extends StatefulWidget {
FirstPage({Key key}) : super(key: key);

@override
_FirstPageState createState() => _FirstPageState();
}

class _FirstPageState extends State<FirstPage> {
@override
Widget build(BuildContext context) {
return InkWell(
onTap(){
Navigator.of(context).push(MaterialPageRoute(builder: (context) =>
                  new SecondPage());
},
child: Text('First', style : TextStyle(fontSize: count == 0 ? 20.0 : 12.0)),
),

}


class SecondPage extends StatefulWidget {
SecondPage({Key key}) : super(key: key);

@override
_SecondPageState createState() => _SecondPageState();
}

class _SecondPageState extends State<SecondPage> {
@override
Widget build(BuildContext context) {
return IconButton(
         icon: new Icon(Icons.add),
         color: Colors.amber,
         iconSize: 15.0,
         onPressed: (){
         count++ ;
         },
       ),
     }

4
변수를 변경해도 위젯이 자동으로 다시 빌드되지는 않습니다. 다시 호출 될 때까지 Navigator.pop()상태를 유지 한 후에 는이 코드에서 발생하지 않습니다. 내가 뭔가를 놓치고 있습니까? Navigator.push()setState
Ricardo BRGWeb

0

이 간단한 코드는 루트로 이동하여 상태를 다시로드하는 데 도움이되었습니다.

    ...
    onPressed: () {
         Navigator.of(context).pushNamedAndRemoveUntil('/', ModalRoute.withName('/'));
                },
    ...

0

간단히 말해서 위젯이 상태를 감시하도록해야합니다. 이를 위해서는 상태 관리가 필요합니다.

내 방법은 Flutter Architecture SamplesFlutter Docs에 설명 된 Provider를 기반으로 합니다. 더 간결한 설명을 위해 그들을 참조하십시오 그러나 단계는 어느 정도 다음과 같습니다.

  • 위젯이 관찰해야하는 상태로 상태 모델을 정의하십시오.

일부 API 프로세스를 기다리기 위해 data및 라고하는 여러 상태를 가질 수 있습니다 isLoading. 모델 자체가 ChangeNotifier.

  • 이러한 상태에 의존하는 위젯을 감시자 클래스로 래핑합니다.

Consumer또는 일 수 있습니다 Selector.

  • "다시로드"해야 할 때 기본적으로 해당 상태를 업데이트하고 변경 사항을 브로드 캐스트합니다.

상태 모델의 경우 클래스는 다음과 같이 다소 보일 것입니다. notifyListeners변경 사항을 브로드 캐스트하는 것에주의 하십시오.

class DataState extends ChangeNotifier{

  bool isLoading;
  
  Data data;

  Future loadData(){
    isLoading = true;
    notifyListeners();

    service.get().then((newData){
      isLoading = false;
      data = newData;
      notifyListeners();
    });
  }
  
}

이제 위젯입니다. 이것은 매우 스켈레톤 코드가 될 것입니다.

return ChangeNotifierProvider(

  create: (_) => DataState()..loadData(),
      
  child: ...{
    Selector<DataState, bool>(

        selector: (context, model) => model.isLoading,

        builder: (context, isLoading, _) {
          if (isLoading) {
            return ProgressBar;
          }

          return Container(

              child: Consumer<DataState>(builder: (context, dataState, child) {

                 return WidgetData(...);

              }
          ));
        },
      ),
  }
);

상태 모델의 인스턴스는 ChangeNotifierProvider에서 제공합니다. 선택자와 소비자는 각각 isLoadingdata각각에 대한 상태를 감시합니다 . 이 그들 사이에 큰 차이는 없습니다 만 개인적으로 당신이 그들의 건축업자가 제공하는 무엇에 따라 달라을 사용하는 방법. 소비자는 상태 모델에 대한 액세스를 제공하므로 loadData바로 아래에있는 모든 위젯에 대한 호출 이 더 간단합니다.

그렇지 않은 경우 Provider.of. 두 번째 화면에서 돌아올 때 페이지를 새로 고치려면 다음과 같이 할 수 있습니다.

await Navigator.push(context, 
  MaterialPageRoute(
    builder: (_) {
     return Screen2();
));

Provider.of<DataState>(context, listen: false).loadData();


0

나를 위해 일했습니다.

...
onPressed: (){pushUpdate('/somePageName');}
...

pushUpdate (string pageName) async {      //in the same class
  await pushPage(context, pageName);
  setState(() {});
}


//---------------------------------------------
//general sub
pushPage (context, namePage) async {
  await Navigator.pushNamed(context, namePage);
}

이 경우 (UI의 버튼 또는 Android의 "뒤로") 팝업 방식은 중요하지 않습니다. 업데이트가 완료됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.