Effective BLoC pattern
Hey Folks, Its been so long I have written anything about Flutter. After writing two articles on BLoC pattern I was spending time doing analysis on the usage of this pattern by the community and after answering some questions on the implementation of BLoC pattern I saw that there was a lot of confusion among people. So I came up with a list of “Rules of thumb” that can be followed to properly implement the BLoC pattern which will help a developer to avoid making common mistakes while implementing it. So today I present to you a list of 8 golden points that must be followed when working with BLoC.
Prerequisites
The audience I expect should know what BLoC pattern is or have created an app using the pattern(at least did CTRL + C
and CTRL + V
). If this is the first time you heard the word “BLoC” then the below three articles would be the perfect place to start understanding this pattern:
- Architect your Flutter project using BLoC pattern PART 1 and PART 2
- When Firebase meets BLoC pattern
Story of those who encountered BLoC
I know I know it is a tough pattern to understand and implement. I have seen many posts from developers asking “Which is the best resource to learn BLoC pattern?” After going through all the different posts and comments I feel the following points are the common hurdles every single person went through when understanding this pattern.
- Thinking reactively.
- Struggling to understand how many BLoC files need to be created.
- Scared whether this will scale or not.
- Don’t know when the streams will get disposed.
- What is the full form of BLoC? (It’s Business Logic Component 😅)
- Many more….
But today I will list down some of the most important points that will help you implement BLoC pattern confidently and efficiently. Without any further delay let’s look at those amazing points.
Every screen has its own BLoC
This is the most important point to remember. Whenever you create a new screen e.g Login screen, Registration screen, Profile screen etc which involves dealing with data, you have to create a new BLoC for it. Don’t use a global BLoC for all the screens in your app. You must be thinking that if I have a common BLoC I can easily use the data between the two screens. That’s not good because your repository should be responsible for providing those common data to the BLoC. BLoC will just take that data and provide to your screen in a manner which can be displayed to the user.
Every BLoC must have a dispose()
method
This is pretty straight forward. Every BLoC you create should have a dispose()
method. This is the place where you do the cleanup or close all the streams you have created. A simple example of the dispose()
method is shown below.
Don’t use StatelessWidget with BLoC
Whenever you want to create a screen which will pass data to a BLoC or get data from a BLoC always use StatefulWidget
. The biggest advantage of using StatefulWidget
over StatelessWidget
are the lifecycle methods available in the StatefulWidget
. Later down the article, we will talk about the two most important methods to override when working with BLoC pattern. StatelessWidgets
are good to make a small static part of your screen e.g showing an image or hardcoded text. If you want to see the implementation of BLoC pattern in a StatelessWidget
check out PART1 and in PART2 I showed why I converted from StatelessWidget
to a StatefulWidget
.
Override didChangeDependencies()
to initialise BLoC
This is the most crucial method to override in a StatefulWidget
if you need a context
at the beginning to initialise a BLoC object. You can think of it as the initializing method(preferred for BLoC initialisation only). You may argue that we even have a initState()
so why use didChangeDependencies()
. As per the doc it’s clearly mentioned that it is safe to call BuildContext.inheritFromWidgetOfExactType
from didChangeDependencies()
method. A simple example of how to use this method is shown below:
Override dispose() method to dispose BLoC
Just like there is an initializing method, we have been provided with a method where we can dispose of the connections we created in the BLoC. The dispose()
method is the perfect place to call the BLoC dispose()
method associated with that particular screen. This method is always called when you leaving the screen(technically when the StatefulWidget
is getting disposed). Below is a small example of that method:
Use RxDart only when dealing with complex logic
If you have worked with BLoC pattern earlier then you must have heard about RxDart
library. It is a reactive functional programming library for Google Dart. This library is just a wrapper over the Stream
API provided by Dart. I would advise you to use this library only when you are dealing with complex logic like chaining multiple network requests. But for simple implementations use the Stream
API provided by the Dart language as it is quite mature. Below I have added a BLoC which uses Stream
API rather than the RxDart
library because the operations are quite simple and I didn’t need an additional library to do the same:
Use PublishSubject over BehaviorSubject
This point is more specific for those using the RxDart
library in their Flutter project. BehaviorSubject
is a special StreamController
that captures the latest item that has been added to the controller, and emits that as the first item to any new listener. Even if you call close()
or drain()
on the BehaviorSubject
it will still hold the last item and emit when subscribed. This can be a nightmare for a developer if he/she is not aware of this feature. Whereas PublishSubject
doesn’t store the last item and is best suited for most of the cases. Check out this project to see the BehaviorSubject
feature in action. Run the app and go to the “Add Goal” screen, enter the details in the form and navigate back. Now again if you visit the “Add Goal” screen you will find the form pre-filled with the data you entered previously. If you are a lazy person like me then look at the video I have attached below:
Proper use of BLoC Providers
Before I say anything about this point do check the below code snippet(line 9 and 10).
You can clearly see that multiple BLoC providers are nested. Now you must be worried that if you keep adding more BLoCs in that same chain then it will be a nightmare and you will conclude that BLoC pattern cannot scale. But let me tell you that there can be a special case(a BLoC only holding the UI configurations which is required across the app) when you need to access multiple BLoCs anywhere down the Widget tree so for such cases the above nesting is completely fine. But I would recommend you to avoid such nesting most of the time and provide the BLoC from where it is actually needed. So for example when you are navigating to a new screen you can use the BLoC provider like this:
This way the MovieDetailBlocProvider
will provide the BLoC to the MovieDetail
screen and not to the whole widget tree. You can see that I stored the MovieDetailScreen
in a new final variable
to avoid recreation of the MovieDetailScreen
every time when the keyboard is opened or closed in the MovieDetailScreen
.
This is not the end
So here we come to the end of this article. But this is not the end of this topic. I will keep adding new points to this ever-growing list of optimizing the BLoC pattern as I learn better ways of scaling and implementing the pattern. I hope these points will surely help you implement BLoC pattern in a better way. Keep learning and keep coding. :) If you liked the article then show your love by hitting 50 claps 😄 👏 👏.
Having any doubt, connect with me at LinkedIn or follow me at Twitter. I will try my best to solve all your queries.