Отсутствует изменение состояния в BlocListener (Cubit, начальная загрузка)

Я рефакторинг некоторых своих блоков до локтей, и я борюсь с неожиданным поведением.

Виджет

@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, и каждый дочерний элемент (прослушиватель и строитель) будет получать события изменения состояния с самого начала.

Что я здесь понимаю или делаю не так?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
75
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Потому что, когда 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(
    ...
  }

Для меня ясно, что экземпляр BlocProvider будет создан раньше экземпляра BlocConsumer. Но я ожидаю, что «обратный вызов ceate» будет запущен после построения дерева виджетов. На данный момент я не могу понять предложенное вами решение, но попробую. Спасибо за ваш вклад!

soumas 12.07.2024 12:51

Я попытался создать Cubit и передать начальное состояние перед BlocProvider, но, как и ожидалось, безуспешно. Кстати: используя Bloc вместо Cubit (добавьте событие вместо вызова Cubit.init()), все работает нормально. Но я хочу использовать Cubits и виджеты без сохранения состояния.

soumas 12.07.2024 13:04
Ответ принят как подходящий

Как упоминалось 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 (или, по крайней мере, задумано). Зная это, первое предложенное решение становится моим любимым. Он делает именно то, что я изначально ожидал, и, на мой взгляд, это ни в коем случае не хакерство. Спасибо всем за вашу помощь!

soumas 15.07.2024 08:39

Другие вопросы по теме