Implementing Dark Mode in Flutter using Provider

Dark mode has become a popular feature in many mobile and web applications. It provides a visually appealing alternative to the default light mode, reduces eye strain, and saves battery life on devices with OLED screens. In this tutorial, we will explore how to implement dark mode in a Flutter app using the Provider package for state management.

Dark mode - Provider


Folder Structure

Before we dive into the code, let's set up the folder structure for our Flutter project. We will follow the standard Flutter project structure, which looks like this:

- lib/
  - main.dart
  - models/
    - dark_mode_provider.dart
  - themes/
    - theme_config.dart
  - screens/
    - dark_mode_page.dart

In the `models` folder, we will create a file called `dark_mode_provider.dart` to define our `DarkModeProvider` class. This class will manage the state of the dark mode feature. The `themes` folder will contain a file called `theme_config.dart`, where we define our light and dark themes. Finally, in the `screens` folder, we will create a file called `dark_mode_page.dart`, which will display the UI for toggling between light and dark modes.

Code Explanation

Now, let's dive into the code. We start by importing the necessary packages:

Next, we define the main entry point for our Flutter app:

Inside the `MyApp` widget, we define a `BuildContext` variable called `currentContext` that will be used later to access the current context:

We also define a `textTheme` variable to apply the display color from the current context's theme:

late BuildContext currentContext;

final textTheme = Theme.of(currentContext)
    .textTheme
    .apply(displayColor: Theme.of(currentContext).colorScheme.onSurface);

Note that we use the `late` keyword to indicate that the variable will be initialized later.

Moving on to the `DarkModeProvider` class, which extends `ChangeNotifier`, we define it in the `dark_mode_provider.dart` file:

class DarkModeProvider with ChangeNotifier {
  bool _isDarkMode = false;

  bool get isDarkMode => _isDarkMode;

  set isDarkMode(bool value) {
    _isDarkMode = value;
    notifyListeners();
  }
}

In this class, we have a private `_isDarkMode` variable that represents the current state of the dark mode. We provide a getter `isDarkMode` to access this value and a setter `isDarkMode` to update it. When the value changes, we call `notifyListeners()` to notify any listeners (such as UI elements) that depend on this state.

Back in the `MyApp` widget, we wrap it with a `MultiProvider` widget to provide the `DarkModeProvider` to its descendants:

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

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
  }

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider<DarkModeProvider>(
          create: (_) => DarkModeProvider(),
        ),
      ],
      child: Builder(
        builder: (context) {
          // Initialize the variables after the MaterialApp widget
          currentContext = context;
          textTheme.apply(displayColor: ThemeConfig.getTextColor(context));

          return Consumer<DarkModeProvider>(builder: (context, appMode, _) {
            return MaterialApp(
              debugShowCheckedModeBanner: false,
              title: 'API Call',
              theme: appMode.isDarkMode
                  ? ThemeConfig.darkTheme
                  : ThemeConfig.lightTheme,
              home: darkModePage(),
              routes: const {
                // Navigating the page using Named Routes
                //ProfileDetailedScreen.routeName: (ctx) => ProfileDetailedScreen(),
              },
            );
          });
        },
      ),
    );
  }
}

Inside the `darkModePage` widget, which is a stateless widget, we build the UI for the dark mode page:

class darkModePage extends StatelessWidget {
  const darkModePage({Key? key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Dark mode - Provider'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                context.watch<DarkModeProvider>().isDarkMode
                    ? "Enable light mode"
                    : "Enable dark mode",
              ),
              Switch(
                value: context.watch<DarkModeProvider>().isDarkMode,
                onChanged: (value) {
                  context.read<DarkModeProvider>().isDarkMode = value;
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

In this widget, we use the `context.watch` method to listen to changes in the `DarkModeProvider` and update the UI accordingly. The `Switch` widget allows the user to toggle between light and dark modes by modifying the `isDarkMode` property of the `DarkModeProvider`.

Finally, let's take a look at the `ThemeConfig` class. This class contains the configurations for our light and dark themes. In the `theme_config.dart` file, define the `ThemeConfig` class as follows:

class ThemeConfig {
  static ThemeData lightTheme = ThemeData.light().copyWith(
    appBarTheme: const AppBarTheme(
      backgroundColor: Colors.orange,
      titleTextStyle: TextStyle(color: Colors.black),
    ),
    cardColor: Colors.white,
    // Add more theme configurations for other elements
  );

  static ThemeData darkTheme = ThemeData.dark().copyWith(
    appBarTheme: const AppBarTheme(
      backgroundColor: Colors.orangeAccent,
      titleTextStyle: TextStyle(color: Colors.white),
    ),
    cardColor: Colors.grey[900],
    // Add more theme configurations for other elements
  );

  static Color getTextColor(BuildContext context) {
    return Theme.of(context).brightness == Brightness.dark
        ? Colors.white
        : Colors.black;
  }
}

In this class, we define `lightTheme` and `darkTheme` properties, which are instances of `ThemeData` that customize various aspects of the app's appearance. The `getTextColor` method returns the appropriate text color based on the current theme's brightness.

Conclusion

In this tutorial, we learned how to implement dark mode in a Flutter app using the Provider package for state management. We explored the folder structure of the project, explained the code in detail, and saw how to create a dark mode page with a toggle switch. Dark mode not only enhances the user experience but also adds a modern touch to your Flutter applications. Happy coding!

Comments

Popular posts from this blog

Error Handling in Flutter - Gradle issue

How to Make a Dynamic and Trending ListView with Flutter Widgets?

Understanding API integration with Getx State management