I've been building a login/authentication feature using a combination of this login tutorial and the resocoder clean architecture tutorials. It's 99% working perfectly, but it is not responding properly to the LoginButton
being pressed.
For some reason when LoginBloc
calls AuthenticationBloc.add(loggedin())
, the AuthenticationBloc yields the AuthenticationAuthenticated()
state just fine, but the BlocBuilder
in Main.dart doesn't receive the state change. Even the OnTransition
inside SimpleBlocDelegate is triggered when AuthenticationAuthenticated
is yielded, but BlocBuilder
does nothing.
Main.dart
looks like this:
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();
)
},
),
);
}
}
I can only think it has something to do with get_it
. The Dependency Injection looks like this:
final sl = GetIt.instance;
Future<void> init() async {
sl.registerFactory(
() => UserAuthenticationBloc(
getCachedUser: sl(),
),
);
sl.registerFactory(
() => LoginBloc(authenticationBloc: sl(), getUserFromEmailAndPassword: sl()),
);
...
}
and then in the widget tree for the loginscreen
the LoginBloc
gets created, so it is available to the login form.
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
),
);
}
}
TWO EDITS:
1. I changed UserAuthenticationBloc
in the dependency-injection file from a factory to a lazysingleton... now it works. However, I heard that using singletons for classes with Streams can cause memory leaks?? I guess it means that LoginBloc
is not talking to the same instance of AuthBloc
that Main.dart is listening to? I've no idea how to ensure that without the singleton...
- Code of 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.
}
}
}