コンテンツにスキップ

Bloc と Cubit

Cubit と Bloc は、Fuery のウィジェットと同じクエリ、ミューテーション、キャッシュを使います。そのため、今の状態管理をそのまま使い続けられます。オブザーバーの stream は、まず現在の結果を流し、その後は変更のたびに流します。リッスンするとオブザーバーが購読し、マウントされたウィジェットと同じように取得します。購読をキャンセルすると、オブザーバーの購読が解除されます。

ビルダーは画面ごとに選んでください。

画面 作り方
サーバーデータをほぼ届いたまま表示する QueryBuilder。間に Cubit を挟むと、QueryResult がすでに持っている読み込み中とエラーのフラグを作り直すことになります。
サーバーデータとアプリの状態(選択、フィルター、フォーム、複数のクエリの組み合わせ)を混ぜる クエリをリッスンする Cubit と BlocBuilder
Bloc で作ったアプリで、すでにイベントで動いている クエリをリッスンする Bloc と BlocBuilder

1 つの画面で両方を使うこともできます。アプリの状態には BlocBuilder、サーバーデータには QueryBuilder を使います。どちらの方法でも、同じキーを使う 2 つの画面は、同じキャッシュエントリとリクエストを共有します。そのため、データではなく画面で選んでください。

クエリは、リポジトリ経由ではなく Cubit からリッスンしてください。クエリ自体が、すでにキャッシュの層です。

ウィジェットの外では、observe() がクエリを、stream を持つオブザーバーに変えます。todosQuery は、クエリを整理すると同じクエリです。

class TodoCubit extends Cubit<TodoState> {
TodoCubit() : super(const TodoState()) {
_subscription = _todos.stream.listen((result) {
emit(state.copyWith(todos: result.data, loading: result.isLoading));
});
}
final _todos = todosQuery.observe();
late final StreamSubscription<QueryResult<List<Todo>>> _subscription;
Future<void> refresh() => _todos.refetch();
@override
Future<void> close() {
_subscription.cancel();
return super.close();
}
}

close() では、cancel() を await せずに呼び出してください。testWidgets と fakeAsync の中では、cancel() の Future が完了しません。Cubit と Bloc をテストするを参照してください。

emit.forEach は、ハンドラーが実行されている間、購読します。

class TodoBloc extends Bloc<TodoEvent, TodoState> {
TodoBloc() : super(const TodoState()) {
on<TodosSubscribed>((event, emit) {
return emit.forEach(
todosQuery.observe().stream,
onData: (result) =>
state.copyWith(todos: result.data, loading: result.isLoading),
);
});
}
}

Cubit や Bloc からのミューテーション

Section titled “Cubit や Bloc からのミューテーション”

Cubit は、独自のオブザーバーを持たずに、定義からミューテーションを実行します。mutateAsync はデータを返すかエラーをスローするので、Cubit のメソッドに向いています。

Future<void> add(String title) async {
try {
await addTodo.mutateAsync(title);
} catch (error) {
emit(state.copyWith(error: error));
}
}

Bloc のイベントハンドラーでも同じです(await addTodo.mutateAsync(event.title))。

  • 実行は Fuery.client を使います。テストのクライアントなど、別のクライアントを受け取った Cubit は、そのクライアントを渡します(addTodo.mutateAsync(title, client))。
  • 実行は Cubit ではなく、クライアントのキャッシュに属します。そのため、どの画面でも実行を表示できます。ウィジェットと共有するを参照してください。
  • オブザーバー(final _addTodo = addTodo.observe();)を保持するのは、Cubit が自身の実行の状態を、オブザーバーの result や stream で追う場合だけにしてください。

同じキーを使う Cubit と QueryBuilder は、同じキャッシュエントリを共有します。通知を既読にするなど、ある画面での変更は、Cubit にもすべてのウィジェットにも反映されます。

addTodo に mutationKey を付けると、Cubit や Bloc が開始した実行が、どの画面の MutationStateBuilder(mutation: addTodo) にも表示されます。ミューテーションのすべての実行を表示するを参照してください。

どこで開始されたかにかかわらず、ミューテーションのすべての実行に反応する Cubit は、MutationStateSlot を保持します。subscribeToRuns は、各実行の後続の変更ごとにリスナーを呼び出します。result は、現在の実行の一覧です。ミューテーションのすべての実行を参照してください。

class TodoCubit extends Cubit<TodoState> {
TodoCubit(QueryClient client)
: _adding = MutationStateSlot(addTodo, client),
super(const TodoState()) {
_adding.subscribeToRuns((previous, current) {
if (current.isError) emit(state.copyWith(error: current.error));
});
}
final MutationStateSlot<Todo, String, Object?> _adding;
@override
Future<void> close() {
_adding.dispose();
return super.close();
}
}

Fuery のウィジェット、フック、FueryProvider は、アプリのライフサイクルを接続します。そのため、アプリが再開すると、Fuery が古いクエリを再取得します。FueryProvider を使わずに Bloc からだけクエリを使うアプリでは、代わりに main で FueryBinding.ensureInitialized() を 1 回呼び出してください。アプリが再開したときを参照してください。

通知の Cubit は、バッジ用に未読の通知を数えます。一方、通知画面は、同じクエリを Fuery のウィジェットで表示します。サンプルアプリの README に、各画面で示している内容の一覧があります。