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
的同一个实例通话?我不知道如何确保没有单例...
- 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()));
}
我一直在结合使用此登录教程和 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
的同一个实例通话?我不知道如何确保没有单例...
- 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()));
}