Изменение состояния Flutter Bloc не обновляет пользовательский интерфейс с помощью get_it

Я создавал функцию входа / аутентификации, используя комбинацию этого руководства по входу в систему и руководств по чистой архитектуре ресокодера. Он на 99% работает отлично, но не реагирует должным образом на нажатие LoginButton.

По какой-то причине, когда LoginBloc вызывает AuthenticationBloc.add(loggedin()), AuthenticationBloc прекрасно выдает состояние AuthenticationAuthenticated(), но BlocBuilder в Main.dart не получает изменения состояния. Даже OnTransition внутри SimpleBlocDelegate срабатывает при выдаче AuthenticationAuthenticated, но BlocBuilder ничего не делает.

Main.dart выглядит так:

import 'package:bloc/bloc.dart';
import 'package:flutter_app/dependency_injector.dart' as di;
import 'package:flutter_app/features/login/presentation/pages/login_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'features/login/presentation/bloc/user_login_bloc.dart';
import 'features/login/presentation/bloc/user_login_events.dart';
import 'features/login/presentation/bloc/user_login_states.dart';

class SimpleBlocDelegate extends BlocDelegate {
  @override
  void onEvent(Bloc bloc, Object event) {
    print(event);
    super.onEvent(bloc, event);
  }

  @override
  void onTransition(Bloc bloc, Transition transition) {
    print(transition);
    super.onTransition(bloc, transition);
  }

  @override
  void onError(Bloc bloc, Object error, StackTrace stackTrace) {
    print(error);
    super.onError(bloc, error, stackTrace);
  }
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await di.init(); //Dependency Injection using get_it
  BlocSupervisor.delegate = SimpleBlocDelegate();
  runApp(
    BlocProvider<UserAuthenticationBloc>(
      create: (_) => sl<UserAuthenticationBloc>()..add(AppStarted()),
      child: App(),
    ),
  );
}

class App extends StatelessWidget {
  App({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocBuilder<UserAuthenticationBloc, AuthenticationState>(
        builder: (context, state) {
          if (state is AuthenticationAuthenticated) {
            return Container(
              child: HomePage(); // THIS NEVER HAPPENS, even though AuthBloc yields the State
          }
          if (state is AuthenticationUnauthenticated) {
            return LoginScreen(); // THIS yeilds fine when AppStarted in passed on init.
          }
          if (state is AuthenticationLoading) {
            return LoadingIndicator();
          }
          return Scaffold(
            body: SplashPage();
          )
        },
      ),
    );
  }
}

Я могу только думать, что это как-то связано с get_it. Внедрение зависимостей выглядит так:

final sl = GetIt.instance;

Future<void> init() async {
  sl.registerFactory(
    () => UserAuthenticationBloc(
      getCachedUser: sl(),
    ),
  );

  sl.registerFactory(
    () => LoginBloc(authenticationBloc: sl(), getUserFromEmailAndPassword: sl()),
  );
...
}

а затем в дереве виджетов для loginscreen создается LoginBloc, поэтому он доступен для формы входа.

class LoginScreen extends StatelessWidget {
  LoginScreen({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: BlocProvider<LoginBloc>(
        create: (_) => sl<LoginBloc>(),
        child: LoginForm(), //login form
      ),
    );
  }
}

ДВА РЕДАКТИРОВАНИЯ: 1. Я изменил UserAuthenticationBloc в файле внедрения зависимостей с factory на lazysingleton ... теперь он работает. Однако я слышал, что использование синглтонов для классов с Streams может вызвать утечку памяти ?? Полагаю, это означает, что LoginBloc не обращается к тому же экземпляру AuthBloc, который слушает Main.dart? Я не знаю, как это сделать без синглтона ...

  1. Код UserAuthenticationBloc:
    class UserAuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState> {
      final GetCachedUser getCachedUser;
      UserAuthenticationBloc({
        @required GetCachedUser getCachedUser,
      })  : assert(getCachedUser != null),
            getCachedUser = getCachedUser;

      @override
      AuthenticationState get initialState => AuthenticationUninitialized();

      @override
      Stream<AuthenticationState> mapEventToState(AuthenticationEvent event) async* {
        if (event is AppStarted) {
             yield AuthenticationUnauthenticated();
          }
        }

        if (event is LoggedIn) {
          yield AuthenticationAuthenticated(); //this fires.
        }
      }
    }

person Gary Frewin    schedule 17.05.2020    source источник
comment
Можете ли вы поделиться кодом UserAuthenticationBloc   -  person Sanjay Sharma    schedule 17.05.2020
comment
@SanjaySharma Добавили.   -  person Gary Frewin    schedule 17.05.2020
comment
AuthenticationState наследует Equatable?   -  person Sanjay Sharma    schedule 17.05.2020
comment
@SanjaySharma Да, это так.   -  person Gary Frewin    schedule 17.05.2020
comment
@GaryFrewin, я по достоинству оценил ваши правки. Я огляделся и не могу найти прямого ответа на ваш вопрос об утечках памяти. Вы что-нибудь узнали? Если я найду что-нибудь, я вернусь к вам.   -  person M-Solutions    schedule 28.05.2020
comment
@ M-Solutions Боюсь, что больше информации я так и не нашел. Я продолжил то, что делал, и теперь в приложении настроено 5 блоков. Производительность пока что кажется хорошей.   -  person Gary Frewin    schedule 28.05.2020
comment
@GaryFrewin, тебе удалось заставить его работать? У меня аналогичная проблема, изменение registerFactory на registerLazySingleton решает проблему, но если я очищаю маршрут и снова перехожу на страницу, выдается исключение. Невозможно добавить новые события после вызова закрытия, произошедшего в блоке Экземпляр 'LoginBloc'   -  person trinvh    schedule 12.06.2020
comment
@trinvh Привет. Как только я изменил класс bloc на lazysingleton, он начал работать. У меня вообще не было проблем с производительностью, и я не совсем все это понимаю, поэтому я оставил все как есть. Если у меня возникнут проблемы с производительностью, я посмотрю еще раз. Однако я также использовал equatable для своих состояний. Я перестал это делать, поскольку читал в другом месте, это может помешать конструктору блоков.   -  person Gary Frewin    schedule 12.06.2020
comment
Привет, getit создает новый блок каждый раз, когда он запрашивает, потому что вы зарегистрировали его в registerFactory. Таким образом, loginbloc имеет другой экземпляр authbloc, чем основной виджет.   -  person A.K.    schedule 06.01.2021


Ответы (1)


Я столкнулся с той же проблемой в своем проекте. Я использую тот же стек, а также, когда я добавляю событие из другого блока, пользовательский интерфейс не видит никаких изменений. Временное решение, которое я использую - я передаю контекст этому блоку с помощью GlobalKey<ScaffoldState>. Итак, UI и Bloc используют один и тот же контекст. Вы можете использовать его в своем LoginBloc:

BlocProvider.of<UserAuthenticationBloc>(yourScaffoldKey.currentContext).add(LoggedIn event)
person Andrei Mikhniuk    schedule 12.10.2020