State Management Using BLoC Pattern In Flutter
I have always been excited about learning Reactive Programming and using it in my app development. Recently, I have been exploring Flutter, Google’s mobile framework to build cross-platform apps in iOS and Android. Specifically, I wanted to explore the different ways of state management in Flutter. Apart from Stateful Widgets , state management can also be achieved using Business Logic Component (BLoC) . In case you have some experience with Redux, BLoC is very similar to Redux and the recommended approach by Google’s Flutter team for state management in large complex apps that require data to be shared across screens. Think of the Amazon shopping app where you do not want to lose the items in your shopping cart, no matter on which screen you are on.
Reminder: Using BLoC in your Flutter app requires some knowledge of streams.
In this article, I will be using RxDart , a ReactiveX functional programming library for Google Dart to build a login form and perform validations on form fields in Flutter. I will also explain how to achieve navigation between screens. RxDart is a wrapper for Dart’s built-in Streams API and makes app development easier as your app size grows, giving us great utilities which we can use while working with streams. Also, I will discuss about state management in Flutter using Inherited Widgets and how BLoC can be used to access data across multiple screens .
What is BLoC?
Business Logic Component (BLoC)can be thought of as a place where you store the global application state . For our app, we will consider BLoC as the single source of truth for our application state. You send the data coming from your UI to the BLoC and use streams to access data from the BLoC in any widget. Sending the data to the BLoC is achieved using RxDart’s Subject and retrieving data from the BLoC is achieved using RxDart’s Observable .
To make it simpler:
- Data entered by the user from the View Layer is added to the stream using Subject’s sink . This is the input to the BLoC.
- Widgets in the View Layer retrieve data using streams . This is the output from the BLoC.
User Interface Setup
In Flutter, everything is a Widget . Hence, a good place to start would be to first have our widgets render on the screen before adding any functionality.
Note: I will discuss more about Provider later in this article when I will discuss about implementing BLoC .
This is how our initial app UI looks like:
Now that we have our user interface setup, let’s add some functionality to our app by diving into Bloc implementation to perform validation.
Note: I will discuss more about emailField , passwordField and loginButton functions below when we perform validation.
Next, we need to need to create a getter to add email and password to the sink and a getter to retrieve email and password which is send to the StreamTransformer. getter is used for better code readability.
Performing Validation Using StreamTransformer
Now, we to create a StreamTransformer to perform validation of email address and a StreamTransformer to perform validation of password.
We create a mixin class named Validator containing the stream transfomers so that we can reuse these wherever we want.
Tip: Think of mixin classes whenever you feel a particular function will we used in multiple places to achieve better code reusability and maintenance.
Providing Access to Bloc Using Inherited Widgets
Now that we have created the Bloc, we need to ensure that all widgets in our app can have access to this Bloc.
We create a Provider class which extends the InheritedWidget provided by Flutter. Depending on where you access the bloc instance in your app widget tree, InheritedWidgets will provide all widgets underneath access to the bloc.
Then, we create a bloc instance and make it accessible via the of function. Also, we create a named constructor which takes the login screen widget and pushes it to the InheritedWidget super class
We make the MaterialApp widget as a child to the ApplicationStateProvider InheritedWidget so that all widgets in our app can have access to the bloc instance.
Note: I will discuss more about routes later in this article when we set up navigation.
Finally, we access this bloc instance in our LoginScreen widget like so:
Retrieving Data from the Bloc and Re-rendering Text Fields
The TextField widget has an onChanged property where we call the updateEmail and updatePassword getter from the bloc instance to add email and password to the sink.
In order to retrieve email and password from the Bloc, we need use StreamBuilder widgets provided by Flutter which enable the widgets to be re-rendered. StreamBuilder has a property named stream where we use the emailStream getter and passwordStream getter from the bloc instance to retrieve email and password from the bloc.
If the validation was not successful, the error is retrieved from the stream and rendered on the errorText property in the InputDecoration widget. snapshot contains data or error that was retrieved from the bloc.
Finally, we need to add functionality to our login button which should be enabled or disabled only when both email and password fields are valid.
RxDart provides a useful utility to combine streams to achieve this. We add this to our bloc class like so:
As I mentioned before, Observable is RxDart’s wrapper for Stream class. We create a stream of bool which will contain true if both email and password fields are valid.
Like we did for email and password fields above, using StreamBuilder widget, we retrieve data from stream. The button will be enabled only if snapshot has valid data.
After adding validations, this is how our app now looks like:
Adding Navigation And Accessing Data Across Multiple Screens
Now that we have our validations set up, let’s add some functionality to the button by navigating the user to a new screen and displaying the email address that the user entered on the new screen using BLoC.
First, you need to set up Routes for each screen in your app on the MaterialApp widget like so:
Then, in the RaisedButton’s onPressed property, we check if the snapshot has valid data. If it does, we navigate the user to the new screen in a callback like so:
() => Navigator.pushNamed(context, “/secondscreen”)
If the snapshot does not have valid data, the button is disabled.
Let’s build a new screen for displaying the email address entered by the user on the login screen:
Now, using StreamBuilder widget, we retrieve email data from the email stream in the BLoC and display it on the screen:
The complete source code of this app can be found here .
Thats it! Hope you got a feel for how you can leverage the power of reactive programming using RxDart to improve your Flutter app’s performance. Although the example in this article was simple, you can imagine how using Bloc pattern can be efficient in improving state management as your Flutter app grows.
If you enjoyed reading this article, please leave a comment or hit the clap icon below to show your appreciation. Feel free to recommend and share this article. Also, if you want to play around or further enhance this app, you can fork the source code from my GitHub repository . Feel free to leave a star or two. Thanks for your time.
Amrut is a Full Stack Software Engineer who is passionate about tech and software development in Web and Mobile. He likes to write about tech, coding and software development. He also loves to watch and discuss about American Football.