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<void> 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<void> 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<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();
}
},
));
I use get_it
for di.
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()));
...
and lastly this is my MultiBlocProvider
void main() async {
await di.init();
runApp(MultiBlocProvider(providers: [
BlocProvider(create: (_) => di.sl<LoginCubit>()),
BlocProvider(create: (_) => di.sl<AuthCubit>()..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.
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));
}
}
}