Improve readability of your BlocListeners
Utilize encapsulation to make your widget tree cleaner
From the bloclibrary
BlocListener is a Flutter widget which takes a
BlocWidgetListener
and an optionalBloc
and invokes thelistener
in response to state changes in the bloc. It should be used for functionality that needs to occur once per state change such as navigation, showing aSnackBar
, showing aDialog
, etc...
listener
*is only called once for each state change (*NOT including the initial state) unlikebuilder
inBlocBuilder
and is avoid
function.If the
bloc
parameter is omitted,BlocListener
will automatically perform a lookup usingBlocProvider
and the currentBuildContext
.
BlocListener and MultiBlocListner are two very useful widgets we almost can't do without when using flutter_bloc
but they come with some verbosity and the flutter widget tree is unforgiving when it comes to that.
Once you start nesting more widgets, the UI code becomes unreadable and messy. Ideally, you should aim for 150 lines of code per file or widget and extract the rest. I know I usually don't stick to it because I'm in the zone or too lazy to do a refactor but surely you don't, right?
Anyways, I'm here to show you how to reduce these lines of code through encapsulation, you know, one of the four OOP principles? Consider this login_page.dart
:
class LoginPage extends StatelessWidget {
const LoginPage({super.key});
@override
Widget build(BuildContext context) {
return MultiBlocListener(
listeners: [
BlocListener<LoginBloc, LoginState>(
listenWhen: (previous, current) {
return previous.email != current.email;
},
listener: (context, state) {
// TODO(Henry): Do something when email changes
},
),
BlocListener<LoginBloc, LoginState>(
listenWhen: (previous, current) {
return previous.password != current.password;
},
listener: (context, state) {
// TODO(Henry): Do something when password changes
},
),
],
child: const LoginPageBody(),
);
}
}
There are two BlocListener
widgets. The first one listens to changes in the email
field of the LoginState
, and the second one listens to changes in the password
field of the LoginState
. The listenWhen
function is used to determine when to call the listener
function. In this case, it's called when the previous state's email
or password
is not equal to the current state's email
or password
.
The entire file is just 27 lines of code but you can see how it can quickly increase if you were to do something when either the email
or password
changes.
We can reduce the LOC to less than half with simple encapsulation like this:
class EmailChangeListener extends BlocListener<LoginBloc, LoginState> {
EmailChangeListener({super.child})
: super(
listenWhen: (previous, current) {
return previous.email != current.email;
},
listener: (context, loginState) {
// TODO(Henry): Do something when email changes
},
);
}
class PasswordChangeListener extends BlocListener<LoginBloc, LoginState> {
PasswordChangeListener({super.child})
: super(
listenWhen: (previous, current) {
return previous.password != current.password;
},
listener: (context, loginState) {
// TODO(Henry): Do something when password changes
},
);
}
By passing the listenWhen
and listener
to the superclass and inheriting all the other properties and methods of the BlocListener
you can extract all relevant code to its class and increase its reusability at the same time. By extending BlocListener, we have our EmailChangeListener
and PasswordChangeListener
with an optional child parameter.
The child
parameter is nullable so that means we can now do this:
class LoginPage extends StatelessWidget {
const LoginPage({super.key});
@override
Widget build(BuildContext context) {
return MultiBlocListener(
listeners: [
EmailChangeListener(),
PasswordChangeListener(),
],
child: const LoginPageBody(),
);
}
}
or this:
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return EmailChangeListener(child: const HomePageBody());
}
}
Although short, that's all I have for today. You can check out other articles of mine or connect with me on linkedin.