Flutter Bloc 状态更改未更新 UI 为 get_it

Flutter Bloc state change is not updated UI with get_it

我一直在结合使用此登录教程和 resocoder clean architecture 教程来构建 login/authentication 功能。它 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 的同一个实例通话?我不知道如何确保没有单例...

  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.
        }
      }
    }

我在我的项目中遇到了同样的问题。我使用相同的堆栈,而且当我从另一个集团添加事件时,UI 没有看到任何变化。我使用的临时解决方案 - 我使用 GlobalKey<ScaffoldState> 将上下文传递给该集团。那么 UI 和 Bloc 使用相同的上下文。您可以在 LoginBloc:

中使用它
BlocProvider.of<UserAuthenticationBloc>(yourScaffoldKey.currentContext).add(LoggedIn event)

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

中所述

In most cases, BlocProvider should be used to create new blocs which will be made available to the rest of the subtree. In this case, since BlocProvider is responsible for creating the bloc, it will automatically handle closing the bloc.

并使用 BlocProvider.value 传递 bloc 官方建议的值 documentation

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

这就是我一起使用 BlocProvider 和 get_it 的方式。我对 Bloc 以外的所有内容都使用 get_it。 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 时使用相同的方法。我们遇到的最常见的情况是我们需要两个集团进行通信是 AuthenticationBloc 与几乎所有其他集团通信。

为什么registerFactory不行。但是 registerLazySingleton 确实

getit 对 registerFactory

的定义

You have to pass a factory function func that returns an NEW instance of an implementation of T. Each time you call get() you will get a new instance returned

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

的其他一些实例添加了事件

registerLazySingleton 定义为

You have to pass a factory function func that returns an instance of an implementation of T. Only the first time you call get() this factory function will be called to create a new instance.

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

建议的解决方案

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

另一种方法是只在必要时(AuthenticationBloc)使Singleton。并继续对所有其他集团(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()));
}