How to implement Dark mode and Light Mode in flutter?
Asked Answered
C

19

163

I want to create a flutter app that has 2 light and dark mode themes that change by a switch in-app and the default theme is default android theme.
I need to pass some custom color to the fellow widget and I don't want to just config material theme.

  • how to detect the user device default theme?
  • the secend question is how to provide a theme to the whole app?
  • third is how change the theme with a simple switch in running time?
Colander answered 14/2, 2020 at 18:55 Comment(3)
Did I understand you correctly, you have 3 themes, light mode, dark mode and a default android theme? The user can switching between the light- and dark mode theme? What exactly do you mean with need to pass some custom color to the fellow widget?Striated
no, I have dark mode and light mode only bypassing color I mean I have 2 colors white and grey for the background and border in the fellow widget so instead if write background:Colors.white i want background:store.xColorColander
Check my answer below, You should use ThemeMode.system to detect system theme.Gunzburg
C
72

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 enter image description here

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.

ThemeManager.dart

import 'package:flutter/material.dart';
import '../services/storage_manager.dart';

class ThemeNotifier with ChangeNotifier {
  final darkTheme = ThemeData(
    primarySwatch: Colors.grey,
    primaryColor: Colors.black,
    brightness: Brightness.dark,
    backgroundColor: const Color(0xFF212121),
    accentColor: Colors.white,
    accentIconTheme: IconThemeData(color: Colors.black),
    dividerColor: Colors.black12,
  );

  final lightTheme = ThemeData(
    primarySwatch: Colors.grey,
    primaryColor: Colors.white,
    brightness: Brightness.light,
    backgroundColor: const Color(0xFFE5E5E5),
    accentColor: Colors.black,
    accentIconTheme: IconThemeData(color: Colors.white),
    dividerColor: Colors.white54,
  );

  ThemeData _themeData;
  ThemeData getTheme() => _themeData;

  ThemeNotifier() {
    StorageManager.readData('themeMode').then((value) {
      print('value read from storage: ' + value.toString());
      var themeMode = value ?? 'light';
      if (themeMode == 'light') {
        _themeData = lightTheme;
      } else {
        print('setting dark theme');
        _themeData = darkTheme;
      }
      notifyListeners();
    });
  }

  void setDarkMode() async {
    _themeData = darkTheme;
    StorageManager.saveData('themeMode', 'dark');
    notifyListeners();
  }

  void setLightMode() async {
    _themeData = lightTheme;
    StorageManager.saveData('themeMode', 'light');
    notifyListeners();
  }
}

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.

Main.dart

void main() {
  return runApp(ChangeNotifierProvider<ThemeNotifier>(
    create: (_) => new ThemeNotifier(),
    child: MyApp(),
  ));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<ThemeNotifier>(
      builder: (context, theme, _) => MaterialApp(
        theme: theme.getTheme(),
        home: Scaffold(
          appBar: AppBar(
            title: Text('Hybrid Theme'),
          ),
          body: Row(
            children: [
              Container(
                child: FlatButton(
                  onPressed: () => {
                    print('Set Light Theme'),
                    theme.setLightMode(),
                  },
                  child: Text('Set Light Theme'),
                ),
              ),
              Container(
                child: FlatButton(
                  onPressed: () => {
                    print('Set Dark theme'),
                    theme.setDarkMode(),
                  },
                  child: Text('Set Dark theme'),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Here is the link to github repository.

Conlan answered 18/9, 2020 at 11:12 Comment(6)
This render slow if you are setting modes in somewhere down the widget hierarchy and not the root widget.Pathy
Normally apps have dark mode settings in-app settings. Do we need to use the MaterialApp widget there too? Let's say I don't want to change it from the home screen. How should I place the notifiers?Chichihaerh
No you don't need a material widget where you need to change the theme. You can use consumer any where in the code and get the theme object and then by using that object you can easily switch theme. Here is a link to a sample app that does what you are looking for github.com/mateenkiani/Inventory_management/blob/master/lib/src/…Conlan
I'm new to Flutter and am trying to implement what you have here using Flutter 3 and Dart 2.17. I'm getting a build error in ThemeManager.dart: Error: Field '_themeData' should be initialized because its type 'ThemeData' doesn't allow null. It seems the getTheme() method on the next line is the initializer, but it doesn't like the uninitialized property above it. Any ideas?Shovel
Hi! I get an error Non-nullable instance field '_themeData' must be initialized. Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late' in ThemeNotifier() function in ThemeManager.dart file. If I put late before ThemeData _themeData; I get an error LateError (LateInitializationError: Field '_themeData@31353943' has not been initialized.). What is happening???!! 🤔Kraut
Hi, I got this error: ( Non-nullable instance field '_themeData' must be initialized., and 'accentColor' is deprecated and shouldn't be used. Use colorScheme.secondary instead. For more information, consult the migration guide at flutter.dev/docs/release/breaking-changes/…. This feature was deprecated after v2.3.0-0.1.pre..)Morava
G
190

Using Material App

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

  1. Detect the dark mode using, WidgetsBinding.instance?.window.platformBrightness

  2. 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();

CupertinoApp Example:

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  Brightness? _brightness;

  @override
  void initState() {
    WidgetsBinding.instance?.addObserver(this);
    _brightness = WidgetsBinding.instance?.window.platformBrightness;
    super.initState();
  }

  @override
  void dispose() {
    WidgetsBinding.instance?.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangePlatformBrightness() {
    if (mounted) {
      setState(() {
        _brightness = WidgetsBinding.instance?.window.platformBrightness;
      });
    }

    super.didChangePlatformBrightness();
  }

  CupertinoThemeData get _lightTheme =>
      CupertinoThemeData(brightness: Brightness.light, /* light theme settings */);

  CupertinoThemeData get _darkTheme => CupertinoThemeData(
        brightness: Brightness.dark, /* dark theme settings */,
      );

  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      title: 'Demo App',
      theme: _brightness == Brightness.dark ? _darkTheme : _lightTheme,
      home: MyHomePage(title: 'Demo Home Page'),
    );
  }
}

You can use scoped_model, provider, bloc or get for seamless experience.

Gunzburg answered 27/6, 2020 at 8:49 Comment(5)
For other readers: you need to restart the application. Hot reloading after changing it to thememode.system does not work.Clothesbasket
How to do this with CupertinoApp?Smallminded
@MuhammadQasim Check the updated answer!Gunzburg
@RajYadav, your solution looks very clean. Could you please post a version using the provider to achieve the same behavior? Thanks in advance!Javed
Excellent solution! However as of Flutter 3.10.1 (at the moment of writing), the window property has been deprecated. This is the message: Deprecated. Will be removed in a future version of Flutter. This property has been deprecated to prepare for Flutter's upcoming support for multiple views and multiple windows. As an alternative it is said to Look up the current FlutterView from the context via View.of(context) or consult the PlatformDispatcher directly instead.Smoothshaven
B
110

Below are three ways to implement Dark Mode:

  • always Dark mode
  • device/platform controlled dark mode
  • app controlled, runtime switchable dark mode

Always Dark Mode

To run your app only in Dark Mode:

  • in MaterialApp, replace ThemeData(...) with ThemeData.dark()
  • restart your app. It will now be running in Dark Mode using the colors defined in ThemeData.dark()

OLD

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

NEW

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData.dark(), // default dark theme replaces default light theme
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

Device Controlled Dark Mode

  • works only on Android 10+, iOS 13+ (when dark mode was introduced)
  • to let the device/platform set the theme, MaterialApp needs 3 args:
    • theme: ThemeData()
    • darkTheme: ThemeData().dark
    • themeMode: ThemeMode.system
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'),
    );
  }
}
  • (you can use custom themes. Above are defaults for simplicity)
  • themeMode: ThemeMode.system tells Flutter to use the device/platform theme setting
  • with the above settings on Android 10+ or iOS 13+, toggling Dark mode via Device Settings will now switch your app between light and dark modes.
  • 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 MyApp StatelessWidget 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:
    1. a "state" field _themeMode
    2. MaterialApp themeMode: arg using _themeMode state field value
    3. 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')),
                /// //////////////////////////////////////////////////////
              ],
            ),
          ],
        ),
      ),
    );
  }
}

change theme buttons

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.

Code: Complete copy-paste code available in this gist.

Baal answered 27/5, 2021 at 0:58 Comment(3)
Broooooo. thank you soooo much bruh!!!Bizet
i used bool darkModeOn(context) => Theme.of(context).brightness == Brightness.dark; to find the app theme if anyone like me also searching for thisBizet
thx very nice, helped a lotSegregationist
C
72

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 enter image description here

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.

ThemeManager.dart

import 'package:flutter/material.dart';
import '../services/storage_manager.dart';

class ThemeNotifier with ChangeNotifier {
  final darkTheme = ThemeData(
    primarySwatch: Colors.grey,
    primaryColor: Colors.black,
    brightness: Brightness.dark,
    backgroundColor: const Color(0xFF212121),
    accentColor: Colors.white,
    accentIconTheme: IconThemeData(color: Colors.black),
    dividerColor: Colors.black12,
  );

  final lightTheme = ThemeData(
    primarySwatch: Colors.grey,
    primaryColor: Colors.white,
    brightness: Brightness.light,
    backgroundColor: const Color(0xFFE5E5E5),
    accentColor: Colors.black,
    accentIconTheme: IconThemeData(color: Colors.white),
    dividerColor: Colors.white54,
  );

  ThemeData _themeData;
  ThemeData getTheme() => _themeData;

  ThemeNotifier() {
    StorageManager.readData('themeMode').then((value) {
      print('value read from storage: ' + value.toString());
      var themeMode = value ?? 'light';
      if (themeMode == 'light') {
        _themeData = lightTheme;
      } else {
        print('setting dark theme');
        _themeData = darkTheme;
      }
      notifyListeners();
    });
  }

  void setDarkMode() async {
    _themeData = darkTheme;
    StorageManager.saveData('themeMode', 'dark');
    notifyListeners();
  }

  void setLightMode() async {
    _themeData = lightTheme;
    StorageManager.saveData('themeMode', 'light');
    notifyListeners();
  }
}

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.

Main.dart

void main() {
  return runApp(ChangeNotifierProvider<ThemeNotifier>(
    create: (_) => new ThemeNotifier(),
    child: MyApp(),
  ));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<ThemeNotifier>(
      builder: (context, theme, _) => MaterialApp(
        theme: theme.getTheme(),
        home: Scaffold(
          appBar: AppBar(
            title: Text('Hybrid Theme'),
          ),
          body: Row(
            children: [
              Container(
                child: FlatButton(
                  onPressed: () => {
                    print('Set Light Theme'),
                    theme.setLightMode(),
                  },
                  child: Text('Set Light Theme'),
                ),
              ),
              Container(
                child: FlatButton(
                  onPressed: () => {
                    print('Set Dark theme'),
                    theme.setDarkMode(),
                  },
                  child: Text('Set Dark theme'),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Here is the link to github repository.

Conlan answered 18/9, 2020 at 11:12 Comment(6)
This render slow if you are setting modes in somewhere down the widget hierarchy and not the root widget.Pathy
Normally apps have dark mode settings in-app settings. Do we need to use the MaterialApp widget there too? Let's say I don't want to change it from the home screen. How should I place the notifiers?Chichihaerh
No you don't need a material widget where you need to change the theme. You can use consumer any where in the code and get the theme object and then by using that object you can easily switch theme. Here is a link to a sample app that does what you are looking for github.com/mateenkiani/Inventory_management/blob/master/lib/src/…Conlan
I'm new to Flutter and am trying to implement what you have here using Flutter 3 and Dart 2.17. I'm getting a build error in ThemeManager.dart: Error: Field '_themeData' should be initialized because its type 'ThemeData' doesn't allow null. It seems the getTheme() method on the next line is the initializer, but it doesn't like the uninitialized property above it. Any ideas?Shovel
Hi! I get an error Non-nullable instance field '_themeData' must be initialized. Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late' in ThemeNotifier() function in ThemeManager.dart file. If I put late before ThemeData _themeData; I get an error LateError (LateInitializationError: Field '_themeData@31353943' has not been initialized.). What is happening???!! 🤔Kraut
Hi, I got this error: ( Non-nullable instance field '_themeData' must be initialized., and 'accentColor' is deprecated and shouldn't be used. Use colorScheme.secondary instead. For more information, consult the migration guide at flutter.dev/docs/release/breaking-changes/…. This feature was deprecated after v2.3.0-0.1.pre..)Morava
Z
24

Letting the system handle themes:

runApp(
  MaterialApp(
    theme: ThemeData.light(), // Provide light theme
    darkTheme: ThemeData.dark(), // Provide dark theme
    home: HomePage(),
  ),
);

Handling the themes yourself:

enter image description here

Use provider to set the theme programmatically. Full code:

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<ThemeModel>(
      create: (_) => ThemeModel(),
      child: Consumer<ThemeModel>(
        builder: (_, model, __) {
          return MaterialApp(
            theme: ThemeData.light(), // Provide light theme.
            darkTheme: ThemeData.dark(), // Provide dark theme.
            themeMode: model.mode, // Decides which theme to show. 
            home: Scaffold(
              appBar: AppBar(title: Text('Light/Dark Theme')),
              body: ElevatedButton(
                onPressed: () => model.toggleMode(),
                child: Text('Toggle Theme'),
              ),
            ),
          );
        },
      ),
    );
  }
}

class ThemeModel with ChangeNotifier {
  ThemeMode _mode;
  ThemeMode get mode => _mode;
  ThemeModel({ThemeMode mode = ThemeMode.light}) : _mode = mode;

  void toggleMode() {
    _mode = _mode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;
    notifyListeners();
  }
}

Answering OP questions:

  • Current theme can be found using:

    bool isDarkMode = MediaQuery.of(context).platformBrightness == Brightness.dark;
    

    or

    bool isDarkMode = SchedulerBinding.instance.window.platformBrightness == Brightness.dark;
    
  • 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.

Zoography answered 3/10, 2020 at 15:35 Comment(2)
How to do this with CupertinoApp?Smallminded
But this does not persist.Flashing
S
14
MaterialApp(
  theme: ThemeData.light(),
  /// theme: ThemeData.dark(),
)

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:

Widget build(BuildContext context) {
  var themeData = Theme.of(context).copyWith(scaffoldBackgroundColor: darkBlue)

  return Scaffold(
    backgroundColor = themeData.scaffoldBackgroundColor,
  );
}

But to handle the ThemeData state (changing its value), you need to implement proper state management.

Spanner answered 15/2, 2020 at 10:19 Comment(1)
That's one example of state management in flutter, there are provider package and flutter_bloc as well. To answer how to do it is very wide question, maybe you can find tutorials about itSpanner
Z
12

Screenshot:

enter image description here


If you don't want to use any third party packages or plugins, you can use ValueListenableBuilder which comes out of the box with Flutter.

Full code:

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  final ValueNotifier<ThemeMode> _notifier = ValueNotifier(ThemeMode.light);

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<ThemeMode>(
      valueListenable: _notifier,
      builder: (_, mode, __) {
        return MaterialApp(
          theme: ThemeData.light(),
          darkTheme: ThemeData.dark(),
          themeMode: mode, // Decides which theme to show, light or dark.
          home: Scaffold(
            body: Center(
              child: ElevatedButton(
                onPressed: () => _notifier.value = mode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light,
                child: Text('Toggle Theme'),
              ),
            ),
          ),
        );
      },
    );
  }
}
Zoography answered 3/10, 2020 at 16:9 Comment(0)
B
11
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.light(), // Provide light theme.
      darkTheme: ThemeData.dark(), // Provide dark theme.
      themeMode: ThemeMode.system,
      home: Scaffold(
        appBar: AppBar(),
        body: Container(),
      ),
    );
  }
}
Burchfield answered 11/2, 2021 at 3:31 Comment(0)
B
7

Here is a code
In this code you i've made custom theme according to my requirements you can change it!!

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Theme',
      debugShowCheckedModeBanner: false,

      /* light theme settings */
      theme: ThemeData(
        primarySwatch: Colors.blue,
        primaryColor: Colors.white,
        brightness: Brightness.light,
        accentColor: Colors.black,
        accentIconTheme: IconThemeData(color: Colors.white),
        dividerColor: Colors.white54,
        scaffoldBackgroundColor: Colors.white,

      ),

      /* Dark theme settings */
      darkTheme: ThemeData(
        primarySwatch: Colors.blue,
        primaryColor: Colors.black,
        brightness: Brightness.dark,
        accentColor: Colors.white,
        accentIconTheme: IconThemeData(color: Colors.black),
        dividerColor: Colors.black12,
        scaffoldBackgroundColor: Color(0xFF131313),

      ),

      /* ThemeMode.system to follow system theme,
         ThemeMode.light for light theme,
         ThemeMode.dark for dark theme */
      themeMode: ThemeMode.system,

      home: MyHomePage(),
    );
  }
}
Bluetongue answered 29/4, 2021 at 20:11 Comment(1)
that's most simple oneInsulting
B
5
  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.
Burchfield answered 11/2, 2021 at 3:36 Comment(0)
I
5

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.

Heres the dartpad demo

See this for changing theme with Telegram like transition

enter image description here

Complete code sample

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

final darkNotifier = ValueNotifier<bool>(false);

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<bool>(
        valueListenable: darkNotifier,
        builder: (BuildContext context, bool isDark, Widget? child) {
          return MaterialApp(
            title: 'Flutter Demo',
            themeMode: isDark ? ThemeMode.dark : ThemeMode.light,
            theme: ThemeData(primaryColor: Colors.blue),
            darkTheme: ThemeData.dark(),
            home: MyHomePage(
              title: 'Homepage',
            ),
          );
        });
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  void dispose() {
    // TODO: implement dispose
    darkNotifier.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    bool isDark = darkNotifier.value;
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              _darkNotifier.value ? 'DarkMode' : 'LightMode',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          isDark = !isDark;
          darkNotifier.value = isDark;
        },
        tooltip: 'Increment',
        child: Icon(isDark ? Icons.wb_sunny_outlined : Icons.bubble_chart),
      ),
    );
  }
}

Irrelevance answered 20/7, 2021 at 9:0 Comment(4)
hey, MyHomePage class is in a new dart file. How can I Implement this in that case? @Mahesh JamdadeJiggerypokery
That shouldn't be a problem you can calldarkNotifier.value from anywhere. I implemented this approach in this app vocabhub.web.app/# and it works pretty well. And the code is open sourced to have a lookIrrelevance
Will whole MaterialApp be rebuilt when you change the theme?Scheld
yes, the entire tree needs to rebuild, sinceValueListenableBuilder is at the root of the tree.Irrelevance
R
5

For Customize dark theme

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

@override
Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      darkTheme: ThemeData(brightness: Brightness.dark, scaffoldBackgroundColor: Colors.red),
      theme: ThemeData(
        scaffoldBackgroundColor: Colors.amber,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }

Full code

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      darkTheme: ThemeData(brightness: Brightness.dark, scaffoldBackgroundColor: Colors.red),
      // themeMode: ThemeMode.dark,
      theme: ThemeData(
        scaffoldBackgroundColor: Colors.amber,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    // print("brightness ${ColorScheme.}")
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'increment',
              style: Theme.of(context).textTheme.headline4,
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}
Riches answered 28/9, 2021 at 12:44 Comment(0)
U
5

Below is the simple example for changing the theme light to darkenter image description here

    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    import 'package:theme_mode_switch/notify.dart';
    
    void main() {
      runApp(
          ChangeNotifierProvider(create: (context) => DarkMode(), child: MyApp()));
    }
    
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        final thmode = Provider.of<DarkMode>(context); ///accessing the variable of provider class
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          title: 'Dark Mode',
          theme: ThemeData(
            ///here the value of darmode var is updationg by switching
            brightness: thmode.darkMode ? Brightness.dark : Brightness.light,
            primarySwatch: Colors.blue,
            visualDensity: VisualDensity.adaptivePlatformDensity,
          ),
          home: MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {      
        final thmode = Provider.of<DarkMode>(context);
        return Scaffold(
            appBar: AppBar(
              title: Text('Dark Mode'),
            ),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Text(thmode.darkMode ? 'Dark' :'Light'),
                  CupertinoSwitch(
                    value: thmode.darkMode,
                    onChanged: (bool val) {
                      thmode.changemode();
                    },
                  ),
                ],
              ),
            ));
      }
    }


class DarkMode with ChangeNotifier {
  bool darkMode = true; ///by default it is true
  ///made a method which will execute while switching
  changemode() {
    darkMode = !darkMode;
    notifyListeners(); ///notify the value or update the widget value
  }
}
Urfa answered 8/11, 2021 at 7:43 Comment(0)
E
3

You can also use the available plugin day_night_theme_flutter

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?

  1. Add the latest version of the package in your pubspec.yaml
  2. Wrap the MaterialApp with DayNightTheme Widget.
Elemental answered 19/1, 2021 at 9:32 Comment(0)
B
3

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.

Example gif

Blackmore answered 28/10, 2021 at 8:20 Comment(0)
S
3

Multiple Flutter themes example (Light and Dark theme)

  1. Add provider in .yaml file

  2. Declare runApp method like this

runApp(ChangeNotifierProvider( create: (context) => ThemeState(), child: MyApp(), ));

  1. Create ThemeState class and extend it with ChangeNotitifer
    import 'package:flutter/material.dart';

    enum ThemeType { DARK, LIGHT }
    
    class ThemeState extends ChangeNotifier {
      bool _isDarkTheme = false;
    
      ThemeState() {
        getTheme().then((type) {
          _isDarkTheme = type == ThemeType.DARK;
          notifyListeners();
        });
      }
      ThemeType get theme => _isDarkTheme ? ThemeType.DARK : ThemeType.LIGHT;
      set theme(ThemeType type) => setTheme(type);
    
      void setTheme(ThemeType type) async {
        _isDarkTheme = type == ThemeType.DARK;
        notifyListeners();
      }
    
      Future<ThemeType> getTheme() async {
        return _isDarkTheme ? ThemeType.DARK : ThemeType.LIGHT;
      }
    }
  1. 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'),
                    ),
                  ),
Serotine answered 5/4, 2022 at 10:21 Comment(0)
D
3

This is the current up-to-date method apparently:

    return MaterialApp(
      theme: ThemeData(
        colorSchemeSeed: Colors.green,
        useMaterial3: true,
      ),
      darkTheme: ThemeData(
        colorSchemeSeed: Colors.green,
        brightness: Brightness.dark,
        useMaterial3: true,
      ),

You don't need to set themeMode - the default is to use the system theme which is what you should want.

If you use colorScheme: ColorScheme.fromSeed(seedColor: Colors.green) in the darkTheme you will get an assertion failure because the colorScheme.brightness has to match darkTheme.brightness. You can "fix" it like this: colorScheme: ColorScheme.fromSeed(seedColor: Colors.green).copyWith(brightness: Brightness.dark) but that won't actually change the colours - your dark theme will look light.

You can use ThemeData.dark() except then it won't be a Material 3 theme.

I recommend looking at this example app. Web demo here. It can change theme animated at runtime.

Dennison answered 14/8, 2023 at 14:56 Comment(0)
D
2

ClojureDart

For those of us writing Flutter with ClojureDart:

You'll need to store the setting somewhere. You probably already have a global state atom -- that's a good place to set the default value:

(defonce !state
  (atom {:theme m/ThemeMode.light}))

(Persisting state is outside the scope of this answer.)

You'll need a way to change the setting. I implemented it as three ListTiles. Here are the relevant parts of the one for selecting System:

(f/widget
 m/Card
 (m/InkWell .onTap (fn []
                     (swap! !state assoc :theme m/ThemeMode.system)))
 (m/ListTile ...))

Lastly, we need to tell Flutter to use the value of :theme in our state atom. For that we need a :watch directive. We capture any changes to the desired key in !state, flowing the current value into the theme binding:

(defn main []
  (f/run
   :watch [{:keys [theme]} !state]
   (m/MaterialApp .theme     theme/light
                  .darkTheme theme/dark
                  .themeMode theme)
   .home (home-screen)))

(Themes are kept in a separate namespace which we have aliased as theme.)

Disrelish answered 30/4 at 15:24 Comment(0)
G
1

much easier than you think with get package

return GetMaterialApp(
  themeMode: lightOrDark?ThemeMode.light:ThemeMode.dark,
  ...
);
Godchild answered 17/1, 2022 at 7:47 Comment(0)
K
1

if you want Theme to your whole app with button toggled, simply you can use provider package, and make your main root listen to changes to mode

Make a class with changenotifier mixin that defines the your theme data with two instances of your dark and light mode

class LightDarkMode with ChangeNotifier {
var currentMode = false;

void switchMode(bool mode) {
  currentMode = mode;
  notifyListeners();
}

final light = const ColorScheme(
/*Light Color OF Icons*/ primary: Color.fromARGB(255, 126, 12, 12),
/*Dark Color OF Icon/Buttons*/ secondary: Color.fromARGB(255, 219, 203, 217),
onPrimary: Color.fromARGB(255, 163, 163, 163),
onSecondary: Colors.amber,
/*BACKGROUND OF APP*/ background: Color.fromARGB(255, 211, 204, 198),
/*Appbar,Big Widgets etc*/ onBackground: Color.fromARGB(255, 0, 0, 0),
surface: Color.fromARGB(255, 165, 158, 158),
onSurface: Color.fromARGB(255, 0, 0, 0),
error: Color.fromARGB(255, 233, 56, 44),
onError: Color.fromARGB(255, 124, 8, 0),
brightness: Brightness.light,
);

final dark = const ColorScheme(
/*Light Color OF Icons*/ primary: Color.fromARGB(255, 9, 75, 9),
/*Dark Color OF Icon/Buttons*/ secondary: Color.fromARGB(255, 23, 159, 168),
onPrimary: Colors.white,
onSecondary: Colors.amber,
/*BACKGROUND OF APP*/ background: Color.fromARGB(255, 0, 0, 0),
/*Appbar,Big Widgets etc*/ onBackground: Color.fromARGB(255, 255, 255, 255),
surface: Color.fromARGB(255, 231, 214, 199),
onSurface: Color.fromARGB(255, 0, 0, 0),
error: Color.fromARGB(255, 233, 56, 44),
onError: Color.fromARGB(255, 124, 8, 0),
brightness: Brightness.dark,
);
}

Now create an istance of your data class through main, and implement a listener to that instance, anywhere in your app like this:

        ChangeNotifierProvider(
      create: (context) => LightDarkMode(),
    )
  ],
  builder: (context, child) => Consumer<LightDarkMode>(
    builder: (context, value, child) => MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',darkTheme: ThemeData() ,
      theme: ThemeData(colorScheme: value.currentMode ? value.dark : value.light),
      home: const Calculator(),
    ),


Widget build(BuildContext context) {
final device = MediaQuery.of(context).size;
return Scaffold(
  appBar: AppBar(
      centerTitle: true,
      title: const Text(
        'Calculator',
        style: TextStyle(color: Colors.white, fontSize: 24),
      ),
      backgroundColor: Theme.of(context).colorScheme.primary,
      actions: [
        Consumer<LightDarkMode>(
          builder: (context, value, child) => ToggleButtons(
            borderWidth: 0,
            isSelected: selected,
            onPressed: (index) {
              selected[0] = !selected[0];
              value.switchMode(selected[0]);
            },
            children: [Icon(value.currentMode == true ? Icons.mode_night : Icons.sunny, color: value.currentMode == true ? Colors.amber : Colors.white)],
          ),
        )
      ]),
Kibitka answered 6/3, 2023 at 9:29 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Sphalerite

© 2022 - 2024 — McMap. All rights reserved.