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'尚未初始化

Flutter 应用中提供程序与 sqflite 的集成

Flutter 初始化 gradle 和 Resolving dependencies 需要很长时间

上传前 Flutter 离线图片