Flutter Bloc 状态更改未使用 get_it 更新 UI

Posted

技术标签:

【中文标题】Flutter Bloc 状态更改未使用 get_it 更新 UI【英文标题】:Flutter Bloc state change is not updated UI with get_it 【发布时间】:2020-09-03 04:28:23 【问题描述】:

我一直在结合使用此登录教程和 resocoder 清洁架构教程来构建登录/身份验证功能。它 99% 工作正常,但它无法正确响应按下的 LoginButton

由于某种原因,当LoginBloc 调用AuthenticationBloc.add(loggedin()) 时,AuthenticationBloc 产生AuthenticationAuthenticated() 状态就好了,但 Main.dart 中的BlocBuilder 没有收到状态更改。当产生AuthenticationAuthenticated 时,即使是SimpleBlocDelegate 中的OnTransition 也会被触发,但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从工厂更改为lazysingleton......现在它可以工作了。但是,我听说对带有 Streams 的类使用单例会导致内存泄漏??我想这意味着 LoginBloc 没有与 Main.dart 正在听的 AuthBloc 的同一个实例交谈?我不知道如何确保没有单例......

    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.
        
      
    

【问题讨论】:

可以分享UserAuthenticationBloc的代码 @SanjaySharma 已添加。 AuthenticationState 是否继承 Equatable @SanjaySharma 是的。 @M-Solutions 恐怕我再也找不到更多信息了。我继续我正在做的事情,现在在应用程序中以这种方式设置了 5 个区块。到目前为止,性能似乎还不错。 【参考方案1】:

编辑 使用 bloc 提供的方法进行依赖注入,而不是 get_it。创建一个单例可能是一个问题,因为它不会被自动处理。BlocProvider 处理并处理文档中提到的创建的块

在大多数情况下,应该使用 BlocProvider 创建新的块,这些块将可供子树的其余部分使用。在这种情况下,由于 BlocProvider 负责创建 bloc,它会自动处理关闭 bloc。

并使用BlocProvider.value 传递值,如集团官方documentation 中建议的那样。

BlocProvider(
  create: (BuildContext context) => BlocA(service1: sl<Service1>()),
  child: ChildA(),
);

这就是我一起使用 BlocProvider 和 get_it 的方式。我将 get_it 用于 Bloc 以外的所有内容。而bloc的参数由get_it的依赖注入提供。

如果您想使用 get_it,请阅读 TLDR;部分。

TLDR;

仅在必要时使用Singleton (AuthenticationBloc)。并继续为所有其他集团(LoginBloc 等)使用Factory

final sl = GetIt.instance;
final Environment _env = Environment();

Future<void> init() async 
  //! Core
  ..load some singletons

  //! Bloc
  sl.registerLazySingleton(() => AuthenticationBloc(secureStorage: sl()));
  sl.registerFactory(() => LoginBloc(authenticationBloc: sl(), authService: sl()));
  sl.registerFactory(() => SignupBloc(authenticationBloc: sl(), authService: sl()));

概念

我在使用 bloc 时使用相同的方法。我们遇到的需要两个 bloc 进行通信的最常见情况是 AuthenticationBloc 与几乎所有其他 bloc 进行通信。

为什么registerFactory 不起作用。但是registerLazySingleton 可以

getit 对registerFactory的定义

你必须传递一个工厂函数 func,它返回一个 T 实现的 NEW 实例。每次调用 get() 都会返回一个新实例

根据 get_it 文档。每次我们调用sl&lt;AuthenticationBloc&gt;() 方法时,registerFactory 都会生成一个新的 Bloc 对象实例。 现在,当LoginBloc 构造函数请求一个参数并且我们在依赖注入文件中传递sl() 时,我们正在创建一个新实例并将其传递给我们的LoginBloc。因此,在整个应用程序中使用的AuthenticationBloc 实例不等于我们提供给LoginBloc 构造函数的AuthenticationBloc。因此,您的 AuthenticationBloc 不会监听 LoginBloc 传达的更改,因为它会将事件添加到 AuthenticationBloc 的其他实例。

registerLazySingleton 定义为

您必须传递一个工厂函数 func,该函数返回 T 的实现的一个实例。只有在您第一次调用 get() 时,才会调用该工厂函数来创建一个新实例。

如上所述,简单的解决方案是将依赖注入从registerFactory 更改为registerLazySingleton。通过这样做,您将在整个应用程序中提供AuthenticationBloc 的单个实例。因此,从 LoginBloc 添加到 AuthenticationBloc 的事件将开始工作。

建议的解决方案

可以有两种解决方案。一个是在这个问题中提出的,即将每个 Bloc 更改为 lazySingleton。但它不会在需要时创建新的 Bloc。通过使用该方法,您将在整个应用程序中使用相同的 Bloc 实例。它适用于大多数情况。

另一种方法是仅在必要时创建Singleton (AuthenticationBloc)。并继续为所有其他集团(LoginBloc 等)使用Factory

身份验证块

class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState> 
  final SecureStorage secureStorage;

  AuthenticationBloc(required this.secureStorage) : super(AppInitial());

  @override
  Stream<AuthenticationState> mapEventToState(AuthenticationEvent event) async* 
    if (event is AppStarted) 
      AuthModel? authModel = await secureStorage.getAuthUser();
      if (authModel != null && authModel.jwtToken.isNotEmpty && authModel.userId.isNotEmpty) 
        yield AuthenticationUserKnown(authModel: authModel);
       else 
        yield AuthenticationUserUnknown();
      
     else if (event is UserAuthenticated) 
      yield AuthenticationUserKnown(authModel: event.authModel);
     else if (event is UserLoggedOut) 
      yield AuthenticationUserUnknown();
    
  


登录区

class LoginBloc extends Bloc<LoginEvent, LoginState> 
  LoginBloc(required this.authenticationBloc, required this.validationHelper, required this.authService)
      : super(LoginInitial());
  final AuthenticationBloc authenticationBloc;
  final AuthService authService;
  final ValidationHelper validationHelper;

  @override
  Stream<LoginState> mapEventToState(LoginEvent event) async* 
    if (event is EmailAuthenticationRequested) 
      yield* _mapEmailAuthencationRequestedEventToState(event);
    
  

  Stream<LoginState> _mapEmailAuthencationRequestedEventToState(EmailAuthenticationRequested event) async* 
    yield AuthenticationInProgress();
    final authEither = await authService.loginWithEmail(email: event.email, password: event.password);

    yield authEither.fold(
      (failure) => LoginAuthenticationFailure(failureMessage: failure.errorMessage),
      (authModel) 
        authenticationBloc.add(UserAuthenticated(authModel: authModel));
        return LoginAuthenticationSuccess(authModel: authModel, authenticationMethod: AuthenticationMethod.EMAIL);
      ,
    );
  

  @override
  Future<void> close() 
    authenticationBloc.close();
    return super.close();
  


依赖注入器

final sl = GetIt.instance;
final Environment _env = Environment();

Future<void> init() async 
  //! Core
  ..load some singletons

  //! Bloc
  sl.registerLazySingleton(() => AuthenticationBloc(secureStorage: sl()));
  sl.registerFactory(() => LoginBloc(authenticationBloc: sl(), validationHelper: sl(), authService: sl()));
  sl.registerFactory(() => SignupBloc(validationHelper: sl(), authService: sl()));


【讨论】:

【参考方案2】:

我在我的项目中遇到了同样的问题。我正在使用相同的堆栈,并且当我从另一个块添加事件时,UI 看不到任何更改。我使用的临时解决方案 - 我使用 GlobalKey&lt;ScaffoldState&gt; 将上下文传递给该集团。所以 UI 和 Bloc 使用相同的上下文。您可以在您的LoginBloc 中使用它:

BlocProvider.of<UserAuthenticationBloc>(yourScaffoldKey.currentContext).add(LoggedIn event)

【讨论】:

以上是关于Flutter Bloc 状态更改未使用 get_it 更新 UI的主要内容,如果未能解决你的问题,请参考以下文章

flutter_bloc 的 BlocBuilder 能否避免重建 Widget 的未更改部分?

Flutter Bloc - Flutter Bloc 状态未更新

Flutter - Bloc 仅在状态未扩展 Equatable 时发出状态

使用 copywith 时 Flutter Bloc 状态未更新

如何使用 bloc flutter 更改单个列表项的状态?

BLoC 产生新状态后 BlocBuilder() 未更新