LoginSignup
20
5

More than 3 years have passed since last update.

FlutterのListViewでリストを削除した際のA dismissed Dismissible widget is still part of the tree

Last updated at Posted at 2020-01-11

はじめに

最近Flutterの勉強を開始しました。
初めてのアプリということで、ToDoアプリの開発を行っていました。
 
ListViewというwidgetを使っているのですが、途中エラーが発生し困ったので解消法の共有です。
超初心者向けなのであしからず。
 

現象

スクリーンショット 2020-01-10 0.03.41.png

上記のような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);
                  });

結論

DismissiblekeyKey(index.toString())を指定しているのが原因でした。
これだと、途中のTODOが削除された場合、削除したindexが他のTODOで使いまわされてしまい、不整合が起きるので、エラーになっているようです。完全に初心者の過ちでした。。(恥ずかしい)

DismissiblekeyObjectKey(_todos[index])にするとTODOごとにユニークなキーになるので、エラーが発生しなくなりました。

Flutter、面白いですね。勉強引き続き頑張ります。
これから頑張っていくので、記事に「いいね」いただけるか、githubフォローしていただけるか、Starつけていただけるとモチベーションに繋がるので、もしよかったらよろしくお願いします。:flushed:
https://github.com/kazumaz/flutter-study

20
5
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
5