Flutter:子块正在初始化,但数据尚未存储在 shared_preferences 中
Posted
技术标签:
【中文标题】Flutter:子块正在初始化,但数据尚未存储在 shared_preferences 中【英文标题】:Flutter: Child bloc is initializing, but data is not stored yet in shared_preferences 【发布时间】:2020-03-25 11:02:55 【问题描述】:我为我的应用程序使用 jwt 身份验证。我有带有 bloc 模式的主页,它选择页面将其加载到屏幕上(LoginScreen 或 AppScreen)。 LoginScreen 有自己的 bloc 模式,也是 AppScreen。 当我输入登录名和密码并单击登录时,会调度 LoginEvent,它将请求发送到 Web 服务并使用 jwt 令牌获取响应,该令牌将存储在共享首选项中,但同时 AppScreen 用他的 bloc 呈现,在这个AppScreen 的时间块正在发送数据请求(需要 jwt 令牌)但为空,因为尚未存储 jwt 令牌。我发现一种解决方法不是优雅的方式,我也可以使用延迟,但这不是正确的方式......
解决 AppScreen:
@override
void initState()
super.initState();
_bloc = BottomNavigationBloc(workoutTypeRepository: WorkoutTypeRepository());
_bloc.add(AppScreenLunched());
_bloc.add(AppScreenLunched());
如果我离开 _bloc.add(AppScreenLunched());
数据将无法加载到主页,因为它从 Web 服务接收到 null
如果我离开
_bloc.add(AppScreenLunched());
_bloc.add(AppScreenLunched());
第一个 add
数据为空
第二次add
我终于得到了与块一起提供给主页的数据
有什么办法吗?
开始屏幕:
class MyApp extends StatefulWidget
@override
_MyAppState createState() => _MyAppState();
class _MyAppState extends State<MyApp>
AuthBloc _authBloc;
LoginRepository _loginRepository;
@override
void initState()
super.initState();
_loginRepository = LoginRepository();
_authBloc = AuthBloc(loginRepository: _loginRepository);
_authBloc.add(AppStarted());
@override
void dispose()
super.dispose();
_authBloc.close();
@override
Widget build(BuildContext context)
return BlocProvider<AuthBloc>(
create: (_) => _authBloc,
child: MaterialApp(
title: 'TEST',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BlocBuilder(
bloc: _authBloc,
builder: (BuildContext context, AuthState state)
if (state is AuthUninitialized)
return LoginPage(loginRepository: _loginRepository);
else if (state is AuthAuthenticated)
return AppScreen();
else if (state is AuthUnauthenticated)
return LoginPage(loginRepository: _loginRepository);
else if (state is AuthLoading)
return LoadingIndicator();
return LoginPage(loginRepository: _loginRepository);
)
)
);
登录屏幕(部分):
class LoginForm extends StatefulWidget
@override
_LoginFormState createState() => _LoginFormState();
class _LoginFormState extends State<LoginForm>
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final registerRepository = RegisterRepository();
LoginBloc _loginBloc;
@override
void initState()
super.initState();
_loginBloc = BlocProvider.of<LoginBloc>(context);
@override
void dispose()
super.dispose();
_loginBloc.close();
@override
Widget build(BuildContext context)
_onLoginButtonPressed()
_loginBloc.add(LoginButtonPressed(
email: _emailController.text,
password: _passwordController.text,
));
应用屏幕:
class AppScreen extends StatefulWidget
@override
_AppScreenState createState() => _AppScreenState();
class _AppScreenState extends State<AppScreen>
BottomNavigationBloc _bloc;
@override
void initState()
super.initState();
_bloc = BottomNavigationBloc(workoutTypeRepository: WorkoutTypeRepository());
_bloc.add(AppScreenLunched());
_bloc.add(AppScreenLunched());
@override
void dispose()
super.dispose();
_bloc.close();
@override
Widget build(BuildContext context)
return BlocProvider<BottomNavigationBloc>(
create: (_) => _bloc,
child: BlocBuilder(
bloc: _bloc,
builder: (BuildContext context, BottomNavigationState state)
return Scaffold(
appBar: AppBar(
title: Text('TEST'),
),
body: _blocBuilder(context, state),
bottomNavigationBar: BottomActionBar(),
);
,
),
);
Widget _blocBuilder(BuildContext context, BottomNavigationState state)
if (state is PageLoading)
return Center(child: CircularProgressIndicator());
else if (state is HomePageLoaded)
return HomePage(workoutTypes: state.workoutTypes);
else if (state is SearchPageLoaded)
return Center(child: Text("SearchPage"));
// return SearchPage();
else if (state is WorkoutPageLoaded)
// return Center(child: Text("WorkoutPage"));
return WorkoutPage();
else if (state is FavoritePageLoaded)
return Center(child: Text("FavoritePage"));
// return SearchPage();
else if (state is ProfilePageLoaded)
return Center(child: Text("ProfilePage"));
// return ProfilePage();
return Container();
AuthBloc:
class AuthBloc extends Bloc<AuthEvent, AuthState>
final LoginRepository loginRepository;
AuthBloc(@required this.loginRepository) : assert(loginRepository != null);
@override
AuthState get initialState => AuthUninitialized();
@override
Stream<AuthState> mapEventToState(
AuthEvent event,
) async*
if (event is AppStarted)
final bool hasToken = await loginRepository.hasToken();
if (hasToken)
yield AuthAuthenticated();
else
yield AuthUnauthenticated();
if (event is LoggedIn)
final bool hasToken = await loginRepository.hasToken();
if (hasToken)
yield AuthAuthenticated();
else
yield AuthUnauthenticated();
if (event is LoggedOut)
yield AuthLoading();
await loginRepository.deleteToken();
yield AuthUnauthenticated();
登录块:
class LoginBloc extends Bloc<LoginEvent, LoginState>
final LoginRepository loginRepository;
final AuthBloc authBloc;
LoginBloc(
@required this.loginRepository,
@required this.authBloc,
) : assert(loginRepository != null),
assert(authBloc != null);
LoginState get initialState => LoginInitial();
@override
Stream<LoginState> mapEventToState(LoginEvent event) async*
if (event is LoginButtonPressed)
yield LoginLoading();
try
await loginRepository.authenticate(
email: event.email,
password: event.password,
);
authBloc.add(LoggedIn());
yield LoginInitial();
catch (error)
yield LoginFailure(error: error.toString());
底部导航栏块:
class BottomNavigationBloc extends Bloc<BottomNavigationEvent, BottomNavigationState>
int currentIndex = 0;
final WorkoutTypeRepository workoutTypeRepository;
BottomNavigationBloc(this.workoutTypeRepository) : assert(workoutTypeRepository != null);
@override
BottomNavigationState get initialState => PageLoading();
@override
Stream<BottomNavigationState> mapEventToState(BottomNavigationEvent event) async*
if (event is AppScreenLunched)
this.add(PageTapped(index: this.currentIndex));
if (event is PageTapped)
this.currentIndex = event.index;
yield CurrentIndexChanged(currentIndex: this.currentIndex);
yield PageLoading();
if (this.currentIndex == 0)
final workoutTypes = await getWorkoutType();
yield HomePageLoaded(workoutTypes: workoutTypes);
if (this.currentIndex == 1)
yield SearchPageLoaded();
if (this.currentIndex == 2)
yield WorkoutPageLoaded();
if (this.currentIndex == 3)
yield FavoritePageLoaded();
if (this.currentIndex == 4)
yield ProfilePageLoaded();
Future<List<WorkoutType>> getWorkoutType() async
List<WorkoutType> workoutType = await workoutTypeRepository.getWorkoutType();
return workoutType;
【问题讨论】:
为什么在 bloc 上使用add
方法而不是 dispatch
?
请说明你在哪里初始化你的 LoginBloc 以及 LoginRepository 里面有什么。我认为这个问题可能是您的 LoginBloc 和 AuthBloc 具有不同的 LoginRepository 实例。
dispatch 已弃用,newst bloc 有 add 方法
最新* 我在下面添加了详细信息;)我初始化身份验证块的主页和初始化登录块的登录页面
【参考方案1】:
主页:
class MyApp extends StatefulWidget
@override
_MyAppState createState() => _MyAppState();
class _MyAppState extends State<MyApp>
AuthBloc _authBloc;
LoginRepository _loginRepository;
@override
void initState()
super.initState();
_loginRepository = LoginRepository();
_authBloc = AuthBloc(loginRepository: _loginRepository);
_authBloc.add(AppStarted());
@override
void dispose()
super.dispose();
_authBloc.close();
@override
Widget build(BuildContext context)
return BlocProvider<AuthBloc>(
create: (context) => _authBloc,
child: MaterialApp(
title: 'TEST',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BlocBuilder(
bloc: _authBloc,
builder: (BuildContext context, AuthState state)
if (state is AuthUninitialized)
return LoadingIndicator();
// return LoginPage(loginRepository: _loginRepository);
else if (state is AuthAuthenticated)
return AppScreen();
else if (state is AuthUnauthenticated)
return LoginPage(loginRepository: _loginRepository);
else if (state is AuthLoading)
return LoadingIndicator();
return LoginPage(loginRepository: _loginRepository);
)
)
);
登录页面:
class LoginPage extends StatelessWidget
final LoginRepository loginRepository;
LoginPage(
Key key,
@required this.loginRepository
) : assert(loginRepository != null), super(key: key);
@override
Widget build(BuildContext context)
return Scaffold(
body: BlocProvider(
create: (context)
return LoginBloc(
loginRepository: loginRepository,
authBloc: BlocProvider.of<AuthBloc>(context)
);
,
child: Stack(
children: <Widget>[
new Container(
decoration: BoxDecoration(
image: backgroundImage
)
),
new Center(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
LoginForm()
]
),
),
)
],
),
)
);
AuthBloc:
class AuthBloc extends Bloc<AuthEvent, AuthState>
final LoginRepository loginRepository;
AuthBloc(@required this.loginRepository) : assert(loginRepository != null);
@override
AuthState get initialState => AuthUninitialized();
@override
Stream<AuthState> mapEventToState(
AuthEvent event,
) async*
if (event is AppStarted)
final bool hasToken = await loginRepository.hasToken();
if (hasToken)
yield AuthAuthenticated();
else
yield AuthUnauthenticated();
if (event is LoggedIn)
final bool hasToken = await loginRepository.hasToken();
if (hasToken)
yield AuthAuthenticated();
else
yield AuthUnauthenticated();
if (event is LoggedOut)
yield AuthLoading();
await loginRepository.deleteToken();
yield AuthUnauthenticated();
登录块:
class LoginBloc extends Bloc<LoginEvent, LoginState>
final LoginRepository loginRepository;
final AuthBloc authBloc;
LoginBloc(
@required this.loginRepository,
@required this.authBloc,
) : assert(loginRepository != null),
assert(authBloc != null);
LoginState get initialState => LoginInitial();
@override
Stream<LoginState> mapEventToState(LoginEvent event) async*
if (event is LoginButtonPressed)
yield LoginLoading();
try
await loginRepository.authenticate(
email: event.email,
password: event.password,
);
authBloc.add(LoggedIn());
yield LoginInitial();
catch (error)
yield LoginFailure(error: error.toString());
登录存储库:
class LoginRepository
LoginProvider _loginProvider;
LoginRepository(LoginProvider loginProvider)
_loginProvider = loginProvider ?? LoginProvider();
Future<String> authenticate(String email, String password) async
if (email == null || password == null)
return "Nie podanu loginu lub hasła";
var loginData =
await _loginProvider.login(email: email, password: password);
var token = AuthToken.fromJson(loginData);
return token.accessToken;
Future<bool> hasToken() async
String token = await LocalStorage.get(Constant.ACCESS_TOKEN);
if (token != null)
return true;
return false;
Future<void> persistToken(String token) async
if (token != null)
await LocalStorage.save(Constant.ACCESS_TOKEN, "$Constant.TOKEN_TYPE_BEARER $token");
Future<void> deleteToken() async
await LocalStorage.remove(Constant.ACCESS_TOKEN);
本地存储:
class LocalStorage
static save(String key, String value) async
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString(key, value);
static Future<String> get(String key) async
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.get(key);
static remove(String key) async
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.remove(key);
static removeAll() async
SharedPreferences prefs = await SharedPreferences.getInstance();
var keys = prefs.getKeys();
keys.map((key)
remove(key);
);
【讨论】:
我也遇到了同样的问题,你找到方法了吗? 我认为使用安全存储方法的存储库初始化较晚。在当前项目中,我在主文件中创建 LoginRepository 的实例,并将带有 RepositoryProvider 的 repo 传递给 Bloc/Cubit。也许也可以从 SharedPreferences 更改为 FlutterSecureStorage。 如果你还有问题在这里发布一些代码我会尽力帮助你 谢谢,我也找到了使用 FlutterSecureStorage 的方法以上是关于Flutter:子块正在初始化,但数据尚未存储在 shared_preferences 中的主要内容,如果未能解决你的问题,请参考以下文章
LateInitializationError:字段“chatRoomsStream”尚未在 Flutter 中初始化
LateInitializationError:字段尚未在 Flutter 中初始化
Flutter LocalStorage:LateInitializationError:字段'localStorage'尚未初始化