kakueki61's dev history

備忘録的に記録を残しています

Flutter: 遷移先画面を終了時に値を受け取る

遷移先の画面を終了する時に、元の画面へ何かしらのデータを渡したいというケースがあります。例えば、遷移先の画面で何か選択をした結果を元の画面へ反映したい時などです。
AndroidであればstartActivityForResultでActivityを起動し、元画面のonActivityResultでデータを受け取るという感じで実装することができます。

FlutterではNavigator.pushFutureを返します。遷移先のNavigator.popはこのFutureをcompleteさせますので、遷移元の画面はFutureへコールバックを登録しておくことでデータを受け取ることができます。

Flutter公式ドキュメント
Return data from a screen
を元にして実装方法を紹介します。

1. 遷移元画面作成

ボタン押下時に画面遷移させるようにします。

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Returnning Data Demo'),
      ),
      body: new Builder(
        builder: (BuildContext context) {
          return new Center(
            child: new RaisedButton(
              onPressed: () {
                _navigateAndDisplaySelection(context);
              },
              child: new Text('Pick an option, any option!'),
            )
          );
        }
      )
    );
  }

  _navigateAndDisplaySelection(BuildContext context) {
    Navigator.push(context, new MaterialPageRoute(builder: (context) => new SelectionScreen()))
  }
}

2. 遷移先画面を作成

"Up!"と"Down!"と書かれた2つのボタンを用意します。

class SelectionScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(title: new Text('Pick an option'),),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Padding(
              padding: const EdgeInsets.all(8.0),
              child: new RaisedButton(
                onPressed: () {
                  Navigator.pop(context, 'Up!');
                },
                child: new Text('Up!'),),
            ),
            new Padding(
              padding: const EdgeInsets.all(8.0),
              child: new RaisedButton(
                onPressed: () {
                  Navigator.pop(context, 'Down!');
                },
                child: new Text('Down!')
              )
            )
          ],
        )
      ),
    );
  }
}

各ボタン押下時にNavigator.popでこの画面を終了する共に、遷移前の画面へ"Up!"と"Down!"の文字列を渡すようにしています。

3. 遷移元画面で値を受け取る

HomeScreen_navigateAndDisplaySelectionを以下のように修正します。

_navigateAndDisplaySelection(BuildContext context) {
  Navigator.push(
    context,
    new MaterialPageRoute(builder: (context) => new SelectionScreen())
  ).then((result) {
    Scaffold.of(context).showSnackBar(new SnackBar(content: new Text('$result')));
  });
}

前述のようにNavigator.pushFutureを返すのでthenでコールバックを登録することができます。
resultにはNavigator.popで渡されたデータが渡ってきてコールバックが実行されます。
ここではSnackBarでその文字列を表示しています。

async / awaitで書く

thenでコールバック登録するような記述は煩雑になりがちなので、以下のように書くことが多いです。

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

  Scaffold.of(context).showSnackBar(new SnackBar(content: new Text('$result')));
}

Navigator.pushFutureが生成されますが、awaitによってFutureがcompleteするまで処理が中断状態になります。前述のようにNavigator.popでデータが渡されると処理が再開し、resultにデータが代入され、そのデータでSnackBarを表示させるという流れになっています。

この辺りのFutureの扱いに関しては
Asynchronous Programming: Futures
が参考になるかと思います。

最後に、AndroidとiOSでビルドすると以下のようになります。

Android iOS
f:id:kakueki61:20180418013852g:plain f:id:kakueki61:20180418013740g:plain