Flutter

Riverpod+AnimatedList+flutter_slidableを組み合わせる

最終更新から5年以上たっています。ライブラリのバージョンや手順が変わっている可能性があります。

こんにちは。趣味グラマのNobu(@nm_aru)です。

趣味で作っているflutterアプリで、Riverpod+StateNotifier+Freezedを使っているのですが、リスト画面にAnimatedListを使ったところ、いくつか問題が発生しました。

いつものようにググればすぐ解決かと思いきや、なかなか答えが見つからず、試行錯誤の末に、ようやく自分で解決出来たので、ブログに残しておこうと思います。

解決した方法をサンプルプロジェクトとして作りました。

GitHub - nmaruy27/animatedlist_riverpod_sample
Contribute to nmaruy27/animatedlist_riverpod_sample development by creating an account on GitHub.

AnimatedListを使うと2つの問題が発生

最初はListViewを使っていたのですが、リストの削除時にアニメーションが欲しくなり、AnimatedListに単純に切り替えると、2つの問題が発生しました。

リストに何も表示されない

AnimatedListに切り替えると、データベースには確実にデータがあるのに、1件も表示されなくなりました。

この原因は分かっていませんが、恐らく以下の理由なのかなと考えました。

  1. リストのデータはRiverpod+StateNotifierを使って管理している
  2. StateNotifierのconstructorでデータベースからデータを取得している(async)
  3. AnimatedListにリストを渡した段階では、まだデータの取得が完了しておらず、データの取得が完了しても、AnimatedListがrebuildされていないのかもしれない

そこで、AnimatedListを使うWidgetの親Widgetで、StateNotifierのstateを使う処理を書いてみたところ、問題なく表示されるようになりました。

main.dart
// 〜省略〜

class Home extends HookWidget {
  const Home({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final todoList = useProvider(todoListProvider.state); // これを追加
    return Scaffold(
      appBar: AppBar(
        title: Text('Todo[${todoList.length}]') // ちょうど必要だったので、titleで件数表示に利用
      ),
      body: TodoListView()
    );
  }
}

// 〜省略〜

リストから削除する時にアニメーションが実行されない

Slidableを使って削除ボタンを表示し削除を実行すると、アニメーションが動く事無く一瞬にして消えてしまいました。

これではAnimatedListを使っている意味が無いので、どうしたものかと悩んだあげく、AnimatedListからの削除と、StateNotifierからの削除を時間差で行う事で解決しました。

ユーザとしては見た目から消えていれば良い訳で、データベースから消えるのに数百ミリセカンドの遅れがあっても何の問題も無いだろうという判断です。

main.dart
// 〜省略〜
  Slidable _buildItem(Todo todo, Animation<double> animation, int index, BuildContext context) {
    return Slidable(
      actionPane: SlidableDrawerActionPane(),
      child: SizeTransition(
          sizeFactor: animation,
          axis: Axis.vertical,
          child: ListTile(title: Text(todo.description), subtitle: Text(todo.id), onTap: () => {})),
      secondaryActions: <Widget>[
        IconSlideAction(
          caption: '削除',
          color: Colors.red,
          icon: Icons.delete,
          onTap: () {
            _listKey.currentState.removeItem(
                index, (context, animation) => _buildItem(todo, animation, index, context),
                duration: Duration(milliseconds: 200));
            _removeItem(context, todo);
          },
        ),
      ],
    );
  }

  void _removeItem(BuildContext context, Todo todo) async {
    await Future.delayed(
        Duration(milliseconds: 200), () => context.read(todoListProvider).remove(todo)); // 17行目のアニメーションにかかる時間と同じだけ遅らせた上で、データベースからの削除を実行する
    
  }
// 〜省略〜

まとめ

相変わらずGoogle先生に答えが無い時は厄介な事になりますが、そこを悩んで解決していくのもプログラミングの楽しさなので、今回も解決に至って本当に良かったです。

StackOverflowで同じような事を質問していた人がいましたが、誰も回答していなかったので、勇気を持って回答してみました。

Just a moment...
made with Flutter

この記事を書いた人がつくったアプリ

Flutter の記事

コメントの受け付けは終了しました。ご意見・ご質問は X(@nm_aru)へどうぞ。