はじめに
最近Flutterの勉強を開始しました。
初めてのアプリということで、ToDoアプリの開発を行っていました。
ListViewというwidgetを使っているのですが、途中エラーが発生し困ったので解消法の共有です。
超初心者向けなのであしからず。
現象
上記のようなTodoリストを作成し、各 TodoはFlipすることで削除できるようにしました。
しかし、test3→test2→test1の順で削除すると、問題なく削除されるのですが、test3を削除する前にtest2を削除するとエラーが発生するという事象に会いました。
エラー内容
Performing hot reload...
Reloaded 1 of 478 libraries in 396ms.
flutter: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
flutter: The following assertion was thrown building Dismissible-[<'0'>](dirty, dependencies:
flutter: [Directionality], state: _DismissibleState#74b1e(tickers: tracking 2 tickers)):
flutter: A dismissed Dismissible widget is still part of the tree.
flutter: Make sure to implement the onDismissed handler and to immediately remove the Dismissible widget from
flutter: the application once that handler has fired.
flutter:
flutter: The relevant error-causing widget was:
flutter: Dismissible-[<'0'>]
flutter: file:///Users/kazuma/Desktop/myproject/flutter-study/todo_app/lib/main.dart:53:20
flutter:
flutter: When the exception was thrown, this was the stack:
flutter: #0 _DismissibleState.build.<anonymous closure> (package:flutter/src/widgets/dismissible.dart:526:11)
flutter: #1 _DismissibleState.build (package:flutter/src/widgets/dismissible.dart:535:8)
flutter: #2 StatefulElement.build (package:flutter/src/widgets/framework.dart:4334:27)
flutter: #3 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4223:15)
flutter: #4 Element.rebuild (package:flutter/src/widgets/framework.dart:3947:5)
flutter: #5 StatefulElement.update (package:flutter/src/widgets/framework.dart:4413:5)
flutter: #6 Element.updateChild (package:flutter/src/widgets/framework.dart:2977:15)
flutter: #7 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:545(ddd78475600b5492fc67889427724d06.png)════════════════════════════════════════════════════════════════
エラーとなっているコード
Widget build(BuildContext context) => Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text('Todoリスト'),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
// Todo入力画面への遷移
_navigateAndInputTodo(context);
},
),
body: ListView.builder(
itemCount: _todos.length, //ここで表示可能なリスト数を制限しないと、表示できなくなった時にエラーになる。
itemBuilder: (BuildContext context, int index) {
return Dismissible( //**ここでエラー発生**
// KeyはFlutterが要素を一意に特定できるようにするための値を設定する。
key: Key(index.toString()),
// onDismissedの中にスワイプされた時の動作を記述する。
// directionにはスワイプの方向が入るため、方向によって処理を分けることができる。
onDismissed: (direction) {
setState(() {
// スワイプされた要素をデータから削除する
_todos.removeAt(index);
});
// スワイプ方向がendToStart(画面左から右)の場合の処理
if (direction == DismissDirection.endToStart) {
Scaffold.of(context).removeCurrentSnackBar();
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text("削除しました")));
// スワイプ方向がstartToEnd(画面右から左)の場合の処理
} else {
Scaffold.of(context).removeCurrentSnackBar();
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text("削除しました")));
}
},
// スワイプ方向がendToStart(画面左から右)の場合のバックグラウンドの設定
background: Container(
alignment: Alignment.centerLeft,
color: Colors.redAccent[700],
child: Padding(
padding: EdgeInsets.fromLTRB(20.0, 0.0, 0.0, 0.0),
child: Icon(Icons.delete_forever, color: Colors.white)),
),
// スワイプ方向がstartToEnd(画面右から左)の場合のバックグラウンドの設定
secondaryBackground: Container(color: Colors.blue),
child: Card(
child: ListTile(
title: Text(_todos[index].title),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
DetailScreen(todo: _todos[index])),
);
}),
),
);
},
),
);
当時参考にしたサイト
https://stackoverflow.com/questions/55792521/how-to-fix-a-dismissed-dismissible-widget-is-still-part-of-the-tree-error-in
https://stackoverflow.com/questions/58470821/a-dismissed-dismissible-widget-is-still-part-of-the-tree-in-flutter
結局ほとんどのサイトで、onDismissed
の時に、ちゃんとsetState
でオブジェクトを削除しようってことが書いていたんですよね。
ただ、当時、自分の実装もそうしており、、、下記です。
onDismissed: (direction) {
setState(() {
// スワイプされた要素をデータから削除する
_todos.removeAt(index);
});
結論
Dismissible
のkey
にKey(index.toString())
を指定しているのが原因でした。
これだと、途中のTODOが削除された場合、削除したindexが他のTODOで使いまわされてしまい、不整合が起きるので、エラーになっているようです。完全に初心者の過ちでした。。(恥ずかしい)
Dismissible
のkey
をObjectKey(_todos[index])
にするとTODOごとにユニークなキーになるので、エラーが発生しなくなりました。
Flutter、面白いですね。勉強引き続き頑張ります。
これから頑張っていくので、記事に「いいね」いただけるか、githubフォローしていただけるか、Starつけていただけるとモチベーションに繋がるので、もしよかったらよろしくお願いします。
https://github.com/kazumaz/flutter-study