How to change the app’s theme at runtime using the BLoC pattern in Flutter.
There are many cases where you might want to support multiple themes in your application, and let the user change them dynamically. Let me explain you how to do it in this tutorial using the “business logic component” pattern, also known as BLoC.
TL;DR; For those just interested in the code, here’s the code covered in this article: https://github.com/jorgecoca/theme_switcher/tree/theme-switcher-tutorial-1
We are going to build a Flutter application that consists of:
- A
MaterialApp
at the root of our app - Two pages: the
HomePage
and theThemeSelectorPage
- A
BLoC
component that will receive a selected theme, and will emit aThemeData
object
Let’s start by creating an app named theme_switcher
, replacing the contents of main.dart
and creating home_page.dart
file that will represent the “initial” screen of our app.
We have just created a simple app with, based on MaterialApp
, with an AppBar
, a Text
centered and a FloatingButton
. Should look like this:
Our next step will be to open the ThemeSelectorPage
from the icon that we added on the AppBar
. To do that, let’s create a theme_selector_page.dart
, and let’s modify our HomePage
to open it when pressing on the IconButton
Now our application looks like this:
As you can see, our buttons still do nothing. So the next step is to add functionality to those buttons. In order to that, we are going to create a new file that we will name themes.dart
. This file will have two missions:
- Listen to the button events, so when we receive an event we can emit a
ThemeData
object containing the specs for the selected theme.
To accomplish this, we will make use of Stream
and Sink
. You can think of Sink
as an asynchronous input to your BLoC, and Stream
as the same thing, but being an output instead.
Using a little bit of RxDart
, our main mission here is to transform the selected theme name input, and transform it into a ThemeData
object. It looks something like this:
selectedTheme.distinct().map((theme) => theme.data);
We use distinct()
here to avoid repainting the same theme again.
In order to use RxDart
, we will need to add new package to our pubspec
and update our dependencies with rxdart: ^0.18.1
.
With this information, our themes.dart
file will look like this:
We have defined a DemoTheme
model that will be our BLoC input, and our ThemeBloc
class has defined a Sink
to process the input, an Stream
output to emit our themes, and our initial theme.
Now we can update our ThemeSelectorPage
to link our button events with the Sink
in the BLoC:
We have added a constructor to mark our ThemeBloc
as a dependency.
Next step here is to modify our main.dart
and home_page.dart
files to react to the new themes emitted:
Let’s break down these changes:
- Our
ThemeSwitcherApp
now returns aStreamBuilder
, that will help us to setup the initial state of the widget, plus also it is responsible for handling the communication with theStream
of theThemeBloc
. - But why do we need to modify the
HomePage
? Well, since we navigate to theThemeSelectorPage
from here, it is just a mere pass through for theThemeBloc
object. If we were creating two instances ofThemeBloc
, we might be emitting events in one object and listening for themes on a different one. There are better ways to do this, as I will explain on following posts, but this works for now.
With all these pieces together, we can test our theme switcher:
Hurray!!! Our application works!!! However, there are still a few details that we can improve:
- In order to use the same instance of
ThemeBloc
, we modifiedHomePage
because it was the nexus between ourThemeSwitcherApp
and ourThemeSelectorPage
- We did not write any tests! And this, “mi amigo”, it is unacceptable!
We will improve these two cases in following articles ;)
I hope you enjoyed this tutorial as much as I did!