Switching Themes in Flutter (☀️/🌙).
Switching app theme with provider and saved with Shared Preferences.
Install Packages:
provider: ^6.0.5
shared_preferences: ^2.0.20
DarkThemePrefs Class:
Define a class DarkThemePrefs
that has methods for setting and getting a boolean value related to the dark theme status of an application, using the shared_preferences
package.
The setDarkTheme
method takes a boolean value as a parameter and saves it to the shared preferences with the key THEME_STATUS
. It uses the SharedPreferences.getInstance()
method to get an instance of the shared preferences object and then calls the setBool
method on that object to save the boolean value with the given key.
The getTheme
method retrieves the previously saved boolean value related to the dark theme status from the shared preferences using the same key THEME_STATUS
. It returns a Future<bool>
type as it needs to await the asynchronous call to SharedPreferences.getInstance()
method. It uses the getBool
method on the shared preferences object to retrieve the boolean value. If there is no saved value, it returns false
by default using the null-aware operator ??
.
Overall, this code allows for the persistent storage and retrieval of a boolean value related to the dark theme status of an application using the shared_preferences
package.
Save and fetch from the phone memory to show the last applied theme.
import 'package:shared_preferences/shared_preferences.dart';
class DarkThemePrefs {
//?defining theme status key
static const themeStatus = 'THEME_STATUS';
//***************SETTING DARK THEME METHOD TO SAVE***************//
setDarkTheme(bool value) async {
SharedPreferences preferences = await SharedPreferences.getInstance();
preferences.setBool(themeStatus, value);
}
//***************GETTING DARK THEME METHOD WHICH IS BEING SAVED ***************//
Future<bool> getTheme() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
return preferences.getBool(themeStatus) ?? false;
}
}
DarkThemeProvider Class:
Now define a class DarkThemeProvider
that extends the ChangeNotifier
class, indicating that it can be used to notify the UI when the dark theme status is changed. The class has a private boolean variable _darkTheme
that represents the current status of the dark theme. It also has a public getter method getDarkTheme
to retrieve the value of the private _darkTheme
variable.
The setDarkTheme
method sets the value of _darkTheme
to the passed value
boolean parameter and then saves this value to shared preferences by calling the setDarkTheme
method of the DarkThemePrefs
object, which we saw in the previous example. Finally, it also calls the notifyListeners()
method to notify the UI about the change in the dark theme status.
The DarkThemeProvider
class works in conjunction with the DarkThemePrefs
class, where the DarkThemePrefs
class handles the actual storage and retrieval of the boolean value related to the dark theme status in the shared preferences, while the DarkThemeProvider
class allows for the manipulation of this value and notifies the UI when there is a change.
Overall, this code provides a convenient way to manage and update the dark theme status of an application, using shared preferences and the ChangeNotifier
class.
import 'package:flutter/material.dart';
import 'package:groceteria_app/Services/ThemeService/dark_theme_prefs.dart';
class DarkThemeProvider with ChangeNotifier {
//? Object of Dark Theme Prefs Class in order to save the theme changes to shared preferences
DarkThemePrefs darkThemePrefs = DarkThemePrefs();
bool _darkTheme = false; //private
bool get getDarkTheme => _darkTheme; //getting private var
//***************SETTING DARK THEME METHOD***************//
set setDarkTheme(bool value) {
_darkTheme = value; //save this in the shared preferences
darkThemePrefs.setDarkTheme(value);
notifyListeners();
}
}
getCurrentTheme Method:
Initializes an object themeChangeProvider
of the DarkThemeProvider
class. It also defines a method getCurrentTheme
, which is an asynchronous method that fetches the current theme from shared preferences using the darkThemePrefs.getTheme()
method.
Inside the method, await themeChangeProvider.darkThemePrefs.getTheme()
is used to retrieve the previously saved boolean value related to the dark theme status from the shared preferences using the getTheme()
method of the DarkThemePrefs
object inside the DarkThemeProvider
.
Once the value is retrieved from the shared preferences, it is passed to the setDarkTheme
method of the DarkThemeProvider
object using the assignment operator =
. The setDarkTheme
method sets the retrieved value as the current dark theme status and saves it to the shared preferences using the setDarkTheme
method of the DarkThemePrefs
object. Finally, it notifies the UI about the change in the dark theme status by calling the notifyListeners()
method.
Overall, this code allows for the retrieval and initialization of the dark theme status of an application by fetching it from the shared preferences using the DarkThemePrefs
and DarkThemeProvider
classes.
//* Fetching theme from shared preference so if the user choose the value so the chosen value will be fetched and applied
//Object oof Dark Theme Provider
DarkThemeProvider themeChangeProvider = DarkThemeProvider();
//Fetching theme from Shared Prefrence.
void getCurrentTheme() async {
themeChangeProvider.setDarkTheme =
(await themeChangeProvider.darkThemePrefs.getTheme());
}
Calling getCurrentTheme Method:
Initialize the state of a widget and set the current dark theme status by calling the getCurrentTheme()
method.
@override
void initState() {
super.initState();
getCurrentTheme();
}
MultiProvider:
The MultiProvider
widget is a widget provided by the provider
package in Flutter, which allows for the creation of multiple Provider
widgets and combines them into a single widget tree.
The ChangeNotifierProvider
widget is a type of provider that creates and provides an instance of a ChangeNotifier
class, which can then be accessed by child widgets.
It creates and returns a MultiProvider
widget that provides access to the themeChangeProvider
object of the DarkThemeProvider
class using the ChangeNotifierProvider
widget. This allows the child widgets to access and manipulate the dark theme status using the DarkThemeProvider
object, as explained in the previous examples.
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) {
return themeChangeProvider;
})
],
Consumer Widget:
Consumer
widget, which allows for reactive programming in Flutter. The Consumer
widget listens to changes in the DarkThemeProvider
object and rebuilds the child widget whenever there are changes.
The builder
function is a callback function that takes three arguments: context
, themeProvider
, and child
. The context
argument is a build context object, which is used to obtain the theme data. The themeProvider
argument is the DarkThemeProvider
object, which is provided by the ChangeNotifierProvider
widget.
A MaterialApp
widget that displays the home screen of the app using the theme data obtained from the DarkThemeProvider
object using the getDarkTheme
getter method. The Consumer
widget listens to changes in the DarkThemeProvider
object and rebuilds the child widget whenever there are changes, ensuring that the app is always up-to-date with the latest dark theme status.
child:Consumer<DarkThemeProvider>(builder: (context, themeProvider, child) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: Styles.themeData(themeProvider.getDarkTheme, context),
home: const HomeScreen(),
);
}),
Complete MyApp Code:
import 'package:flutter/material.dart';
import 'package:groceteria_app/Providers/ThemeProviders/dark_theme_provider.dart';
import 'package:provider/provider.dart';
import 'Features/Home/home_screen.dart';
import 'Themes/theme.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
//Object oof Dark Theme Provider
DarkThemeProvider themeChangeProvider = DarkThemeProvider();
//Getting theme from class DrakThemeProvider.
void getCurrentTheme() async {
themeChangeProvider.setDarkTheme =
(await themeChangeProvider.darkThemePrefs.getTheme());
}
@override
void initState() {
super.initState();
getCurrentTheme();
}
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) {
return themeChangeProvider;
})
],
child:
Consumer<DarkThemeProvider>(builder: (context, themeProvider, child) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: Styles.themeData(themeProvider.getDarkTheme, context),
home: const HomeScreen(),
);
}),
);
}
}
In Home Screen:
Retrieve the current state of the dark theme status from the DarkThemeProvider
object using the Provider.of
method and the current BuildContext
object. This allows for reactive programming in Flutter, where the UI is updated automatically whenever the state of the dark theme status changes.
final themeState = Provider.of<DarkThemeProvider>(context);
SwitchListTile Widget:
creates a SwitchListTile
widget.
The secondary
parameter is used to display an icon beside the title
. If the getDarkTheme
method of the themeState
object is true
, the Icons.dark_mode_rounded
icon is displayed, otherwise the Icons.light_mode
icon is displayed.
The value
parameter is set to the current state of the dark theme status, which is obtained by calling the getDarkTheme
method of the themeState
object.
The setDarkTheme
method of the themeState
object is then called with the new value of the switch, which updates the dark theme status of the app.
return Scaffold(
body: Center(
child: SwitchListTile(
activeColor: Colors.yellow,
title: const Text("Theme"),
secondary: Icon(themeState.getDarkTheme
? Icons.dark_mode_rounded
: Icons.light_mode),
onChanged: (bool value) {
setState(() {
themeState.setDarkTheme = value;
});
},
value: themeState.getDarkTheme,
),
),
);
Complete Home SCreen Code:
import 'package:flutter/material.dart';
import 'package:groceteria_app/Providers/ThemeProviders/dark_theme_provider.dart';
import 'package:provider/provider.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
final themeState = Provider.of<DarkThemeProvider>(context);
return Scaffold(
body: Center(
child: SwitchListTile(
activeColor: Colors.yellow,
title: const Text("Theme"),
secondary: Icon(themeState.getDarkTheme
? Icons.dark_mode_rounded
: Icons.light_mode),
onChanged: (bool value) {
themeState.setDarkTheme = value;
},
value: themeState.getDarkTheme,
),
),
);
}
}
I hope this article was helpful to you. Thank you for taking the time to read it. Your feedback and suggestions are always welcome.