Multi — Themes Using Riverpod in Flutter

Shree Bhagwat
11 min readOct 23, 2022

--

Image depicting multi-theme
Multi-Themes using riverpod in Flutter.

Using mobile apps has become an integral task in today’s world. There is an app for almost everything out there and many apps for the same use case. Therefore, retaining users has become a difficult task nowadays. In situations like these, the UI/UX of the app plays a very critical role in attracting and making the user stay on the app.

Themes have a major role in increasing the User Experience of the application as user can set the theme of the application as he/she wishes. Hence, the user is able to create a more personalised and comfortable experience with the app.

In 2019 google introduced Dark Theme at the Google’s I/O and immediately later on Apple in WWDC introduced Dark Mode. Both tech Giants introduced the Dark and light mode to improve the user experience of the mobile usage. This opened the gates to bring dark and light theme to other apps as well and many applications support the basic dark and light mode.

Let us take Theming one step ahead by adding multiple themes to the application.

In the article I will be explaining how to add multiple themes to Flutter apps and how you can use riverpod to manage the themes of the application.

This article assumes that you have some basic knowledge of Flutter and Provider or Inherited Widgets.

Getting Started:

Before we begin — — this is what we are going to build. An app where user can switch between multiple themes without reloading/restarting the app.

Multi Theme in Flutter by Shree Bhagwat
Multi-theme Flutter App

First of all, create a new Flutter app and show a blank HomeScreen() as the home widget in side MaterialApp. We will set this HomeScreen later when we have created our themes.

Creating Themes:

Lets start by creating themes. Create a new folder named Style inside lib and create a dart file named styles.dart inside the Style folder. Create a class named Styles inside the styles.dart file. All the styling code will be written inside this class.

Create a function named themeData of return type ThemeData which takes two parameters: BuildContext and int This function will return the ThemeData that we will be creating soon and the int value will decide which theme data to return.

ThemeData themeData(int index, BuildContext context){ 
}

Understanding the ThemeData:

ThemeData class defines the configuration of the overall visual Theme for a MaterialApp or a widget subtree within the app. This MaterialApp theme property can be used to configure the appearance of the entire app.

ThemeData has the following properties that we can change and configure the apps theme. Below are the basic properties of the ThemeData that we are going to configure. These all properties are self explanatory. You can read more about them in the official flutter doc — ThemeData

ThemeData(
primaryColor:
backgroundColor:
indicatorColor:
hintColor:
errorColor:
highlightColor:
focusColor:
disabledColor:
cardColor:
brightness:
buttonTheme:
appBarTheme:
);

Create a Light Mode Theme:

Create a new function named setWhiteTheme which takes a BuildContext as a parameter and has ThemeData as the return type. We will configure our Day/Light/White theme in this function. We are going to assign colours to the ThemeData and return this themeData. To do so add the following code to the function.

ThemeData setWhiteTheme(BuildContext context) {
return ThemeData(
primaryColor: Colors.white,
backgroundColor: Colors.white,
indicatorColor: Colors.black,
hintColor: Colors.grey.shade600,
errorColor: Colors.red.shade500,
highlightColor: Colors.grey.shade200,
focusColor: Colors.black,
disabledColor: Colors.grey.shade300,
cardColor: Colors.grey.shade100,
brightness: Brightness.light,
buttonTheme: Theme.of(context)
.buttonTheme
.copyWith(colorScheme: const ColorScheme.light()),
appBarTheme: const AppBarTheme(
backgroundColor: Colors.blue,
elevation: 0,
),
);
}

Creating a Dark Mode Theme:

Great, let us create a dark mode theme as we created the light mode theme.

Create a new function below setWhiteTheme and name it as setBlackTheme as follows.

ThemeData setBlackTheme(BuildContext context) {
return ThemeData(
primaryColor: Colors.black,
backgroundColor: Colors.black,
indicatorColor: Colors.white,
hintColor: Colors.grey.shade500,
errorColor: Colors.red.shade900,
highlightColor: Colors.grey.shade700,
focusColor: Colors.white,
disabledColor: Colors.grey.shade800,
cardColor: const Color.fromARGB(255, 41, 40, 40),
brightness: Brightness.dark,
buttonTheme: Theme.of(context)
.buttonTheme
.copyWith(colorScheme: const ColorScheme.dark()),
appBarTheme: const AppBarTheme(
backgroundColor: Color.fromARGB(255, 28, 28, 28),
elevation: 0,
),
);
}

A common Function for other Themes

Lets create a common function to manage other themes as well. We will pass Color as a parameter in the this function so that we can change the colour of the application by setting this theme as well.

ThemeData setOtherTheme(
{required BuildContext context,
required MaterialColor mColor,
required Color color}) {
return ThemeData(
primarySwatch: mColor,
primaryColor: mColor,
backgroundColor: Colors.white,
indicatorColor: mColor,
hintColor: mColor.shade200,
errorColor: mColor.shade500,
highlightColor: Colors.grey.shade200,
focusColor: color,
disabledColor: Colors.grey.shade300,
cardColor: Colors.grey.shade100,
brightness: Brightness.light,
buttonTheme: Theme.of(context)
.buttonTheme
.copyWith(colorScheme: const ColorScheme.light()),
appBarTheme: AppBarTheme(
backgroundColor: mColor,
elevation: 0,
),
);
}

Returning the Correct Theme as Required

Now that our theme data configuration is done. lets call these functions inside themeData function that was created on top. Scroll up to the themeData function and we will use a Switch — Case to return the type of ThemeData configuration we want for our application.

ThemeData themeData(int index, BuildContext context) {
switch (index) {
case 0:
return setWhiteTheme(context);
case 1:
return setBlackTheme(context);
case 2:
return setOtherTheme(
context: context, mColor: Colors.red, color: Colors.red);
case 3:
return setOtherTheme(
context: context, mColor: Colors.green, color: Colors.greenAccent);
;
case 4:
return setOtherTheme(
context: context, mColor: Colors.blue, color: Colors.blueAccent);
case 5:
return setOtherTheme(
context: context,
mColor: Colors.yellow,
color: Colors.yellowAccent);
case 6:
return setOtherTheme(
context: context, mColor: Colors.amber, color: Colors.amber);
case 7:
return setOtherTheme(
context: context,
mColor: Colors.deepPurple,
color: Colors.deepPurpleAccent);
default:
return setWhiteTheme(context);
}
}

We pass this int value to the function from the listView that we will be creating on the HomeScreen to select the themeColour.

Make sure the int value you pass denotes the same colour in the listView as it denotes here. Otherwise you app will show a different theme which wont be optimal.

Custom TextTheme:

Using custom colour is only the half part of our theming journey. Let us also create a custom TextTheme that can be used to theme the text of the app.

Inside the Styles class itself on top lets create a default text style which we will be modifying to create custom text theme.

static const _defaultTextStyle = TextStyle(fontWeight: FontWeight.w500, fontFamily: your_custom_font_family);

This helps us create a default custom TextStyle which we will be modifying further to customise as we want. You can also pass your own custom font here.

Understanding TextTheme:

Just like ThemeData helps in configuring the overall visual appearance of the app. TextTheme helps in Configuring overall text of the app. We can use TextTheme to decide the type of text you want.

Text Themes have properties like headline1 … headline6, bodyText1, bodyText2, subtitle1, subtitle2. We will set the custom settings for these properties and use it in our application.

Creating Custom TextThemes

Add the following function and the end of your Styles class In this function we customise the above created _defaultTextStyle and assign them to the parameters of the TextTheme

In this function we use the CopyWith method to modify the the _defaultTextStyle and have altered the font size and font colour. You are free to experiment to all the configuration here.

static TextTheme textTheme(BuildContext context) {
return TextTheme(
headline1: _defaultTextStyle.copyWith(
fontSize: 100,
color: Theme.of(context).indicatorColor,
fontWeight: FontWeight.w200),
headline2: _defaultTextStyle.copyWith(
fontSize: 25, color: Theme.of(context).indicatorColor),
headline3: _defaultTextStyle.copyWith(
fontSize: 16, color: Theme.of(context).indicatorColor),
headline4: _defaultTextStyle.copyWith(
fontSize: 18, color: Theme.of(context).indicatorColor),
headline5: _defaultTextStyle.copyWith(
fontSize: 14, color: Theme.of(context).indicatorColor),
bodyText1: _defaultTextStyle.copyWith(
fontSize: 13, color: Theme.of(context).indicatorColor),
bodyText2: _defaultTextStyle.copyWith(
fontSize: 20, color: Theme.of(context).indicatorColor),
subtitle1: _defaultTextStyle.copyWith(
fontSize: 12, color: Theme.of(context).hintColor),
subtitle2: _defaultTextStyle.copyWith(
fontSize: 10, color: Theme.of(context).hintColor),
);
}

Thats it we are done creating the Styles and Theme configuration. Our Styles and Themes are ready to be used inside our application.

Adding Riverpod to Project

Next we will be using riverpod to select and add the above created themes into our application.

lets start by adding riverpod dependencies to our pubspec.yaml file under the dependencies section as follows. You might have a different version but as long its above 2.0.2 you are good to go.

dependencies:
flutter_riverpod: ^2.0.2

don’t forget to run flutter pub get in terminal after adding dependencies.

Creating ThemeProvider

Let’s create a Theme Provider which will help us provide the theme that we have selected to our entire app.

Create a folder named Provider and create a new file named theme_provider.dart . Add a class named ThemeProvider which impliments ChangeNotifier inside this file. This will help us to notify when the selected theme is changed and changes will be brought to the application.

add a private variable which will hold the int value of the selected theme that will passed to the themeData function in the Styles class. Let’s give 0 as the default value.

int _themeIndex = 0;

Private variables are not accessible outside the class so lets create getter of the private int variable.

int get themeIndex => _themeIndex;

now finally lets us create a function which will help us assign the int value to the private variable which is created on top. This function takes an int value as a parameter and assigns that value to private int variable. We call notify Listeners so that our widget and application listen the changes that took place.

setTheme(int value) {
_themeIndex = value;
notifyListeners();
}

Create a global variable below the ThemeProvider class to make our theme provider accessible through out the application. We use ChangeNotifierProvider for this.

final themeProvider = ChangeNotifierProvider<ThemeProvider>((ref) => ThemeProvider());

Compiling our app to Riverpod:

When you use riverpod there are some minute change you have to do inside your main.dart file to make the app working.

  • Wrapping the widget inside ProviderScope : In the main.dart wrap the widget that you pass inside the runApp function with a ProviderScope widget
void main() {runApp(ProviderScope(child: MultithemeRiverpod()));}
  • Warp the MaterialApp with a Consumer Widget so that it consumes all the changes of the theme data that we have passed. When you use Consumer widget you have to use the builder parameter of the consumer widget to build your widget when there is some change in the data.
@override
Widget build(BuildContext context) {
return Consumer(builder: (context, ref, child) {
// Create a theme notifier variable to use the index
return MaterialApp(
// Todo: Add theme styles here
debugShowCheckedModeBanner: false,
home: HomeScreen(),
);
});
}
}

If you notice we have 2 TODOs left.

  • Lets start by creating a themeNotifier variable that will be used to pass the index inside our themeData function
final themeNotifer = ref.watch(themeProvider);
  • Call the themeData function on the theme: parameter of the material app and pass the selected index and context inside the function. You will get the index from the themeNotifier variable that you have create.
theme: styles.themeData(themeNotifer.themeIndex, context),

do not forget to initialise the styles variable at the top or it will throw an error

Styles styles = Styles();

How to use the custom theme:

  • Theme.of(context): Use the Theme.of(context) to use the custom colour that your configured inside the themeData function.
    for example —
// Scaffold background colour
* backgroundColor: Theme.of(context).backgroundColor
// Text Error Border Colour
* color: Theme.of(context).errorColor,
  • Styles.textTheme(context): Use the static textTheme function from the Styles class to change the theme of the text. You can choose from headline1…headline6, bodyText and subtitle text themes that you want.
style: Styles.textTheme(context).headline4,

Creating the UI

Now that you know how to use the themes lets us create a basic UI to test our Theme. Copy paste the code below in the home_screen.dart file.

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:multitheme_riverpod/Providers/theme_provider.dart';
import 'package:multitheme_riverpod/Style/style.dart';
class HomeScreen extends StatelessWidget {
HomeScreen({super.key});
List<Color> colorsList = [
Colors.white,
Colors.black,
Colors.redAccent,
Colors.green,
Colors.blue,
Colors.amber,
Colors.orange,
Colors.deepPurpleAccent,
];
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
appBar: AppBar(
title: const Text('Home Screen'),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Title',
style: Styles.textTheme(context).headline1,
),
),
Padding(
padding: const EdgeInsets.all(12.0),
child: TextField(
style: Theme.of(context).textTheme.bodyText1,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.person),
suffixIcon: IconButton(
icon: Icon(
Icons.visibility,
color: Theme.of(context).focusColor,
),
onPressed: () {},
),
hintText: 'Enter Name',
hintStyle: Styles.textTheme(context).subtitle1,
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(
color: Theme.of(context).focusColor,
),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(
color: Theme.of(context).errorColor,
),
),
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Checkbox'),
Checkbox(
value: true,
onChanged: (value) {
print(value);
},
),
],
),
),
// Text having lorem ipsum paragraph
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec auctor, nisl eget aliquam tincidunt, nunc nisl aliquam nisl, eget aliquam nisl nunc eget nisl. Donec auctor, nisl eget aliquam tincidunt, nunc nisl aliquam nisl, eget aliquam nisl nunc eget nisl.',
style: Styles.textTheme(context).bodyText1,
),
),
Padding(
padding: const EdgeInsets.all(20.0),
child: Text(
'Themes',
style: Styles.textTheme(context).headline4,
),
),
SizedBox(
width: MediaQuery.of(context).size.width,
height: 50,
child: ListView.builder(
physics: const PageScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: colorsList.length,
shrinkWrap: true,
itemBuilder: ((context, index) {
return Consumer(
builder:
(BuildContext context, WidgetRef ref, Widget? child) {
final themeNotifer = ref.watch(themeProvider);
return GestureDetector(
child: Container(
height: 100,
width: 100,
decoration: BoxDecoration(
border: Border.all(color: Colors.black, width: 3),
color: colorsList[index],
shape: BoxShape.circle),
),
onTap: () {
themeNotifer.setTheme(index);
},
);
},
);
}),
),
),
],
),
);
}
}

Please go through the code, you will see how we have used the theme properties to add custom theme to the widget and text.

At the bottom we have created a horizontal list view which will help us in selecting the theme that we want for our app. On the top we have list of Colours that can be displayed in the list view below.

We have used Consumer widget to warp our GestureDectector widget and called the setTheme function where we pass the index of the colour that has been selected/clicked up. The setTheme function sets the index to the private variable _themeIndex that we have created in the ThemeProvider class and this value is called in the main.dart by the getter value of the themeIndex.

Run the app and it should work as shown in the following gif.

Multi Theme in Flutter by Shree Bhagwat
Final working app

Conclusion

Using riverpod for managing the state of the application makes setting the themes very easy. Themes gives our app a way of personalisation which helps in increasing the User Experience of the app. This gives our user an option to choose among any theme the user is comfortable.

You can also give user an option to select the primary colour and secondary colour and assign that values to our themeData function.

Use SharedPreference to save the users selected theme so user wont have to set the theme everytime they open the app.

Here is the complete code pushed to Github — Multi-Theme-App
don’t forget to Like/Start the repo.

Thank You for reading my blog, if you have any doubt or recommendation please let me know in the comment section below. I would like to know what are the other way to add multi theme to flutter app.

You have 50 Claps per day. Claps do Motivate us to keep writing. Gotta Use Them All

--

--