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.
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 therunApp
function with aProviderScope
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 useConsumer
widget you have to use thebuilder
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 theindex
inside ourthemeData
function
final themeNotifer = ref.watch(themeProvider);
- Call the
themeData
function on thetheme:
parameter of the material app and pass the selected index and context inside the function. You will get the index from thethemeNotifier
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 thethemeData
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 statictextTheme
function from theStyles
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.
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