Down the widget tree, you can access ThemeData simply by writing Theme.of(context). If you want to access the current ThemeData and provide your own styling for certain field, you can do for an instance:
MaterialApp(
title: 'App Title',
theme: ThemeData(
brightness: Brightness.light,
/* light theme settings */
),
darkTheme: ThemeData(
brightness: Brightness.dark,
/* dark theme settings */
),
themeMode: ThemeMode.dark,
/* ThemeMode.system to follow system theme,
ThemeMode.light for light theme,
ThemeMode.dark for dark theme
*/
debugShowCheckedModeBanner: false,
home: YourAppHomepage(),
);
Using CupertinoApp
Detect the dark mode using, WidgetsBinding.instance?.window.platformBrightness
You may also have to listen for the brightness changes from the system in order to update in real-time using WidgetsBindingObserver, and overriding, didChangePlatformBrightness();
The easiest way in my opinion is by using provider to manage the state of your app and shared_preferences to save your theme preference on file system. By following this procedure you can save your theme so the user doesn't have to switch theme every time.
Output
You can easily store your theme preference in form of a string and then at the start of your app check if there is value stored on file system, if so apply that theme as shown below.
StorageManager.dart
import 'package:shared_preferences/shared_preferences.dart';
class StorageManager {
static void saveData(String key, dynamic value) async {
final prefs = await SharedPreferences.getInstance();
if (value is int) {
prefs.setInt(key, value);
} else if (value is String) {
prefs.setString(key, value);
} else if (value is bool) {
prefs.setBool(key, value);
} else {
print("Invalid Type");
}
}
static Future<dynamic> readData(String key) async {
final prefs = await SharedPreferences.getInstance();
dynamic obj = prefs.get(key);
return obj;
}
static Future<bool> deleteData(String key) async {
final prefs = await SharedPreferences.getInstance();
return prefs.remove(key);
}
}
Define your theme properties in a theme variable like below and initialize your _themedata variable on the basis of value inside storage.
Wrap your app with themeProvider and then apply theme using consumer. By doing so whenever you change the value of theme and call notify listeners widgets rebuild to sync changes.
You can provide theme to your whole app using theme for default themes, darkTheme for Dark themes (if dark mode is enabled by the system or by you using themeMode)
You can make use of provider package as shown in the code above.
A Flutter plugin that helps you to automatically change the theme of the app with sunrise and sunset. Just specify the light and dark theme to use, and you are all set. You can use your custom sunrise and sunset time too.
How to use it?
Add the latest version of the package in your pubspec.yaml
theme: ThemeData.light(), // Provide light theme.
darkTheme: ThemeData.dark(), // Provide dark theme.
themeMode: ThemeMode.system,
//use only these three line for dynamic change theme respect to system theme.
iOS simulator: Settings > Developer > Dark Appearance.
any time the device theme changes, your app will immediately reflect the chosen device theme
to get the current device theme mode programmatically, we can check device brightness (Brightness.light or Brightness.dark) which corresponds to light mode and dark mode. Do this by querying platformBrightness with: MediaQuery.of(context).platformBrightness
App Controlled Dark Mode
our app can run in either light or dark mode, controlled by user and switched freely at runtime inside the app and completely ignore the device's theme setting
as before, supply all three theme arguments to MaterialApp: theme:, darkTheme: and themeMode:, but we'll adjust themeMode: to use a state field below
To switch between light / dark modes within the app, we'll swap the themeMode: argument between ThemeMode.light and ThemeMode.dark and rebuild the MaterialApp widget.
How to Rebuild MaterialApp widget
to switch our app theme from anywhere, we need to access MaterialApp from anywhere in our app
we can do this without any package using just StatefulWidget, or we can use a state management package
example of runtime theme switching anywhere in app using StatefulWidget below
Before - Stateless
we started with this, but we'll replace it with a StatefulWidget next
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(),
darkTheme: ThemeData.dark(), // standard dark theme
themeMode: ThemeMode.system, // device controls theme
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
After - Stateful
here we've replaced MyAppStatelessWidget with a StatefulWidget and its complementary State class, _MyAppState
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(),
darkTheme: ThemeData.dark(), // standard dark theme
themeMode: ThemeMode.system, // device controls theme
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
Add Static Accessor to StatefulWidget
adding this static of() method to our StatefulWidget makes its State object accessible for any descendant widget
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
/// ↓↓ ADDED
/// InheritedWidget style accessor to our State object.
static _MyAppState of(BuildContext context) =>
context.findAncestorStateOfType<_MyAppState>()!;
}
/// State object hidden ↓. Focusing on ↑ StatefulWidget here.
note the return Type of our of() method: _MyAppState
we're not getting the StatefulWidget, we're getting its State object: _MyAppState
_MyAppState will hold the "state" of our ThemeMode setting (in next step). This is what controls our app's current theme.
next in our _MyAppState class we'll add a ThemeMode "state" field and a method to change theme & rebuild our app
_MyAppState
below is our State class modified with:
a "state" field _themeMode
MaterialAppthemeMode: arg using _themeMode state field value
changeTheme method
class _MyAppState extends State<MyApp> {
/// 1) our themeMode "state" field
ThemeMode _themeMode = ThemeMode.system;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(),
darkTheme: ThemeData.dark(),
themeMode: _themeMode, // 2) ← ← ← use "state" field here //////////////
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
/// 3) Call this to change theme from any context using "of" accessor
/// e.g.:
/// MyApp.of(context).changeTheme(ThemeMode.dark);
void changeTheme(ThemeMode themeMode) {
setState(() {
_themeMode = themeMode;
});
}
}
next, we'll show how to access changeTheme() to change our theme & rebuild the app
Change Theme & Rebuild
below is an example of using the of() accessor method to find our State object and call its changeTheme method from the two buttons below which call:
MyApp.of(context).changeTheme(ThemeMode.light)
MyApp.of(context).changeTheme(ThemeMode.dark)
class MyHomePage extends StatelessWidget {
final String title;
MyHomePage({required this.title});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Choose your theme:',
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
/// //////////////////////////////////////////////////////
/// Change theme & rebuild to show it using these buttons
ElevatedButton(
onPressed: () => MyApp.of(context).changeTheme(ThemeMode.light),
child: Text('Light')),
ElevatedButton(
onPressed: () => MyApp.of(context).changeTheme(ThemeMode.dark),
child: Text('Dark')),
/// //////////////////////////////////////////////////////
],
),
],
),
),
);
}
}
To return theme control back to the device's Dark mode setting, create a third button that makes a call to set themeMode: to ThemeMode.system:
MyApp.of(context).changeTheme(ThemeMode.system)
Running this method will delegate control of the app's theme back to whatever Dark mode setting the device is currently using.
Little late to the party you can implement it without any third party state management using the built-in ValueNotifier.This approach allows you to change the theme of your entire app from any part of the app.
as per your need use darkTheme: ThemeData( use theme properties you need in dark mode)
description:
if dark mode is selected in your system then flutter uses darkTheme property of MaterialApp and if light is selected then flutter uses theme property of MaterialApp, below code shows when you select (try it in your cellphone) dark option in your system then your app will show scaffoldBackgroundColor: Colors.red and if you select light then it will show scaffoldBackgroundColor: Colors.amber
I've found a very nice approach from ITnext where no third-party packages (except for either shared_preferences or hive) are necessary. Here a short summary (without the imports and with a switcher):
// this makes all variables available globally
library config.globals;
// initialize the theme model once
ThemeModel currentTheme = ThemeModel();
// also declare the box
Box? box;
config.dart
class ThemeModel with ChangeNotifier {
// initialize the standard theme here, possible with an elvis to check for the brightness
static bool _isDark = false;
// set a getter just for a more clean approach
bool get isDark => _isDark;
ThemeModel() {
// check for a stored value on initialization
if(box!.containsKey("currentTheme")) {
_isDark = box!.get("currentTheme");
} else {
// if there is no value, apply the standard theme
box!.put("currentTheme", _isDark);
}
}
ThemeMode currentTheme() {
return _isDark ? ThemeMode.dark : ThemeMode.light;
}
void switchTheme() {
// switches the theme by reversing the boolean
_isDark = !_isDark;
// storing the new value
box!.put("currentTheme", _isDark);
// notifies all listeners attached to the theme model
notifyListeners();
}
}
theme_model.dart
void main() async {
// waits for the hive init before running the app
box = await Hive.openBox("theme");
runApp(YourApp());
}
class YourApp extends StatefulWidget {
@override
_YourAppState createState() => _YourAppState();
}
class _YourAppState extends State<YourApp> {
@override
void initState() {
super.initState();
// we are setting a listener to the currentTheme,
// so it gets notified once we toggle it
currentTheme.addListener(() {
setState((){});
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Your title',
theme: ThemeData().light,
darkTheme: ThemeData().dark,
// it will always listen to changes made to currentTheme
themeMode: currentTheme.currentTheme(),
home: HomePage(),
);
}
}
main.dart
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: <Widget>[
Switch(
// looking for the current value and sets the switch state
value: currentTheme.isDark,
onChanged: (value) {
setState(() {
// we then set the state to the new current theme
currentTheme.switchTheme();
});
},
),
// this is just a text next to the switch stating the current theme
Text("${currentTheme.currentTheme().toString().split(".")[1]} mode"),
],
);
);
}
}
homepage.dart
You can set the default value to ThemeData.system, if you want to get the users ui preferences. You have to adjust the code to look after the current brightness and then set the theme regarding to the state of it. After that it uses a switch to toggle between dark and light mode.
In the MyApp class declare this in MaterialApp like this
theme: Provider.of<ThemeState>(context).theme == ThemeType.DARK
? ThemeData(
// Define the default brightness and colors.
brightness: Brightness.dark,
primaryColor: Colors.lightBlue[800],
// Define the default font family.
fontFamily: 'Georgia',
// Define the default `TextTheme`. Use this to specify the default
// text styling for headlines, titles, bodies of text, and more.
textTheme: const TextTheme(
headline1:
TextStyle(fontSize: 32.0, fontWeight: FontWeight.bold),
headline6:
TextStyle(fontSize: 16.0, fontStyle: FontStyle.italic),
bodyText2: TextStyle(fontSize: 10.0, fontFamily: 'Hind'),
),
)
: ThemeData(
// Define the default brightness and colors.
brightness: Brightness.light,
primaryColor: Colors.lightGreen[300],
// Define the default font family.
fontFamily: 'Georgia',
// Define the default `TextTheme`. Use this to specify the default
// text styling for headlines, titles, bodies of text, and more.
textTheme: const TextTheme(
headline1:
TextStyle(fontSize: 32.0, fontWeight: FontWeight.normal),
headline6:
TextStyle(fontSize: 16.0, fontStyle: FontStyle.italic),
bodyText2: TextStyle(fontSize: 10.0, fontFamily: 'Hind'),
),
),