There are two ways in which one can introduce dark/light theme mode in a flutter app.
- Theming automatically according to the device dark/light mode.
- Explicit toggle option of dark/light mode in the app.
Let us try out both the options.
1. Theme according to device dark/light theme
Flutter provides a darkTheme property for MaterialApp. This dark theme property is used when the phone setting has dark mode enabled.
So let us start with just one line of basic setting of dark theme.
MaterialApp( title: "My App", MaterialApp( theme: ThemeData( brightness: Brightness.light, primaryColor: Colors.red, ), darkTheme: ThemeData( brightness: Brightness.dark, ), home: MyHomePage(), );
Run the app, change your device appearance to dark/light and you can see the change automatically reflected in the app!
Okay, next let us add some custom styles to our theme data.
2. Custom dark/light mode using Provider [and persisting the user setting]
Step 1: Create themes
First off, let us define some theme data for both dark and light themes. Put these anywhere, I have put these functions in a separate file named styles.dart, for cleaner code. Here we use the base light and dark theme data from flutter and provide some values of our own. You can specify any of the other attributes inside theme data.
ThemeData buildLightTheme() { final ThemeData base = ThemeData.light(); return base.copyWith( buttonColor: Colors.redAccent, cardColor: Colors.white, backgroundColor: Colors.white, primaryColor: Colors.red, accentColor: Colors.redAccent, scaffoldBackgroundColor: Colors.white, ); } ThemeData buildDarkTheme() { final ThemeData base = ThemeData.dark(); return base.copyWith( buttonColor: Colors.blueAccent, cardColor: Colors.grey[800], backgroundColor: Colors.grey[800], primaryColor: Colors.blue[900], accentColor: Colors.blueAccent, scaffoldBackgroundColor: Colors.grey[900], ); }
Now let us start implementing the functionality.
Step 2: Add dependency
For this method, we will be using Providers, so we start off by adding the dependency in pubspec,yaml file. We will also add the shared_preferences dependency so that we can persist the theme setting when the user opens the app later too.
dependencies: provider: ^4.0.1 shared_preferences: ^0.5.6+3
Step 3: Preference class for saving the settings
First, create a class called AppPreference where we shall save and retrieve the user’s chosen theme setting via SharedPreferences. We are also returning a default value of false for the first time when there is nothing saved. We are doing this so that the setting is persistent, that is, it remains when the user closes the app and reopens it later. You can add more code to save other such user settings also in this class.
import 'package:shared_preferences/shared_preferences.dart'; class AppPreference { static const THEME_SETTING = "THEMESETTING"; setThemePref(bool value) async { SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.setBool(THEME_SETTING, value); } Future<bool> getTheme() async { SharedPreferences prefs = await SharedPreferences.getInstance(); return prefs.getBool(THEME_SETTING) ?? false; } }
Step 4: The provider class
Create a provider class for theme data, make it extend ChangeNotifier. A ChangeNotifier class allows your app to be notified everytime it changes, so that any class consuming it (our entire app in this case) can be redrawn.
You can learn more about App State Management here.
In this class, we define a boolean value that will hold the theme setting, its getter and setter. in the setter, we save the data in our shared preferences as well to persist it.
class AppModel extends ChangeNotifier { AppPreference appPreference = AppPreference(); bool _darkTheme = false; bool get darkTheme => _darkTheme; set darkTheme(bool value) { _darkTheme = value; appPreference.setThemePref(value); notifyListeners(); } }
In the setter, we have called notifyListeners(), so whenever this value is changed, the UI will be updated immediately, as long as the provider is attached to the screen. Since we will need this throughout the app, we will attach the consumer at the top level of the app.
Step 5: The consumer
It’s time to build the root of our app. Change the main function of app to this
void main() async { Provider.debugCheckInvalidValueType = null; runApp(App()); }
And then build the app widget. On initialization, in the initState method we will get the saved theme value (or the default value, if nothing’s saved yet). We wrap our app with ChangeNotidierProvider and set this value that we received into it.
We put our MaterialApp widget inside this wrapped with a Consumer so that our MaterialApp is rebuilt whenever the theme changes.
class App extends StatefulWidget { @override State<StatefulWidget> createState() { return AppState(); } } class AppState extends State<App> { AppModel appModel = new AppModel(); @override void initState() { super.initState(); _initAppTheme(); } void _initAppTheme() async { appModel.darkTheme = await appModel.appPreference.getTheme(); } @override Widget build(BuildContext context) { return ChangeNotifierProvider<AppModel>.value( value: appModel, child: Consumer<AppModel>( builder: (context, value, child) { return MaterialApp( debugShowCheckedModeBanner: false, theme: appModel.darkTheme ? buildDarkTheme() : buildLightTheme(), home: SplashScreen(), ); } ), ); } }
Step 6: The button for theme change
Now we simply use Provider to get the AppModel and set the theme value from wherever you like, and the entire UI will be updated immediately.
First get the Provider
final appModel = Provider.of<AppModel>(context);
Now add the function to the widget
FlatButton( onPressed: () { appModel.darkTheme = !appModel.darkTheme; }, child: Text('Toggle dark theme'), ),
Thank you for reading!
Leave A Comment