Flutter Cubit 到 Cubit 的通信不起作用。

huangapple go评论89阅读模式
英文:

flutter cubit to cubit communication not working

问题

NOTE: POST UPDATED!

我有两个cubit,其中一个是`LoginCubit`,负责处理登录操作,有3个不同的状态(`LoginLoading`,`LoginSuccess`,`LoginFailure`)。另一个cubit是`AuthCubit`,有4个状态(`AuthInitial`, `AuthLoading`, `Authenticated`, `Unauthenticated`),负责身份验证事件。

如果用户已登录(即状态为`LoginSuccess`),则`AuthState`必须转换为`Authenticated`。在这一点上,我监听来自`AuthCubit`的`LoginState`,以检测是否发生任何状态更改。以下是`AuthCubit`监听`LoginState`的代码。

**AuthCubit**

final IsSignInUseCase isSignInUseCase;
final SignOutUseCase signOutUseCase;
final ReadAuthFromStorageUseCase readAuthFromStorageUseCase;
final LoginCubit loginCubit;
late final StreamSubscription streamSubscription;

AuthCubit(
{required this.signOutUseCase,
required this.loginCubit,
required this.readAuthFromStorageUseCase,
required this.isSignInUseCase})
: super(AuthInitial()) {
streamSubscription = loginCubit.stream.listen((state) {

//在这里,我监听Login状态,如果登录状态成功,则进行身份验证

  if (state is LoginSuccess) {
    emit(Authenticated(authResponse: state.authResponse));
  }
});

}
Future checkAuthentication() async {
emit(AuthLoading());
bool isSignIn = await isSignInUseCase.call();

if (isSignIn) {
  final AuthResponse authResponse = await readAuthFromStorageUseCase.call();
  emit(Authenticated(authResponse: authResponse));
} else {
  emit(Unauthenticated());
}

}

//关闭流
@override
Future close() {
streamSubscription.cancel();
return super.close();
}


由于我正在监听`LoginState`,现在我可以区分已登录用户和未登录用户。根据这一点,我构建以下屏幕。

**main.dart**

BlocBuilder<AuthCubit, AuthState>(
builder: (context, state) {
if (state is Authenticated) {
return const HomeScreen();
} else if (state is Unauthenticated) {
return BlocListener<LoginCubit, LoginState>(
listener: (context, state) {
if (state is LoginFailure) {
showDialog(
context: context,
builder: (context) => CustomAlertDialog(
title: ExceptionConst.exceptionTitle,
content: state.appException.message,
onPressed: () => Navigator.pop(context)));
}
},
child: const LoginScreen(),
);
} else {
return const SplashScreen();
}
},
));


我使用`get_it`进行依赖注入。

**dependency_injection**

sl.registerFactory(() => LoginCubit(loginUserUseCase: sl.call()));
sl.registerFactory(() => AuthCubit(
isSignInUseCase: sl.call(),
loginCubit: sl.call(),
readAuthFromStorageUseCase: sl.call(),signOutUseCase: sl.call()));

sl.registerLazySingleton(() => LoginUserUseCase(authRepo: sl.call()));
sl.registerLazySingleton(() => SignUpUserUseCase(authRepo: sl.call()));
sl.registerLazySingleton(() => IsSignInUseCase(authRepo: sl.call()));
sl.registerLazySingleton(() => SignOutUseCase(authRepo: sl.call()));
sl.registerLazySingleton(
() => ReadAuthFromStorageUseCase(authRepo: sl.call()));
...


最后,这是我的`MultiBlocProvider`。

void main() async {
await di.init();
runApp(MultiBlocProvider(providers: [
BlocProvider(create: () => di.sl()),
BlocProvider(create: (
) => di.sl()..checkAuthentication()),
], child: const MyApp()));
}


但是,当我按下登录按钮时,除非我手动使用热重启重新加载应用程序,否则`AuthState`不会更改。
英文:

NOTE: POST UPDATED!

I have two cubits one of them is LoginCubit which is responsible for login actions and has 3 different states( LoginLoading, LoginSuccess, LoginFailure). The other cubit is AuthCubit which has 4 states(AuthInitial, AuthLoading, Authenticated, Unauthenticated) and it is responsible for authentication events.

If the user is logged in (that means if the state is LoginSuccess), then the AuthState must be converted to Authenticated). At this point, I listen to LoginState from my AuthCubit, if any state changes occur. This is the code where AuthCubit listens LoginState.

AuthCubit

  final IsSignInUseCase isSignInUseCase;
  final SignOutUseCase signOutUseCase;
  final ReadAuthFromStorageUseCase readAuthFromStorageUseCase;
  final LoginCubit loginCubit;
  late final StreamSubscription streamSubscription;

  AuthCubit(
      {required this.signOutUseCase,
      required this.loginCubit,
      required this.readAuthFromStorageUseCase,
      required this.isSignInUseCase})
      : super(AuthInitial()) {
    streamSubscription = loginCubit.stream.listen((state) {

//here I listen to Login changes, if the login state is success, then authenticate

      if (state is LoginSuccess) {
        emit(Authenticated(authResponse: state.authResponse));
      }
    });
  }
  Future&lt;void&gt; checkAuthentication() async {
    emit(AuthLoading());
    bool isSignIn = await isSignInUseCase.call();

    if (isSignIn) {
      final AuthResponse authResponse = await readAuthFromStorageUseCase.call();
      emit(Authenticated(authResponse: authResponse));
    } else {
      emit(Unauthenticated());
    }
  }


//close stream sub
  @override
  Future&lt;void&gt; close() {
    streamSubscription.cancel();
    return super.close();
  }

Since I am listening to LoginState, now I can distinguish between the logged-in users and non-logged-in users. According to this, I build my screen as the following.

main.dart

BlocBuilder&lt;AuthCubit, AuthState&gt;(
          builder: (context, state) {
            if (state is Authenticated) {
              return const HomeScreen();
            } else if (state is Unauthenticated) {
              return BlocListener&lt;LoginCubit, LoginState&gt;(
                listener: (context, state) {
                  if (state is LoginFailure) {
                    showDialog(
                        context: context,
                        builder: (context) =&gt; CustomAlertDialog(
                            title: ExceptionConst.exceptionTitle,
                            content: state.appException.message,
                            onPressed: () =&gt; Navigator.pop(context)));
                  }
                },
                child: const LoginScreen(),
              );
            } else {
              return const SplashScreen();
            }
          },
        ));

I use get_it for di.

dependency_injection

 sl.registerFactory(() =&gt; LoginCubit(loginUserUseCase: sl.call()));
 sl.registerFactory(() =&gt; AuthCubit(
      isSignInUseCase: sl.call(),
      loginCubit: sl.call(),
      readAuthFromStorageUseCase: sl.call(),signOutUseCase: sl.call()));

  sl.registerLazySingleton(() =&gt; LoginUserUseCase(authRepo: sl.call()));
  sl.registerLazySingleton(() =&gt; SignUpUserUseCase(authRepo: sl.call()));
  sl.registerLazySingleton(() =&gt; IsSignInUseCase(authRepo: sl.call()));
  sl.registerLazySingleton(() =&gt; SignOutUseCase(authRepo: sl.call()));
  sl.registerLazySingleton(
      () =&gt; ReadAuthFromStorageUseCase(authRepo: sl.call()));
...

and lastly this is my MultiBlocProvider

void main() async {
  await di.init();
  runApp(MultiBlocProvider(providers: [
    BlocProvider(create: (_) =&gt; di.sl&lt;LoginCubit&gt;()),
    BlocProvider(create: (_) =&gt; di.sl&lt;AuthCubit&gt;()..checkAuthentication()),
  ], child: const MyApp()));
}

However, when I press the login button the AuthState is not changing unless I reload the app manually using hot restart.

答案1

得分: 1

I solved this problem by changing my listening technique from StreamSubscription to BlocListener. Still, I don't know why the StreamSubscription has not worked. Until someone can solve it I will accept my answer.

Here is the solution, I cleaned all StreamSubscription codes.

AuthCubit

class AuthCubit extends Cubit<AuthState> {
  final IsSignInUseCase isSignInUseCase;
  final SignOutUseCase signOutUseCase;
  final ReadAuthFromStorageUseCase readAuthFromStorageUseCase;

  AuthCubit(
      {required this.signOutUseCase,
      required this.readAuthFromStorageUseCase,
      required this.isSignInUseCase})
      : super(AuthInitial());
  Future<void> checkAuthentication() async {
    emit(AuthLoading());
   try{
     bool isSignIn = await isSignInUseCase.call();
     if (isSignIn) {
       final AuthResponse authResponse = await readAuthFromStorageUseCase.call();
       emit(Authenticated(authResponse: authResponse));
     } else {
       emit(Unauthenticated());
     }
   }catch(_){
     emit(Unauthenticated());
   }
  }

  Future<void> signOut() async {
    try {
      emit(AuthLoading());
      await signOutUseCase.call();
      emit(Unauthenticated());
    } catch (e) {
      emit(Unauthenticated());
    }
  }
}

main.dart

BlocBuilder<AuthCubit, AuthState>(
  builder: (context, authState) {
    if (authState is Authenticated) {
      return const HomeScreen();
    } else if (authState is Unauthenticated) {
      return BlocListener<LoginCubit, LoginState>(
        listener: (context, loginState) {
          //the following if check make it work. If the LoginSuccess then recheck authentication status.
          if (loginState is LoginSuccess) {
            BlocProvider.of<AuthCubit>(context).checkAuthentication();
          }
          if (loginState is LoginFailure) {
            showDialog(
                context: context,
                builder: (context) => CustomAlertDialog(
                    title: ExceptionConst.exceptionTitle,
                    content: loginState.appException.message,
                    onPressed: () => Navigator.pop(context)));
          }
        },
        child: const LoginScreen(),
      );
    } else if (authState is AuthLoading) {
      return const Center(
        child: ButtonSpinner(),
      );
    }
    return const SplashScreen();
  },
)

Finally, in my LoginCubit, I emit AuthState.

class LoginCubit extends Cubit<LoginState> {
  final LoginUserUseCase loginUserUseCase;
  final AuthCubit authCubit;

  LoginCubit({required this.authCubit, required this.loginUserUseCase})
      : super(LoginInitial());

  Future<void> loginUser(LoginRequest loginDto) async {
    try {
      emit(LoginLoading());
      final authResponse = await loginUserUseCase.call(loginDto);
      emit(LoginSuccess(authResponse: authResponse));
      //if the login process is succeed, then change emit the AuthState as Authenticated.
      authCubit.emit(Authenticated(authResponse: authResponse));
    } on AppException catch (appEx) {
      emit(LoginFailure(appException: appEx));
    }
  }
}
英文:

I solved this problem by changing my listening technique from StreamSubscription to BlocListener. Still, I don't know why the StreamSubscription has not worked. Until someone can solve it I will accept my answer.

Here is the solution, I cleaned all `StreamSubscription' codes.

AuthCubit


class AuthCubit extends Cubit&lt;AuthState&gt; {
  final IsSignInUseCase isSignInUseCase;
  final SignOutUseCase signOutUseCase;
  final ReadAuthFromStorageUseCase readAuthFromStorageUseCase;

  AuthCubit(
      {required this.signOutUseCase,
      required this.readAuthFromStorageUseCase,
      required this.isSignInUseCase})
      : super(AuthInitial());
  Future&lt;void&gt; checkAuthentication() async {
    emit(AuthLoading());
   try{
     bool isSignIn = await isSignInUseCase.call();
     if (isSignIn) {
       final AuthResponse authResponse = await readAuthFromStorageUseCase.call();
       emit(Authenticated(authResponse: authResponse));
     } else {
       emit(Unauthenticated());
     }
   }catch(_){
     emit(Unauthenticated());
   }
  }

  Future&lt;void&gt; signOut() async {
    try {
      emit(AuthLoading());
      await signOutUseCase.call();
      emit(Unauthenticated());
    } catch (e) {
      emit(Unauthenticated());
    }
  }
}

main.dart

BlocBuilder&lt;AuthCubit, AuthState&gt;(
            builder: (context, authState) {
              if (authState is Authenticated) {
                return const HomeScreen();
              } else if (authState is Unauthenticated) {
                return BlocListener&lt;LoginCubit, LoginState&gt;(
                  
                  listener: (context, loginState) {
//the following if check make it work. If the LoginSuccess then recheck authentication status.
                    if (loginState is LoginSuccess) {
                      BlocProvider.of&lt;AuthCubit&gt;(context).checkAuthentication();
                    }
                    if (loginState is LoginFailure) {
                      showDialog(
                          context: context,
                          builder: (context) =&gt; CustomAlertDialog(
                              title: ExceptionConst.exceptionTitle,
                              content: loginState.appException.message,
                              onPressed: () =&gt; Navigator.pop(context)));
                    }
                  },
                  child: const LoginScreen(),
                );
              } else if (authState is AuthLoading) {
                return const Center(
                  child: ButtonSpinner(),
                );
              }
              return const SplashScreen();
            },
          ),

finally in my LoginCubit I emit AuthState.

class LoginCubit extends Cubit&lt;LoginState&gt; {
  final LoginUserUseCase loginUserUseCase;
  final AuthCubit authCubit;

  LoginCubit({required this.authCubit, required this.loginUserUseCase})
      : super(LoginInitial());

  Future&lt;void&gt; loginUser(LoginRequest loginDto) async {
    try {
      emit(LoginLoading());
      final authResponse = await loginUserUseCase.call(loginDto);
      emit(LoginSuccess(authResponse: authResponse));
//if the login process is succeed, then change emit the AuthState as Authenticated.
      authCubit.emit(Authenticated(authResponse: authResponse));
    } on AppException catch (appEx) {
      emit(LoginFailure(appException: appEx));
    }
  }
}

huangapple
  • 本文由 发表于 2023年3月7日 18:18:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/75660666.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定