App Theming in Flutter with Riverpod — Light Mode/Dark Mode

Fintasys
5 min readOct 8, 2020

--

Light Mode/Dark Mode with Riverpod in Flutter

There have been already a lot of articles about this topic, but recently popular Flutter developer Remi released his new state management library Riverpod. This library is based on the experience he made with Provider and should remove some of its weaknesses along with some syntax improvements. So I was curious to try it out to see in what way it can make our life easier.

Let’s get started!

First we need to add Riverpod to the pubspec.yaml of the project.

dependencies:
flutter:
sdk: flutter
flutter_hooks: ^0.14.0
hooks_riverpod: ^0.11.1

I prefer to use it in combination with flutter_hooks to reduce the boilerplate code.

For our example we use this sample code with a simple switch to toggle between Light Mode and Dark Mode manually.

void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 2.0,
title: Text("Flutter Theme Riverpod Demo"),
),
body: Column(
children: [
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Light Mode"),
DarkThemeSwitch(),
Text("Dark Mode"),
],
),
),
],
),
);
}
}
class DarkModeSwitch extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Switch(
value: false,
onChanged: (enabled) {
// Todo
},
);
}
}

As you can see inside MaterialApp we have declared already different themes for Light Mode and Dark Mode. This would already be enough to support Dark Mode and switch theme when user activates Dark Mode in the device settings.

The different color schemes for the themes can be configured in our AppTheme class

class AppTheme {
// Private Constructor
AppTheme._();
static final lightTheme = ThemeData(
scaffoldBackgroundColor: Colors.white,
appBarTheme: AppBarTheme(
color: Colors.teal,
iconTheme: IconThemeData(
color: Colors.white,
),
),
textTheme: TextTheme(
bodyText2: TextStyle(
color: Colors.black,
),
),
// ... more
);
static final darkTheme = ThemeData(
scaffoldBackgroundColor: Colors.black,
appBarTheme: AppBarTheme(
color: Colors.black,
iconTheme: IconThemeData(
color: Colors.white,
),
),
textTheme: TextTheme(
bodyText2: TextStyle(
color: Colors.white,
),
),
// ... more
);
}

But we want to go a step further and give the user the option to enable/disable Night Mode as they please via our Switch Widget managed by Riverpod.

Let’s continue with the integration of Riverpod.

We need to add ProviderScope to the root of our app that stores the state of the Providers.

void main() {
runApp(ProviderScope(child: MyApp()));
}

Then we have to make a choice which Provider we want to use. Suitable Providers are:

StateNotifierProvider: New Provider that holds one state and rebuilds listening Widgets automatically once the state changes.

ChangeNotifierProvider : Provider that also exists in Provider library and only rebuilds listening Widgets when notifyListeners(); is called manually.

I decided to go with ChangeNotifierProvider for the readability but I will also attach the source code with StateNotifierProvider at the end.

Let’s create the provider as global provider optionally in an extra file:

import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:themingappsample/src/theme/app_theme_state.dart';
// Theme
final appThemeStateNotifier = ChangeNotifierProvider((ref) => AppThemeState());

This will give us access to an instance of AppThemeState class. Which looks like this:

class AppThemeState extends ChangeNotifier {
var isDarkModeEnabled = false;
void setLightTheme() {
isDarkModeEnabled = false;
notifyListeners();
}
void setDarkTheme() {
isDarkModeEnabled = true;
notifyListeners();
}
}

As you can see we have one variable called isDarkModeEnabled which represents our state and two method with those we can adjust this state.

Also AppThemeState extends ChangeNotifier which allows you to subscribe to its changes and call notifyListeners() Method to rebuild the listening Widgets.

As next we have to update our MyApp Widget to listen to our appThemeStateNotifier.

class MyApp extends HookWidget {
@override
Widget build(BuildContext context) {
final appThemeState = useProvider(appThemeStateNotifier);
return MaterialApp(
title: 'Flutter Demo',
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: appThemeState.isDarkModeEnabled ? ThemeMode.dark : ThemeMode.light,
home: MyHomePage(),
);
}
}

We made MyApp extend from HookWidget which is an alternative to ConsumerWidget in order to be able to listen to ChangeNotifier and it also reduces the boilerplate code we would otherwise need to write. useProvider, which is only usable in HookWidget, will give us access to our appThemeStateNotifier. Furthermore we added themeMode which will decide by our appThemeState if the theme will become Light Mode or Dark Mode.

So now every time our state changes MaterialApp will be rebuild and changes the apps theme according to our state.

At last step we need to adjust our Switch-Widget to change our state for us.

class DarkModeSwitch extends StatelessWidget {
@override
Widget build(BuildContext context) {
final appThemeState = context.read(appThemeStateNotifier);
return Switch(
value: appThemeState.isDarkModeEnabled,
onChanged: (enabled) {
if (enabled) {
appThemeState.setDarkTheme();
} else {
appThemeState.setLightTheme();
}
},
);
}
}

As you can see we use normal StatelessWidget here because we don’t need this part of the code listen to our state. When we change the state it will get rebuild anyway.

context.read() is another way to access our provider when we are not inside a HookWidget. We read the state, change the initial value for the Switch-Widget, and set Dark Mode or Light Mode whether the user enables or disables the switch.

That’s basically all you need to do.

But you might notice that Text won’t change its color when you are changing the Mode. We have to tell some Widgets which style they should listen to, by adding style Attribute and the specific style in the Theme.

Text("Light Mode", style: Theme.of(context).textTheme.bodyText2),
DarkModeSwitch(),
Text("Dark Mode", style: Theme.of(context).textTheme.bodyText2),

Here is our result:

Screen toggling Light and Dark Mode

In my opinion Riverpod is amazing and helps to reduce to write code over and over again, especially in combination with flutter_hooks. I have just started to use it and wanted to share my first experience with it.

Here is the full code:

And here the code using StateNotifierProvider instead:

Thank you very much for reading. Please let me know if you have any Feedback — I would appreciate it !

--

--