I am trying to make an app using flutter blocs, but I am having troubles with the BlocListener not being called and I can't figure out what I'm doing wrong.
Here is a minimalish code reproducing my issue:
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(const App());
}
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => AuthBloc(),
child: const AppView(),
);
}
}
/**************** APP VIEW **************/
class AppView extends StatefulWidget {
const AppView({Key? key}) : super(key: key);
@override
State<AppView> createState() => _AppViewState();
}
class _AppViewState extends State<AppView> {
final _navigatorKey = GlobalKey<NavigatorState>();
NavigatorState get _navigator => _navigatorKey.currentState!;
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: _navigatorKey,
builder: (context, child) {
print('App builder');
return BlocListener<AuthBloc, AuthState>(
listener: (context, state) {
print('Bloc listener');
switch (state.status) {
case AuthStatus.authenticated:
_navigator.pushAndRemoveUntil<void>(
MaterialPageRoute(
builder: (context) {
return Center(
child: Column(
children: [
const Text('Home'),
ElevatedButton(
onPressed: () {
context.read<AuthBloc>().add(
const AuthStatusChanged(
AuthStatus.unauthenticated));
},
child: const Text('Log out'),
),
],
),
);
},
),
(route) => false,
);
break;
default:
_navigator.pushAndRemoveUntil<void>(
MaterialPageRoute(
builder: (context) {
return Center(
child: Column(
children: [
const Text('Login'),
ElevatedButton(
onPressed: () {
context.read<AuthBloc>().add(
const AuthStatusChanged(
AuthStatus.authenticated));
},
child: const Text('Log in'),
),
],
),
);
},
),
(route) => false,
);
break;
}
},
child: child,
);
},
onGenerateRoute: (_) => MaterialPageRoute(
builder: (context) {
return const Center(
child: Text('splash'),
);
},
),
);
}
}
/**************** AUTH BLOC CLASSES **************/
/**************** AUTH State **************/
enum AuthStatus { unknown, unauthenticated, authenticated }
class AuthState extends Equatable {
final AuthStatus status;
const AuthState._({
this.status = AuthStatus.unknown,
});
const AuthState.unknown() : this._();
const AuthState.authenticated() : this._(status: AuthStatus.authenticated);
const AuthState.unauthenticated()
: this._(status: AuthStatus.unauthenticated);
@override
List<Object?> get props => [status];
}
/**************** AUTH Event **************/
abstract class AuthEvent extends Equatable {
const AuthEvent();
@override
List<Object> get props => [];
}
class AuthStatusChanged extends AuthEvent {
final AuthStatus status;
const AuthStatusChanged(this.status);
@override
List<Object> get props => [status];
}
/**************** AUTH BLOC **************/
class AuthBloc extends Bloc<AuthEvent, AuthState> {
AuthBloc() : super(const AuthState.unknown()) {
print('Bloc constructor');
on<AuthStatusChanged>(_onAuthStatusChanged);
}
_onAuthStatusChanged(
AuthStatusChanged event,
Emitter<AuthState> emit,
) async {
switch (event.status) {
case AuthStatus.unauthenticated:
return emit(const AuthState.unauthenticated());
case AuthStatus.authenticated:
return emit(const AuthState.authenticated());
default:
return emit(const AuthState.unknown());
}
}
}
When I launch the app I would expect the BlocListener to be called once but instead it sits on the splash page.
I used this tutorial to produce this code : https://bloclibrary.dev/#/flutterlogintutorial
Edit: Thank you all for your insight, I didn't understand that the BlocListener won't fire an event on the initialState (RTFM I guess xD). Looking back at the tutorial I used, this is dealt with by the "Repository" that feeds a stream delayed on creation and the Bloc is listening for that stream to fire a change of state events. Reusing the same concept works for me!
CodePudding user response:
BlocListener only trigger when state has changed. On application load you may want to trigger a bloc event to change the AuthBloc state. This could be achieved by adding a bloc event in the initState() function and placing a breakpoint to see if the listener is being triggered.
https://pub.dev/documentation/flutter_bloc/latest/flutter_bloc/BlocListener-class.html
CodePudding user response:
The listener is only called after a state change, it's not called after AuthBloc
is created.
One more tip: context.read<AuthBloc>().add(const AuthStatusChanged(AuthStatus.authenticated));
DON'T do that. Prefer to create methods inside your bloc instance that will fire events, it makes your code easier to test, if add very_good_analysis
to your project, which is a linter built by VGV (the same company behind flutter bloc), you will see there's a lint rule against that.