Я рефакторинг некоторых своих блоков до локтей, и я борюсь с неожиданным поведением.
Виджет
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => injector.get<DocumentListCubit>()..load(),
child: BlocConsumer<DocumentListCubit, DocumentListState2>(
listener: (context, state) {
context.read<GlobalBusyIndicatorCubit>().busy = state.phase == DocumentListState2Enum.loading;
},
builder: (context, state) {
switch (state.phase) {
case DocumentListState2Enum.data:
return const DocumentListWidget();
case DocumentListState2Enum.failure:
return DvtErrorWidget(error: state.error);
default:
return const SizedBox();
}
},
),
);
}
Локоть:
Future<void> load() async {
try {
//await Future.delayed(const Duration(milliseconds: 100)); <-- this line 'solves' the problem
emit(state.copyWith(state: DocumentListState2Enum.loading));
final lst = await schreibenRepo.find();
emit(state.copyWith(state: DocumentListState2Enum.data, documents: lst));
} catch (e) {
emit(state.copyWith(state: DocumentListState2Enum.failure, error: e));
rethrow;
}
return Future.delayed(Duration.zero);
}
Прослушиватель не получает первое выданное состояние «загрузка».
Я отладил равенство -> первое выданное состояние «загрузка» в порядке (не равно предыдущему состоянию) Я обнаружил, что это «работает», когда я даю задержку, например. 100 мс перед отправкой состояния «загрузка». Мне кажется, что это своего рода проблема состояния гонки.
Я ожидаю, что «обратный вызов создания» будет вызван после полной инициализации BlocConsumer, и каждый дочерний элемент (прослушиватель и строитель) будет получать события изменения состояния с самого начала.
Что я здесь понимаю или делаю не так?
Потому что, когда Flutter создает дерево виджетов, оно смотрит сверху. Это означает, что сначала будет создан BlocProvider, следовательно, функция
create: ... => load()
будет запускаться до того, как ваш BlocConsumer будет создан и у него будет прослушиватель.
Обычно у меня есть функция инициализации, с помощью которой я могу установить правильное состояние инициализации при работе с Bloc. Но я не знаком с Cubit. Вероятно, вы можете сначала инициализировать свой injector.get<DocumentListCubit>()
с состоянием загрузки, прежде чем возвращать BlocProvider.
@override
Widget build(BuildContext context) {
// create initLoading function first
injector.get<DocumentListCubit>().initLoading()
return BlocProvider(
...
}
Я попытался создать Cubit и передать начальное состояние перед BlocProvider, но, как и ожидалось, безуспешно. Кстати: используя Bloc вместо Cubit (добавьте событие вместо вызова Cubit.init()), все работает нормально. Но я хочу использовать Cubits и виджеты без сохранения состояния.
Как упоминалось manhtuan21, основной причиной является проблема состояния гонки: метод load() выполняется до тех пор, пока не будет выполнено первое ожидание, и во время этого ожидания будет построено оставшееся дерево виджетов. Поэтому ваш BlocConsumer еще не прослушивает, когда выполняется первый выпуск.
Несколько идей, как это исправить:
create: (context) {
final cubit = DocumentListCubit();
WidgetsBinding.instance.addPostFrameCallback((_) {
cubit.load();
});
return cubit;
},
Это задержит выполнение метода load() до тех пор, пока все дерево виджетов не будет построено и BlocConsumer не будет его прослушивать.
Быстро, легко и немного хакерски.
Если вы сохраните свой код в том виде, в каком он есть, прослушиватель никогда не будет вызываться при первом выбрасывании(state.copyWith(state: DocumentListState2Enum.loading)); Следовательно, вы можете установить для context.read().busy значение true по умолчанию. После создания нового состояния ему будет присвоено значение false.
Быстро и просто.
Замените BlocConsumer только на BlocBuilder и обработайте LoadingState в методе сборки (например, верните ProgressIndicator). Но это будет совершенно другой подход, чем использование GlobalBusyIndicatorCubit.
Большой рефакторинг.
Вдохновляясь выступлением Марцина Войнаровски на FlutterCon Europe 24 «Презентационные мероприятия – недостающая часть в BLoC», вы можете ввести вторичный «поток презентации» в свой BLoC. Тогда ваша концепция индикатора прогресса могла бы прослушивать этот поток вместо использования BlocConsumer.
Не уверен, что это хороший дизайн...
Приятного кодирования.
Хорошо, теперь ясно, что обратный вызов создания BlocProvider вызывается до того, как дерево виджетов будет построено, и мне кажется, что это желаемое поведение Bloc (или, по крайней мере, задумано). Зная это, первое предложенное решение становится моим любимым. Он делает именно то, что я изначально ожидал, и, на мой взгляд, это ни в коем случае не хакерство. Спасибо всем за вашу помощь!
Для меня ясно, что экземпляр BlocProvider будет создан раньше экземпляра BlocConsumer. Но я ожидаю, что «обратный вызов ceate» будет запущен после построения дерева виджетов. На данный момент я не могу понять предложенное вами решение, но попробую. Спасибо за ваш вклад!