質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.47%
Flutter

Flutterは、iOSとAndroidのアプリを同じコードで開発するためのフレームワークです。オープンソースで開発言語はDart。双方のプラットフォームにおける高度な実行パフォーマンスと開発効率を提供することを目的としています。

Dart

Dartは、Googleによって開発されたJavaScriptの代替となることを目的に作られた、ウェブ向けのプログラミング言語である。

Q&A

解決済

1回答

2304閲覧

Flutterで画面遷移元に戻る際に値を渡す

momiji0210

総合スコア60

Flutter

Flutterは、iOSとAndroidのアプリを同じコードで開発するためのフレームワークです。オープンソースで開発言語はDart。双方のプラットフォームにおける高度な実行パフォーマンスと開発効率を提供することを目的としています。

Dart

Dartは、Googleによって開発されたJavaScriptの代替となることを目的に作られた、ウェブ向けのプログラミング言語である。

0グッド

0クリップ

投稿2021/05/25 16:07

編集2021/05/26 02:37

下記のような2画面でテキスト文言を編集できるようなサンプルプログラムを記載しております。

TopScreen
・ListViewでテキストデータが並んで表示
・タップするとNectScreenへ遷移

NectScreen
・TopScreenで選択したテキストデータが編集可能
・キャンセルボタンを選択した際には編集前のデータを設定したい

実際に遷移前のページに正しい値が送れてるみたいなのですが、

キャンセルボタンを押しても遷移前のテキストデータが編集されてしまいます。
setStateがうまく効いてない気もするのですが、
原因などわかりますでしょうか。
処理は入ってるので書き方が違うのかもしれません。

下記を参考にしながら、似たような実装をしています。

[https://qiita.com/mamoru_takami/items/f53aebd97fb1140d122a]

Dart

1import 'package:flutter/material.dart'; 2 3void main() => runApp(MyApp()); 4 5class Todo { 6 final String title; 7 final String description; 8 Todo({ this.title, this.description}) 9 : assert(title != null), 10 assert(description != null); 11} 12 13class MyApp extends StatelessWidget { 14 15 Widget build(BuildContext context) { 16 return MaterialApp( 17 debugShowCheckedModeBanner: false, // Debug文言の削除 18 title: 'Navigation', 19 home: ListScreen2(title: 'List'), 20 ); 21 } 22} 23 24class Model { 25 String title; 26 String subTitle; 27 String key; 28 29 Model({ 30 this.title, 31 this.subTitle, 32 this.key, 33 }); 34} 35 36class ListScreen2 extends StatefulWidget { 37 ListScreen2({Key key, this.title}) : super(key: key); 38 39 final String title; 40 41 42 _ListScreenState2 createState() => _ListScreenState2(); 43} 44 45class _ListScreenState2 extends State<ListScreen2> { 46 List<Model> modelList; 47 48 49 // 起動時に初期化 50 void initState() { 51 super.initState(); 52 53 modelList = []; 54 List<String> titleList = ["Title A", "Title B", "Title C"]; 55 List<String> subTitleList = ["SubTitle A", "SubTitle B", "SubTitle C"]; 56 57 for (int i = 0; i < 3; i++) { 58 Model model = Model( 59 title: titleList[i], 60 subTitle: subTitleList[i], 61 key: i.toString(), 62 ); 63 modelList.add(model); 64 } 65 } 66 67 // リスト項目となる削除可能なウィジェットを作成 68 Widget buildItem(Model model, int index) { 69 return Dismissible( 70 key: Key('${model.key}'), // 項目が特定できるよう固有の文字列をキーとする 71 background: Container( 72 padding: EdgeInsets.only( 73 right: 48, 74 ), 75 alignment: AlignmentDirectional.centerEnd, 76 color: Colors.red, 77 child: Icon( 78 Icons.delete, 79 color: Colors.white, 80 ), 81 ), // スワイプしているアイテムの背景色 82 direction: DismissDirection.endToStart, 83 confirmDismiss: (direction) async { 84 // 削除するか確認する 85 return await showDialog( 86 context: context, 87 builder: (BuildContext context) { 88 return AlertDialog( 89 title: Text('確認ダイアログ'), 90 content: Text('本当に削除しますか?'), 91 actions: <Widget>[ 92 TextButton( 93 onPressed: () => Navigator.of(context).pop(false), 94 child: Text('CANCEL'), 95 ), 96 TextButton( 97 onPressed: () => Navigator.of(context).pop(true), 98 child: 99 Text('DELETE', style: TextStyle(color: Colors.red))), 100 ], 101 ); 102 }, 103 ); 104 }, 105 onDismissed: (direction) { 106 // 削除時の処理 107 setState(() { 108 modelList.remove(model); 109 }); 110 }, 111 // 各項目のレイアウト 112 child: Card( 113 child: ListTile( 114 tileColor: Theme.of(context).cardColor, 115 title: Text('${model.title}', 116 style: TextStyle( 117 fontWeight: FontWeight.bold, fontSize: 20, height: 1.2)), 118 subtitle: Text('${model.key} ${model.subTitle}'), 119 onTap: () async { 120 var result = await Navigator.push( 121 context, 122 MaterialPageRoute( 123 builder: (context) => NextScreen2(model: model))); 124 //print(result); 125 setState(() { 126 if (result != null) { 127 model = result; 128 print('set state ' + model.title); 129 } 130 }); 131 }, 132 ))); 133 } 134 135 136 Widget build(BuildContext context) { 137 return Scaffold( 138 appBar: AppBar( 139 title: Text(widget.title), 140 ), 141 body: Container( 142 child: ReorderableListView( 143 onReorder: (int oldIndex, int newIndex) { 144 if (newIndex > oldIndex) { 145 // 元々下にあった要素が上にずれるため一つ分後退させる 146 newIndex -= 1; 147 } 148 149 // 並び替え処理 150 Model model = modelList[oldIndex]; 151 setState(() { 152 modelList.removeAt(oldIndex); 153 modelList.insert(newIndex, model); 154 }); 155 }, 156 padding: EdgeInsets.only(top: 4), 157 children: modelList 158 .asMap() 159 .map((i, item) => MapEntry(i, buildItem(item, i))) 160 .values 161 .toList()) 162 //], 163 )); 164 } 165} 166 167/////////// 168 169class NextScreen2 extends StatefulWidget { 170 Model _model; 171 172 NextScreen2({Key key, Model model}) 173 : assert(model != null), 174 this._model = model, 175 super(key: key); 176 177 178 _NextScreenState2 createState() => _NextScreenState2(); 179} 180 181class _NextScreenState2 extends State<NextScreen2> { 182 // 起動時に初期化 183 Model _initModel; 184 185 void initState() { 186 super.initState(); 187 188 _initModel = Model( 189 title: widget._model.title, 190 subTitle: widget._model.subTitle, 191 key: widget._model.key, 192 ); 193 } 194 195 196 Widget build(BuildContext context) { 197 return WillPopScope( 198 onWillPop: () { 199 print('onWillPop'); 200 Navigator.of(context).pop(_initModel); 201 return Future.value(false); 202 }, 203 child: Scaffold( 204 appBar: AppBar( 205 title: Text('Edit'), 206 ), 207 body: Container( 208 padding: EdgeInsets.all(20), 209 child: Column( 210 mainAxisAlignment: MainAxisAlignment.start, 211 children: <Widget>[ 212 // タイトル 213 TextField( 214 controller: TextEditingController() 215 ..text = widget._model.title, 216 decoration: InputDecoration( 217 hintText: 'タイトル', 218 hintStyle: TextStyle( 219 color: Colors.black26, 220 ), 221 ), 222 onChanged: (text) { 223 widget._model.title = text; 224 }, 225 ), 226 227 // 登録ボタン 228 Container( 229 //padding: EdgeInsets.all(_PADDING_SIZE), 230 padding: EdgeInsets.fromLTRB(0, 10, 0, 10), 231 width: double.infinity, // 横幅いっぱいに広げる 232 height: 60, 233 child: ElevatedButton( 234 child: Text('確定'), 235 onPressed: () { 236 // TODO: 新規登録 237 // 前の画面に戻る 238 print('確定'); 239 if (Navigator.of(context).canPop()) { 240 Navigator.of(context).pop(widget._model); 241 } 242 }, 243 ), 244 ), 245 246 // キャンセルボタン 247 Container( 248 //padding: EdgeInsets.all(_PADDING_SIZE), 249 padding: EdgeInsets.fromLTRB(0, 10, 0, 10), 250 width: double.infinity, // 横幅いっぱいに広げる 251 height: 60, 252 child: ElevatedButton( 253 child: Text('キャンセル'), 254 onPressed: () { 255 print('キャンセル'); 256 // 前の画面に戻る 257 widget._model = _initModel; 258 if (Navigator.of(context).canPop()) { 259 Navigator.of(context).pop(null); 260 } 261 }, 262 style: ElevatedButton.styleFrom( 263 primary: Colors.grey[200], 264 onPrimary: Colors.black45, 265 ), 266 ), 267 ), 268 ], 269 ), 270 ), 271 )); 272 } 273} 274

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

コード全体がないので、確かなことをは言えませんが、 _initModel が書き換わってしまっているのだと思います。

そこで、キャンセル時は _initModel を返さずに null を返すようにして、 TopScreen側では resultnull じゃなかったら setState で状態を更新するようにしたらどうでしょうか


コード全体を見ての追記:5/26 13:00

そうですね、TopScreenのmodelの参照をNectScreen側に渡しているので、TextFieldのonChangeの時点で値が変わってしまっています

NectScreenのTextEditingControllerの使い方がよくない気がします。TextEditingControllerが現在編集中の値を保持する役割をしてくれます。
TextEditingControllerの初期化をbuild内でしていますが、TextEditingControllerはプロパティとして保持して、確定時にModelに反映するような実装にするといい気がします

修正してみたコードを以下に貼っておきます(もともとのコードと比較すると変更箇所がわかると思います)

import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class Todo { final String title; final String description; Todo({@required this.title, @required this.description}) : assert(title != null), assert(description != null); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, // Debug文言の削除 title: 'Navigation', home: ListScreen2(title: 'List'), ); } } class Model { String title; String subTitle; String key; Model({ @required this.title, @required this.subTitle, @required this.key, }); } class ListScreen2 extends StatefulWidget { ListScreen2({Key key, this.title}) : super(key: key); final String title; @override _ListScreenState2 createState() => _ListScreenState2(); } class _ListScreenState2 extends State<ListScreen2> { List<Model> modelList; @override // 起動時に初期化 void initState() { super.initState(); modelList = []; List<String> titleList = ["Title A", "Title B", "Title C"]; List<String> subTitleList = ["SubTitle A", "SubTitle B", "SubTitle C"]; for (int i = 0; i < 3; i++) { Model model = Model( title: titleList[i], subTitle: subTitleList[i], key: i.toString(), ); modelList.add(model); } } // リスト項目となる削除可能なウィジェットを作成 Widget buildItem(Model model, int index) { return Dismissible( key: Key('${model.key}'), // 項目が特定できるよう固有の文字列をキーとする background: Container( padding: EdgeInsets.only( right: 48, ), alignment: AlignmentDirectional.centerEnd, color: Colors.red, child: Icon( Icons.delete, color: Colors.white, ), ), // スワイプしているアイテムの背景色 direction: DismissDirection.endToStart, confirmDismiss: (direction) async { // 削除するか確認する return await showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text('確認ダイアログ'), content: Text('本当に削除しますか?'), actions: <Widget>[ TextButton( onPressed: () => Navigator.of(context).pop(false), child: Text('CANCEL'), ), TextButton( onPressed: () => Navigator.of(context).pop(true), child: Text('DELETE', style: TextStyle(color: Colors.red))), ], ); }, ); }, onDismissed: (direction) { // 削除時の処理 setState(() { modelList.remove(model); }); }, // 各項目のレイアウト child: Card( child: ListTile( tileColor: Theme.of(context).cardColor, title: Text('${model.title}', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 20, height: 1.2)), subtitle: Text('${model.key} ${model.subTitle}'), onTap: () async { var result = await Navigator.push( context, MaterialPageRoute( builder: (context) => NextScreen2(model: model))); //print(result); setState(() { if (result != null) { model = result; print('set state ' + model.title); } }); }, ))); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Container( child: ReorderableListView( onReorder: (int oldIndex, int newIndex) { if (newIndex > oldIndex) { // 元々下にあった要素が上にずれるため一つ分後退させる newIndex -= 1; } // 並び替え処理 Model model = modelList[oldIndex]; setState(() { modelList.removeAt(oldIndex); modelList.insert(newIndex, model); }); }, padding: EdgeInsets.only(top: 4), children: modelList .asMap() .map((i, item) => MapEntry(i, buildItem(item, i))) .values .toList()) //], )); } } /////////// class NextScreen2 extends StatefulWidget { Model _model; NextScreen2({Key key, @required Model model}) : assert(model != null), this._model = model, super(key: key); @override _NextScreenState2 createState() => _NextScreenState2(); } class _NextScreenState2 extends State<NextScreen2> { // 起動時に初期化 Model _initModel; TextEditingController _controller; void initState() { super.initState(); _initModel = Model( title: widget._model.title, subTitle: widget._model.subTitle, key: widget._model.key, ); _controller = TextEditingController(); _controller.text = widget._model.title; } @override Widget build(BuildContext context) { return WillPopScope( onWillPop: () { print('onWillPop'); Navigator.of(context).pop(_initModel); return Future.value(false); }, child: Scaffold( appBar: AppBar( title: Text('Edit'), ), body: Container( padding: EdgeInsets.all(20), child: Column( mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ // タイトル TextField( controller: _controller, decoration: InputDecoration( hintText: 'タイトル', hintStyle: TextStyle( color: Colors.black26, ), ), ), // 登録ボタン Container( //padding: EdgeInsets.all(_PADDING_SIZE), padding: EdgeInsets.fromLTRB(0, 10, 0, 10), width: double.infinity, // 横幅いっぱいに広げる height: 60, child: ElevatedButton( child: Text('確定'), onPressed: () { // TODO: 新規登録 // 前の画面に戻る print('確定'); widget._model.title = _controller.text; if (Navigator.of(context).canPop()) { Navigator.of(context).pop(); } }, ), ), // キャンセルボタン Container( //padding: EdgeInsets.all(_PADDING_SIZE), padding: EdgeInsets.fromLTRB(0, 10, 0, 10), width: double.infinity, // 横幅いっぱいに広げる height: 60, child: ElevatedButton( child: Text('キャンセル'), onPressed: () { print('キャンセル'); // 前の画面に戻る widget._model = _initModel; if (Navigator.of(context).canPop()) { Navigator.of(context).pop(); } }, style: ElevatedButton.styleFrom( primary: Colors.grey[200], onPrimary: Colors.black45, ), ), ), ], ), ), )); } }

この実装なら pop() で値を返す必要もないです
もし pop() で値を返すような実装にしたいなら、NectScreenに渡す際にモデルを生成し直せば、それでも実現できると思います

投稿2021/05/25 22:31

編集2021/05/26 08:15
popobot

総合スコア6586

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

momiji0210

2021/05/26 02:37

アドバイス、ご回答ありがとうございます!そちらの方が良さそうですね。 nullに変更してsetStateでnull更新しないような処理を書いたのですが変更されてしまいました。 何となくですが、画面遷移時の値を渡し方がいけないのかもしれません。参照されている気がします。 setStateはprint出力されていたので呼ばれているっぽいです。(null以外で) コード全体がないの失礼しました!追記させてください。
momiji0210

2021/05/26 04:41

回答ありがとうございます!あまり、参考になる書籍もなかったので、アドバイス本当にありがたいです。 ソースコードなのですが、説明文と同じコードではないでしょうか。 Difffツールで比較してみたのですが、変更箇所が見当たらなくて・・・。 何となく問題部分が頂いたアドバイスでわかったので、色々といじってみます!
momiji0210

2021/05/26 04:57

できましたー!!!!!アドバイス本当にありがとうございます。 説明がわかりやすくて、助かりました・・・。 質問ばかりで申し訳ないのですが、今回のプログラムで 下記認識となったのですがあっておりますでしょうか。 ・下記ソースは画面1の参照渡しをしてる  画面2で値を変更すると画面1の値も変更される var result = await Navigator.push( context, MaterialPageRoute( builder: (context) => NextScreen2(model: model))); ・上記のような参照渡しをしない場合、画面2からpop(model)みたいに記載すると画面1のresultで値を取得できる みたいな認識で大丈夫でしょおうか。
popobot

2021/05/26 08:22 編集

コード貼り直しました。すみませんでした! そういう認識であっていると思います。以下のようにモデルのコピーを作って渡すなどすれば、いいと思います final copiedModel = Model( title: model.title, subTitle: model.subTitle, key: model.key, ); var result = await Navigator.push( context, MaterialPageRoute( builder: (context) => NextScreen2(model: copiedModel)));
momiji0210

2021/05/26 08:37

ご丁寧にありがとうございます!なるほど・・・。 そうやってオブジェクトを新しく作るのもありなんですね! めちゃくちゃ参考になりました。 わざわざソースコードまで見ていただいてありがとうございます。 有用な情報助かりました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.47%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問