Changing Theme at run-time - Flutter

Changing Theme at run-time : Flutter

Our Final Goal

Theming is a crucial part of app development, whether it be mobile or web. Some users like to stay on the light side while some not so much.

Welcome to the dark side.

Firstly, the dependencies that we will be using:

  1. flutter_bloc
  2. bloc
  3. shared_preferences

Let’s start off by defining our themes. I will be defining three themes but the number of themes you want is up to your need.

final ThemeData kLightTheme = _buildLightTheme();

ThemeData _buildLightTheme() {
  final ThemeData base = ThemeData.light();
  return base.copyWith(
    primaryColor: Colors.white,
    accentColor: Colors.black,
    canvasColor: Colors.transparent,
    primaryIconTheme: IconThemeData(color: Colors.black),
    textTheme: TextTheme(
      headline: TextStyle(
          fontFamily: 'Sans',
          fontWeight: FontWeight.bold,
          color: Colors.black,
          fontSize: 24),
      body1: TextStyle(
          fontFamily: 'Sans',
          fontWeight: FontWeight.bold,
          color: Colors.black,
          fontSize: 24),
      body2: TextStyle(
          fontFamily: 'Sans',
          fontWeight: FontWeight.bold,
          color: Colors.black,
          fontSize: 18),
    ),
  );
}

final ThemeData kDarkTheme = _buildDarkTheme();

ThemeData _buildDarkTheme() {
  final ThemeData base = ThemeData.dark();
  return base.copyWith(
    primaryColor: Color(0xff323639),
    accentColor: Colors.blue,
    canvasColor: Colors.transparent,
    primaryIconTheme: IconThemeData(color: Colors.black),
    textTheme: TextTheme(
      headline: TextStyle(
          fontFamily: 'Sans',
          fontWeight: FontWeight.bold,
          color: Colors.white,
          fontSize: 24),
      body1: TextStyle(
          fontFamily: 'Sans',
          fontWeight: FontWeight.bold,
          color: Colors.white,
          fontSize: 24),
      body2: TextStyle(
          fontFamily: 'Sans',
          fontWeight: FontWeight.bold,
          color: Colors.white,
          fontSize: 18),
    ),
  );
}

final ThemeData kAmoledTheme = _buildAmoledTheme();

ThemeData _buildAmoledTheme() {
  final ThemeData base = ThemeData.dark();
  return base.copyWith(
    primaryColor: Colors.black,
    accentColor: Colors.white,
    canvasColor: Colors.transparent,
    primaryIconTheme: IconThemeData(color: Colors.black),
    textTheme: TextTheme(
      headline: TextStyle(
          fontFamily: 'Sans',
          fontWeight: FontWeight.bold,
          color: Colors.white,
          fontSize: 24),
      body1: TextStyle(
          fontFamily: 'Sans',
          fontWeight: FontWeight.bold,
          color: Colors.white,
          fontSize: 24),
      body2: TextStyle(
          fontFamily: 'Sans',
          fontWeight: FontWeight.bold,
          color: Colors.white,
          fontSize: 18),
    ),
  );
}

Create a file that will decide to change the theme.

abstract class ChangeThemeEvent {}

class DecideTheme extends ChangeThemeEvent {}

class LightTheme extends ChangeThemeEvent {
  
  String toString() => 'LightTheme';
}

class DarkTheme extends ChangeThemeEvent {
  
  String toString() => 'Dark Theme';
}

class AmoledTheme extends ChangeThemeEvent {
  
  String toString() => 'Amoled Theme';
}

Now, a file that will change the theme state.

import 'package:flutter/material.dart';
import 'package:wallpapers/bloc/utils.dart';

class ChangeThemeState {
  final ThemeData themeData;

  ChangeThemeState({ this.themeData});

  factory ChangeThemeState.lightTheme() {
    return ChangeThemeState(themeData: kLightTheme);
  }

  factory ChangeThemeState.darkTheme() {
    return ChangeThemeState(themeData: kDarkTheme);
  }
  factory ChangeThemeState.amoledTheme() {
    return ChangeThemeState(themeData: kAmoledTheme);
  }
}

Now create a file that will do the heavy lifting i.e. change the theme when you need and store the user’s choice even if the app is closed.

import 'package:bloc/bloc.dart';
import 'change_theme_event.dart';
import 'change_theme_state.dart';
import 'package:shared_preferences/shared_preferences.dart';

class ChangeThemeBloc extends Bloc<ChangeThemeEvent, ChangeThemeState> {
  void onLightThemeChange() => dispatch(LightTheme());
  void onDarkThemeChange() => dispatch(DarkTheme());
  void onAmoledThemeChange() => dispatch(AmoledTheme());
  void onDecideThemeChange() => dispatch(DecideTheme());
  
  ChangeThemeState get initialState => ChangeThemeState.lightTheme();

  
  Stream<ChangeThemeState> mapEventToState(ChangeThemeEvent event) async* {
    if (event is DecideTheme) {
      final int optionValue = await getOption();
      if (optionValue == 0) {
        yield ChangeThemeState.lightTheme();
      } else if (optionValue == 1) {
        yield ChangeThemeState.darkTheme();
      } else if (optionValue == 2) {
        yield ChangeThemeState.amoledTheme();
      }
    }
    if (event is LightTheme) {
      yield ChangeThemeState.lightTheme();
      try {
        _saveOptionValue(0);
      } catch (_) {
        throw Exception("Could not persist change");
      }
    }

    if (event is DarkTheme) {
      yield ChangeThemeState.darkTheme();
      try {
        _saveOptionValue(1);
      } catch (_) {
        throw Exception("Could not persist change");
      }
    }

    if (event is AmoledTheme) {
      yield ChangeThemeState.amoledTheme();
      try {
        _saveOptionValue(2);
      } catch (_) {
        throw Exception("Could not persist change");
      }
    }
  }

  Future<Null> _saveOptionValue(int optionValue) async {
    SharedPreferences preferences = await SharedPreferences.getInstance();
    preferences.setInt('theme_option', optionValue);
  }

  Future<int> getOption() async {
    SharedPreferences preferences = await SharedPreferences.getInstance();
    int option = preferences.get('theme_option') ?? 0;
    return option;
  }
}

final ChangeThemeBloc changeThemeBloc = ChangeThemeBloc()
  ..onDecideThemeChange();

Finally, all the needed files are created. The reason that there are so many files is that you can use write the code once and reuse it in any application that you will create.

Now to add the ability to change themes in your own custom widget, you will have to do the following steps.

Wrap your widget in BlocBuilder *and subscribe the builder to *changeThemeBloc *in bloc: *parameter.

class SettingsPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return BlocBuilder(
      bloc: changeThemeBloc,
      builder: (BuildContext context, ChangeThemeState state) {
        return Container(
          color: state.themeData.primaryColor,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              RaisedButton(
                color: Colors.blue,
                child: Text(
                  'Light Theme',
                  style: state.themeData.textTheme.body1,
                ),
                onPressed: () {
                  changeThemeBloc.onLightThemeChange();
                },
              ),
              RaisedButton(
                color: Colors.blue,
                child: Text(
                  'Dark Theme',
                  style: state.themeData.textTheme.body1,
                ),
                onPressed: () {
                  changeThemeBloc.onDarkThemeChange();
                },
              ),
              RaisedButton(
                color: Colors.blue,
                child: Text(
                  'Amoled Theme',
                  style: state.themeData.textTheme.body1,
                ),
                onPressed: () {
                  changeThemeBloc.onAmoledThemeChange();
                },
              ),
            ],
          ),
        );
      },
    );
  }
}

Now for the moment, we have all been waiting for. Drumroll, please……..

Our final product.Our final product.

Now you are one step closer to making the best app the world has ever seen.

If you would like to inspect the complete app, feel free to visit this GitHub repo.

I would like to thank Michael Adeyemo for the initial source code.

Share:

© 2021 • Bibek Timsina