[Flutter/dart]Reduxパターンでアイテム削除に対応する
- M.R 
- 2023年9月9日
- 読了時間: 2分
前提
以下のようなケースを考えます。
- アイテムの一覧がある 
- ある1つのアイテムの詳細を表示するページがある 
- 詳細ページではそのアイテムの削除ができる 
- 削除したら他のページ(一覧ページなど)に戻る 
これはアプリではよくあるパターンだと思います。
課題
reduxパターンを用いている、より具体的にはProviderで状態を管理している場合、以下のような構成になっているのではないでしょうか?
- アプリ全体がStoreProviderの子になっており、storeの内容に変化があれば全体がリビルドされる 
- アイテム詳細ページは、StoreConnectorによってそのアイテムをstoreから切り出す 
- アイテム詳細ページ内で、アイテムを削除したら所定のページに遷移 
//詳細ページに移行
await Navigator.of(context).push(
  MaterialPageRoute(
    builder: (context)=> StoreConnector<AppState, ItemDetailViewModel>(
      converter: (store) 
      => ItemDetailViewModel.create(store, itemId), 
      //2 storeからitemIdのアイテムを取得
      builder: (context, model) {
        return ItemDetailPage(model);  //詳細ページ
      },
    )
  )
);//削除ボタン
Widget _itemDeleteButton(){
  return GestureDetector(
    child: const Icon(Icons.delete),
    onTap: ()async{
       await _deleteItem();  //削除
       await Navigator.of(context).push(
         //3 別ページに移動
       );
    }
}この場合、以下のような課題があります。
- アイテムが削除されると、StoreProviderによりアプリ全体がリビルドされる 
- 詳細ページ作成処理も再度実行される 
- つまり、「storeから指定のアイテムを切り出す処理」(2)も再度実行される 
- すでにそのidは存在しないので、例外になる 
もちろん詳細ページから抜け出すのが2の処理より早ければこの問題は起こりません(抜け出しているから詳細ページ作成処理は走らない)。
しかし、StoreProviderによるリビルドは詳細ページでの処理とは非同期に走るので、先に抜け出せることは保証できません。
解決策
- 2の処理で、storeに指定のアイテムが存在しない場合はnullを返します。 
- StoreConnectorによる切り出し部分では、convertorの返り値がnullの場合は別途用意した別のページ(「このアイテムは削除されました」など)に遷移するようにします 
await Navigator.of(context).push(
  MaterialPageRoute(
    builder: (context)=> StoreConnector<AppState, ItemDetailViewModel?>(
      converter: (store) => 
      ItemDetailViewModel.create(store, itemId),
      builder: (context, model) {
        if(model == null){
          return ItemDeletedPage(); 
          //「このアイテムは削除されました」が表示される
        }
        return ItemDetailPage(model);
      },
    )
  )
);最後に
根本的な解決にはなっていない気もしますが、プラットフォームに乗っかるうえではある程度設計に制約が出てしまうのは仕方ないかなと思います。
reduxに限らずProviderを用いていると、このように予期しない、制御もできないタイミングでページがリビルドされたりするので注意が必要ですね。
というか大前提として「削除されたアイテムを取り出すことなんてないだろ」と思って例外処理してないのが悪いですねw






コメント