パルカワ2

最近はFlutterをやっています

UIで使うデータクラスでIterableを使うかListを使うか

UIで使うデータクラスのメンバー変数にIterableでデータを持たせていた。Listが持つaddやらなんやらをしてほしくないからそもそもそういうIFを提供しないのが良いと思っていた。

@freezed
class User with _$User {
  User({
    required Iterable<Favorite> favorites,
  }) = _User;
}

一方で、DartのIterableは遅延処理される。

例えば以下のようにmapの中で例外を投げる実装があったとする。これは遅延処理されるので、buildメソッド内で例外が発生しうる。

@freezed
class User with _$User {
  User({
    required Iterable<Favorite> favorites,
  }) = _User;
}

final provider = FutureProvider((ref) async {
  final user = await ...; // なんらかの方法でUserResponse取得
  return User(favorites: user.favorites.map((e) {
    swtich (e) {
       case ...:
         return Favorite();
       case ...:
         throw UnknownException(); // 例外を投げている
    }
  }));
});

class UserView extends ConsumerWidget {
  @override
  Widget build(context, ref) {
    final data = ref.watch(provider); // AsyncValue<User>を返す。ここではまだmapの内容は評価されていない
    return data.map(data: (data) {
      print(user); // ここではまだfavoritesは評価されていない
      return FavoritesView(data.favorites); // ここでfavoritesが評価されるので、ここで例外が投げられる可能性がある
    }, loading: (_) {}, error: (_) {});
  }
}

どこで評価されるのか/すでに評価済みかもわからないので、AsyncErrorにいくと思って書いていても、buildメソッド内で例外が起きてしまう。そしてbuildメソッド内で例外が起きるとErrorWidget.builderを定義していない限りWidgetが表示されないのでユーザーはなにが起きてるのかわからない。

Hixieさんは、buildメソッド内で例外を投げないことを推奨すると言ってる。それを防ぐ仕組みがないのにそんな無茶な…と凡人の自分は思うのだが、まあ言ってるのでしょうがない。

github.com

Widgetで利用するデータは、すべて評価済みのものを渡すのがよさそう。そう考えるとUIで使うデータクラスは、基本的にListに依存するのがよく、必要であればIterableとするのがよいのかもしれない。