Flutter & MobX: Dark/Light Mode Switcher
Tags: flutter
Published: 2020-04-27 15:33:04

---
title: Flutter & MobX: Dark/Light Mode Switcher
published: true
date: 2020-04-16 07:50:37 UTC
tags: flutter
canonical_url: 
---

 ![Flutter & MobX: Dark/Light Mode Switcher](https://developer.school/content/images/2020/04/mobxDark.png)

In this article we're going to create a small application that uses  `Flutter MobX` and `Provider` to toggle between two `ThemeData` states. We'll look at the following key concepts:

1. How to change between dark and light mode.
2. How to create a `ThemeStore` which will be responsible for firing action(s) and managing observable values.
3. How to inject the `ThemeStore` into our Widget tree
4. How to show a `SnackBar` based on a `reaction` between when the application is in `dark` or `light` mode.

### Project Setup

Let's go ahead and create a new Flutter project and install our required dependencies:

```
# New Flutter project
$ flutter create mobx_theme

# Open in VS Code
$ cd mobx_theme && code .
```

Once we've got the project open, we can update `pubspec.yaml` with the following `dependencies` and `dev_dependencies`:

```
dependencies:
  flutter:
    sdk: flutter

  provider: ^4.0.5
  mobx: ^1.1.1
  flutter_mobx: ^1.1.0
  shared_preferences: ^0.5.6+3

dev_dependencies:
  flutter_test:
    sdk: flutter
  build_runner:
  mobx_codegen: ^1.0.3

```

### Switching Between Dark and Light Mode

Before implementing any state management solutions, _how do we switch between dark and light mode?_ Flutter makes it easy with changes to `brightness` within a selected `ThemeData`.

Here's an example of a `lightTheme` and `darkTheme` respectively:

```
  ThemeData get lightTheme => ThemeData(
        primarySwatch: Colors.teal,
        accentColor: Colors.deepPurpleAccent,
        brightness: Brightness.light,
        scaffoldBackgroundColor: Color(0xFFecf0f1),
        visualDensity: VisualDensity.adaptivePlatformDensity,
      );

  ThemeData get darkTheme => ThemeData(
        primarySwatch: Colors.teal,
        accentColor: Colors.tealAccent,
        brightness: Brightness.dark,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      );
```

This only represents a small amount of the overall configurable theming options, and it's encouraged that you come up with your own theme(s) here. :)

### Theme Repository

We'll start off by creating an `IThemeRepository` interface:

```
/// lib/domain/theme/interfaces/i_theme_repository.dart
import 'package:flutter/material.dart';

abstract class IThemeRepository {
  Future<String> getThemeKey();
  Future<void> setThemeKey(Brightness brightness);
}

```

We'll then create a `ThemeKey` class to hold our constant key(s) related to Theme:

```
class ThemeKey {
  static const String THEME = "theme";
}

```

In the future, you may want to abstract this functionality out into a Preferences repository as seen in my other article: [https://developer.school/how-to-save-data-to-localstorage-shared-prefs-in-flutter-flutter-web/](https://dev.to/paulhalliday/how-to-save-data-to-localstorage-and-sharedpreferences-in-flutter-6el-temp-slug-79980)

Our implementation of this can be seen here:

```
/// lib/infrastructure/theme/datasources/theme_repository.dart
import 'dart:ui';

import 'package:mobx_theme/domain/theme/constants/theme_keys.dart';
import 'package:mobx_theme/domain/theme/interfaces/i_theme_repository.dart';
import 'package:shared_preferences/shared_preferences.dart';

class ThemeRepository implements IThemeRepository {
  @override
  Future<void> setThemeKey(Brightness brightness) async {
    (await SharedPreferences.getInstance()).setString(
      ThemeKey.THEME,
      brightness == Brightness.light ? "light" : "dark",
    );
  }

  @override
  Future<String> getThemeKey() async {
    return (await SharedPreferences.getInstance()).getString(ThemeKey.THEME);
  }
}

```

Essentially, we're either setting a `SharedPreferences` value of either `light` or `dark` depending on the brightness passed in. We're also able to retrieve this value to set the appropriate theme in the future.

### Theme Service

We've now got the ability to save and retrieve theme keys. We can go ahead and create a `ThemeService` which uses this to return an appropriate theme for our user:

```
/// lib/application/theme/services/theme_service.dart
import 'package:flutter/material.dart';
import 'package:mobx_theme/domain/theme/interfaces/i_theme_repository.dart';

class ThemeService {
  ThemeService(IThemeRepository themeRepository)
      : _themeRepository = themeRepository;

  IThemeRepository _themeRepository;

  ThemeData get lightTheme => ThemeData(
        primarySwatch: Colors.teal,
        accentColor: Colors.deepPurpleAccent,
        brightness: Brightness.light,
        scaffoldBackgroundColor: Color(0xFFecf0f1),
        visualDensity: VisualDensity.adaptivePlatformDensity,
      );

  ThemeData get darkTheme => ThemeData(
        primarySwatch: Colors.teal,
        accentColor: Colors.tealAccent,
        brightness: Brightness.dark,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      );

  Future<ThemeData> getTheme() async {
    final String themeKey = await _themeRepository.getThemeKey();

    if (themeKey == null) {
      await _themeRepository.setThemeKey(lightTheme.brightness);

      return lightTheme;
    } else {
      return themeKey == "light" ? lightTheme : darkTheme;
    }
  }

  Future<ThemeData> toggleTheme(ThemeData theme) async {
    if (theme == lightTheme) {
      theme = darkTheme;
    } else {
      theme = lightTheme;
    }

    await _themeRepository.setThemeKey(theme.brightness);
    return theme;
  }
}
```

Next up, we'll use this service inside of our `ThemeStore` to handle reactivity on these value(s):

### Theme Store

The Store will be responsible for exposing our current `theme` to our `MaterialApp`. We've also created a `computed` getter for `isDark` which we can use at any point to determine whether we're currently in `dark` mode.

```
/// lib/application/theme/store/theme_store.dart
import 'package:flutter/material.dart';
import 'package:mobx/mobx.dart';
import 'package:mobx_theme/application/theme/services/theme_service.dart';

part 'theme_store.g.dart';

class ThemeStore extends _ThemeStore with _$ThemeStore {
  ThemeStore(ThemeService themeService) : super(themeService);
}

abstract class _ThemeStore with Store {
  _ThemeStore(this._themeService);

  final ThemeService _themeService;

  @computed
  bool get isDark => theme.brightness == Brightness.dark;

  @observable
  ThemeData theme;

  @action
  Future<void> getTheme() async {
    theme = _themeService.lightTheme;
    theme = await _themeService.getTheme();
  }

  @action
  Future<void> toggleTheme() async {
    theme = await _themeService.toggleTheme(theme);
  }
}

```

As MobX requires some code generation, we'll need to run the `build_runner` with `mobx_codegen`. Run the following in your terminal:

```
$ flutter pub run build_runner build 
```

### Providing our Store

We're now able to switch brightness, _however,_ we need to provide our `MaterialApp` with the current value of our `theme`. We can do that by using `Provider`:

```
///lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx_theme/application/theme/services/theme_service.dart';
import 'package:mobx_theme/application/theme/store/theme_store.dart';
import 'package:mobx_theme/infrastructure/theme/datasources/theme_repository.dart';
import 'package:mobx_theme/presentation/pages/splash_screen.dart';
import 'package:provider/provider.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        Provider<ThemeStore>(
            create: (_) =>
                ThemeStore(ThemeService(ThemeRepository()))..getTheme())
      ],
      child: Consumer<ThemeStore>(
        builder: (_, ThemeStore value, __) => Observer(
          builder: (_) => MaterialApp(
            debugShowCheckedModeBanner: false,
            title: 'MobX Theme Switcher',
            theme: value.theme,
            home: SplashPage(),
          ),
        ),
      ),
    );
  }
}

```

Here we're providing the `ThemeStore` into our Widget tree and immediately using `Consumer` to get the current `theme.value`.

As we've created `theme` as an `@observable` using MobX, any changes to `theme` will be reactive as we've wrapped our `MaterialApp` in an `Observer` widget.

### Creating our SplashPage

As we're strictly dealing with the ability to change between dark and light mode in this article, I've created one page - `SplashPage` which has a simple title/subtitle. We're able to switch between `dark` and `light` mode by clicking the Floating Action Button:

![Flutter & MobX: Dark/Light Mode Switcher](https://developer.school/content/images/2020/04/DarkLight.png)

Here's how we achieve this:

```
/// lib/presentation/pages/splash_page.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mobx_theme/application/theme/store/theme_store.dart';
import 'package:provider/provider.dart';

class SplashPage extends StatefulWidget {
  @override
  _SplashPageState createState() => _SplashPageState();
}

class _SplashPageState extends State<SplashPage> {
  ThemeStore themeStore;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();

    themeStore ??= Provider.of<ThemeStore>(context);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: themeStore.toggleTheme,
        child: themeStore.isDark
            ? Icon(Icons.brightness_high)
            : Icon(Icons.brightness_2),
      ),
      body: buildSplash(context),
    );
  }

  Widget buildSplash(BuildContext context) {
    return AnnotatedRegion<SystemUiOverlayStyle>(
      value: themeStore.isDark
          ? SystemUiOverlayStyle.light
          : SystemUiOverlayStyle.dark,
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            SizedBox(
              height: 20,
            ),
            Text(
              "Foodie",
              style: TextStyle(fontSize: 26),
            ),
            SizedBox(
              height: 4,
            ),
            Text(
              "The best way to track your nutrition.",
              style: TextStyle(fontSize: 16),
            ),
          ],
        ),
      ),
    );
  }
}

```

There isn't too much out of the ordinary here. We're getting access to our `ThemeStore` in `didChangeDependencies` and using the `themeStore.toggleTheme` action when our FAB is pressed.

### Using MobX Reactions

I can't think of many use cases for this, but what if you wanted to show a Snackbar (or another `reaction`) whenever the theme has been changed? Here's an example of what this could look like:

![Flutter & MobX: Dark/Light Mode Switcher](https://developer.school/content/images/2020/04/DarkLight_Reaction.png)

This is made easy with MobX. We have to register our `ReactionDisposer` with the `Observable` that we want to react against:

```
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mobx/mobx.dart';
import 'package:mobx_theme/application/theme/store/theme_store.dart';
import 'package:provider/provider.dart';

class SplashPage extends StatefulWidget {
  @override
  _SplashPageState createState() => _SplashPageState();
}

class _SplashPageState extends State<SplashPage> {
  ThemeStore themeStore;

  GlobalKey<ScaffoldState> _scaffoldKey;
  List<ReactionDisposer> _disposers;

  @override
  void initState() {
    super.initState();
    _scaffoldKey = GlobalKey<ScaffoldState>();
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();

    themeStore ??= Provider.of<ThemeStore>(context);
    _disposers ??= [
      reaction((fn) => themeStore.isDark, (isDark) {
        _scaffoldKey.currentState?.removeCurrentSnackBar();

        if (isDark) {
          _scaffoldKey.currentState.showSnackBar(SnackBar(
            content: Text("Hello, Dark!"),
          ));
        } else {
          _scaffoldKey.currentState.showSnackBar(SnackBar(
            content: Text("Hello, Light!"),
          ));
        }
      })
    ];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      floatingActionButton: FloatingActionButton(
        onPressed: themeStore.toggleTheme,
        child: themeStore.isDark
            ? Icon(Icons.brightness_high)
            : Icon(Icons.brightness_2),
      ),
      body: buildSplash(context),
    );
  }

  Widget buildSplash(BuildContext context) {
    return AnnotatedRegion<SystemUiOverlayStyle>(
      value: themeStore.isDark
          ? SystemUiOverlayStyle.light
          : SystemUiOverlayStyle.dark,
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            SizedBox(
              height: 20,
            ),
            Text(
              "Foodie",
              style: TextStyle(fontSize: 26),
            ),
            SizedBox(
              height: 4,
            ),
            Text(
              "The best way to track your nutrition.",
              style: TextStyle(fontSize: 16),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _disposers.forEach((disposer) => disposer());
    super.dispose();
  }
}

```

Whenever we register a `reaction` it returns a `ReactionDisposer<T>` which can be called to `dispose` of the `reaction`. We're only registering one `reaction` here, but for simplicity sake we've used a `List` to make it more flexible.

Our application is now able to react to `isDark` changes.

### Summary

In this article we looked at one potential way to implement dynamic theming with MobX. I'd love to hear your thoughts as to how this could be improved and/or any future libraries you'd like me to investigate.

Code for this article: [https://github.com/PaulHalliday/mobx\_theme](https://github.com/PaulHalliday/mobx_theme)