ポップ時にFlutterナビゲーターに状態をリロードさせる


108

私は1つを持っているStatefulWidget他に私をナビゲートボタン、とフラッターにStatefulWidget使用しますNavigator.push()。2番目のウィジェットで、グローバル状態(一部のユーザー設定)を変更しています。2番目のウィジェットから最初のウィジェットに戻るとNavigator.pop()、最初のウィジェットの使用は古い状態ですが、強制的にリロードしたいと思います。これを行う方法はありますか?私は1つのアイデアを持っていますが、それは醜いように見えます:

  1. ポップして2番目のウィジェット(現在のウィジェット)を削除します
  2. もう一度ポップして最初のウィジェット(前のウィジェット)を削除します
  3. 最初のウィジェットをプッシュします(強制的に再描画する必要があります)

1
答えはありません。一般的なコメントです。私の場合、これを探してここに来たのは、shared_preferencesのsyncメソッドを使用するだけで解決され、少し前に別のページで書いた更新された設定を確実に取り戻すことができます。 。:\ .then(...)を使用しても、常に保留中の書き込みが更新されたデータを取得できるとは限りません。
ChrisH

popの新しいページから値を返したところ、問題が解決しました。参照してくださいflutter.dev/docs/cookbook/navigation/returning-data
ジョー・M

これを見る必要があります:stackoverflow.com/a/64006691/10563627
PareshMangukiya20年

回答:


85

ここでできることがいくつかあります。@Mahiの正解はもう少し簡潔で、OPが求めていたshowDialogではなく実際にpushを使用する可能性があります。これは、以下を使用する例です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最初のページのビルドに影響を与えるものとしてを使用している場合は、AttachedWidgetを使用してグローバルユーザー設定を定義できます。これらが変更されるたびに、FirstPageが再構築されます。これは、以下に示すようにステートレスウィジェット内でも機能します(ただし、ステートフルウィジェットでも機能するはずです)。

フラッターの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(),
      ),
    );

継承されたウィジェットを使用する場合、プッシュしたページのポップを監視することを心配する必要はありません。これは基本的なユースケースでは機能しますが、より複雑なシナリオでは問題が発生する可能性があります。


すばらしい、最初のケースは私にとって完璧に機能しました(2番目のケースはより複雑な状況に最適です)。2ページ目からOnWillPopUpを使用することは、私にはわかりませんでした。まったく機能しませんでした。
cdsaenz

ねえ、リストアイテムを更新したい場合はどうすればよいですか。1ページ目にリストアイテムが含まれ、2ページ目にアイテムの詳細が含まれているとします。アイテムの値を更新していますが、リストアイテムで更新して戻したいです。それを達成する方法は?
AanalMehta19年

@AanalMehta簡単な推奨事項を提供できます-両方のページから使用されるバックエンドストア(つまり、sqfliteテーブルまたはメモリ内リスト)を用意し、次にウィジェットを強制的に更新することができます(私は個人的に以前にsetStateで変更したインクリメントカウンターを使用したことがあります-これは最もクリーンなソリューションではありませんが、機能します)...または、.then関数で変更を元のページに戻し、リストに変更を加えて、次に再構築します(ただし、リストに変更を加えても更新はトリガーされないため、もう一度インクリメントカウンターを使用してください)。
rmtmckenzie

@AanalMehtaしかし、それでも問題が解決しない場合は、より関連性の高い他の回答を探すか(リストなどについていくつか見たことがあると思います)、新しい質問をすることをお勧めします。
rmtmckenzie

@rmtmckenzie実際に私は上記の1つの解決策を試しました。変更を適用しましたが、反映されません。今では、プロバイダーを使用するオプションは1つしかないと思います。とにかく、助けてくれてありがとう。
AanalMehta19年

20

からデータを渡すことは2つあります

  • 1ページ目から2ページ目

    1ページ目でご利用ください

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

    2ページ目でご利用ください。

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

  • 2ページ目から1ページ目

    2ページ目でご利用ください

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

    これを1ページ目で使用します。以前に使用したものと同じですが、ほとんど変更がありません。

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

Navigator.popUntilメソッドを使用してルートCからポップするときに、ルートAをリロードするにはどうすればよいですか。
VinothVino20年

1
@VinothVinoまだそれを行う直接的な方法はありません、あなたはある種の回避策をする必要があります。
CopsOnRoad

アクティビティTabBarの@CopsOnRoadを使用して、ダイアログ送信ボタンから2番目のタブを呼び出し、Navigator.pushreplacementからクリックして、2番目のタブにリダイレクトします。
SJ

13

簡単なトリックを使用することですNavigator.pushReplacement方法を

ページ1

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

ページ2

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

このようにして、ナビゲータースタックが失われます。戻るボタンで画面をポップするのはどうですか?
encubos

11

pushReplacementを使用して、新しいルートを指定できます


1
しかし、あなたはナビゲータースタックを失っています。Androidの戻るボタンを使用して画面をポップするとどうなりますか?
encubos

5

この作品は本当に良いです、私はフラッターページからこのドキュメントから得ました:フラッタードキュメント

最初のページからナビゲーションを制御する方法を定義しました。

_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);
},

そして最後に2ページ目で、何かを返すために(私はブール値を返しました、あなたはあなたが望むものは何でも返すことができます):

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

1
await Navigator....popの結果値をそのまま省略することもできます。
0llie

5

私にとって、これはうまくいくようです:

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

次に、単にNavigator.pop()子供を呼び出します。


4

これを2番目の画面(非同期関数内)にプッシュする場所に配置します

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

あなたがポップしているところにこれを置いてください

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

setState内部で呼ばれているpopデータを更新するために閉鎖。


2
setState引数として正確にどこに渡しますか?あなたは基本的にsetStateクロージャの中で呼んでいます。引数として渡さない。
ボルカングベン

これは私が探していた正確な答えです。基本的に、の完了ハンドラーが必要でしたNavigator.pop
デヴィッド・ショパン

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そうでない場合はtheを呼び出して、状態をそのままにしておくことができます。

参考までに、いくつかのコードスニペットを貼り付けました。

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;
}

よろしく、マヒ


2番目の部分の実行方法がわかりません。まずは単純Navigator.pop(context, true)ですよね?しかし、どうすればこのtrue値を取得できますか?使い方BuildContext
bartektartanus 2018

私のために働いていません。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 2018

うーん、これは興味深いことです。この方法をif(!mounted) return;呼び出すたびに、破棄setState((){});されたウィジェットやアクティブでなくなったウィジェットの更新を回避できます。
マヒ2018

エラーはありませんが、予測どおりに機能していません;)
bartektartanus 2018

@bartektartanusと同じ問題があります。setStateの前に!mountedを追加すると、状態が設定されなくなります。簡単そうに見えますが、数時間経ってもわかりません。
スウィフト

1

ステートレスウィジェットの1つを強制的に再構築する必要がありました。ステートフルを使用したくありませんでした。この解決策を思いついた:

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

contextとencloseingWidgetContextは、同じコンテキストでも異なるコンテキストでもかまいません。たとえば、StreamBuilderの内部からプッシュする場合、それらは異なります。

ModalRouteではここでは何もしません。単独でサブスクライブするという行為は、再構築を強制するのに十分です。


1

アラートダイアログを使用している場合は、ダイアログが閉じられたときに完了するフューチャーを使用できます。将来の完了後、ウィジェットに状態を再ロードさせることができます。

一ページ目

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

アラートダイアログで

Navigator.of(context).pop();

0

今日、私は同じ状況に直面しましたが、はるかに簡単な方法でそれを解決することができました。最初のステートフルクラスで使用されるグローバル変数を定義し、2番目のステートフルウィジェットに移動すると、グローバルの値を更新しました。最初のウィジェットを自動的に更新する変数。これが例です(私は急いで書いたので、足場やマテリアルアプリを配置しませんでした、私は自分のポイントを説明したかっただけです):

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
リカルドBRGWeb19年

0

この単純なコードは、ルートに移動して状態を再ロードするのに役立ちました。

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

0

つまり、ウィジェットに状態を監視させる必要があります。これには状態管理が必要です。

私の方法は、プロバイダに基づいていて説明フラッターアーキテクチャのサンプルだけでなく、フラッタードキュメント。より簡潔な説明についてはそれらを参照してくださいが、多かれ少なかれ手順は次のとおりです。

  • ウィジェットが監視する必要のある状態を使用して状態モデルを定義します。

いくつかのAPIプロセスを待つために、複数の状態に「dataisLoading」を付けることができます。モデル自体は拡張されChangeNotifierます。

  • これらの状態に依存するウィジェットをウォッチャークラスでラップします。

これは可能性がありConsumerSelector

  • 「リロード」する必要がある場合は、基本的にそれらの状態を更新し、変更をブロードキャストします。

状態モデルの場合、クラスは多かれ少なかれ次のようになります。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によって提供されます。セレクターとコンシューマーは、それぞれisLoadingとの状態を監視しdataます。それらの間に大きな違いはありませんが、個人的にそれらをどのように使用するかは、ビルダーが提供するものによって異なります。コンシューマーは状態モデルへのアクセスを提供するため、loadDataその直下にあるウィジェットの呼び出しは簡単です。

そうでない場合は、を使用できますProvider.of。2番目の画面から戻ったときにページを更新したい場合は、次のようにすることができます。

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.