DEV Community

Erick Murai
Erick Murai

Posted on

Implementing persistent dark mode in flutter

A modern staple of present day UI/UX design is the implementation of functionality that enables switching between dark mode and light mode within apps. Luckily for us flutter developers, we can implement this too in DART. In our app, we shall use the toggle button and not rely on the phone's theme. We also want to persist our theme thus we shall use the provider package for app-wide theming and shared preferences to persist our theme.
The source code for the project can be found here
Firstly, we shall create a simple UI to demonstrate this. I've gone for a fintech sample app frontend for the purposes of demonstration

import 'package:darkmode/home/homepage.dart';

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


void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await SystemChrome.setPreferredOrientations([
    DeviceOrientation.portraitUp,
    DeviceOrientation.portraitDown,
  ]);
  runApp(const MyApp());
}

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
          return MaterialApp(
            title: 'Flutter Demo',
            home: HomePage(),
          );

  }
}
Enter fullscreen mode Exit fullscreen mode

Secondly, we shall create a class in which we can define light theme and dark theme using ThemeData that enables us define the visual theme. We shall also pass various properties within the ThemeData such as iconTheme, appBarTheme and textTheme.

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
ThemeData lightThemeData(BuildContext context) {
  return ThemeData.light().copyWith(
      visualDensity: VisualDensity.adaptivePlatformDensity,
      scaffoldBackgroundColor: Colors.white,
      appBarTheme: appBarTheme,
      iconTheme:const IconThemeData(color: Colors.black),

     textTheme: Theme.of(context).textTheme.apply(bodyColor: Colors.black));
}

ThemeData darkThemeData(BuildContext context) {
  return ThemeData.dark().copyWith(
      visualDensity: VisualDensity.adaptivePlatformDensity,
      scaffoldBackgroundColor:const Color(0xFF1D1D35),
      appBarTheme: appBarTheme,
      iconTheme:const IconThemeData(color: Colors.white),

      textTheme: Theme.of(context).textTheme.apply(bodyColor: Colors.white));
}

const appBarTheme = AppBarTheme(centerTitle: false, elevation: 0);
Enter fullscreen mode Exit fullscreen mode

Thirdly, we shall add the Provider package and shared preferences package .We shall use the provider package to enable the switching of the app-wide theme and the shared preferences package to store the persisted theme details thus when we exit the app and open it up again, the theme shall persist.

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:shared_preferences/shared_preferences.dart';


class ThemeProvider extends ChangeNotifier {
 final String key = "theme";
 late SharedPreferences _prefs;
 late bool _darkTheme;

  bool get  darkTheme => _darkTheme;

  ThemeProvider() {
    _darkTheme = true;
    _loadFromPrefs();
  }

  toggleTheme() {
    _darkTheme = !_darkTheme;
    _saveToPrefs();
    notifyListeners();
  }

  _initPrefs() async {
    // if(_prefs == null)
      _prefs = await SharedPreferences.getInstance();
  }

  _loadFromPrefs() async {
    await _initPrefs();
    _darkTheme = _prefs.getBool(key) ?? true;
    notifyListeners();
  }

  _saveToPrefs()async {
    await _initPrefs();
    _prefs.setBool(key, _darkTheme);
  }
}
Enter fullscreen mode Exit fullscreen mode

With this done we shall go back to the Materialapp in main.dart and wrap the MaterialApp widget with a ChangeNotifierProvider widget that listens to the ChangeNotifier widget in its descendants and rebuilds dependents whenever ChangeNotifier.notifyListeners is called. We also wrap the MaterialApp widget with the Consumer widget that calls provider.of for us and listens to changes in our theme thus rebuilds it whenever necessary.

import 'package:darkmode/home/homepage.dart';
import 'package:darkmode/provider/theme_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await SystemChrome.setPreferredOrientations([
    DeviceOrientation.portraitUp,
    DeviceOrientation.portraitDown,
  ]);
  runApp(const MyApp());
}

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
        create: (_) => ThemeProvider(),
        child: Consumer<ThemeProvider>(
            builder: (context, ThemeProvider notifier, child) {
          return MaterialApp(
            title: 'Flutter Demo',
            theme: notifier.darkTheme
                ? darkThemeData(context)
                : lightThemeData(context),
            debugShowCheckedModeBanner: false,
            home: HomePage(),
          );
        }));
  }
}
Enter fullscreen mode Exit fullscreen mode

We shall now create a switch button that can toggle between light and dark mode. We shall listen to changes in the ThemeProvider by wrapping our Switch button with the consumer widget.

import 'package:darkmode/provider/theme_provider.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';


class ChangeThemeButtonWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<ThemeProvider>(
      builder: (context, notifier, child) => Switch.adaptive(
        value: notifier.darkTheme,
        onChanged: (val) {
          notifier.toggleTheme();
          //bool value of switch which is true or false
        },
        activeTrackColor: Colors.green[100],
        inactiveTrackColor: Colors.green[300],
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

I created a fintech frontend as an easy way to demonstrate the switch between dark mode and light mode whose source code can be found here
Here is the result:
Theme Video

Thanks for the read up and stay tuned for more articles on flutter development.

Top comments (0)