In this article, we will learn about changing the theme (Light/Dark mode) of an app by using custom options with Bloc. Bloc is a state management library that helps improve the code’s quality and makes handling states in the app much more manageable.

Dynamic Theme means changing/updating themes on the fly from anywhere inside the app. A theme in Flutter is provided by ThemeData. ThemeData is responsible for holding themes for each and every widget in a flutter. Also, we can explicitly change the theme of the widget. Changing themes in mobile applications has become a popular feature nowadays. This feature not only enhances the UI of the mobile applications but also provides a good user experience.

There are two ways to change the theme (Light/dark mode) in the Flutter app. One way is to change from the default phone setting and another way is to add an option inside the app to change the theme.

Read more: A Complete Guide on Bloc

Let’s take an example of how we can implement the App Theming in our flutter application.

Dependencies Installation

dependencies:
  cupertino_icons: ^1.0.2
  flutter:
    sdk: flutter
  flutter_bloc: ^8.0.1

Now we Create a separate class for color schemas to declare the color of our choice for light mode and dark mode.

import 'package:flutter/material.dart';

enum AppTheme {
  darkTheme,
  lightTheme,
}

class AppThemes {
  static final appThemeData = {
    AppTheme.darkTheme: ThemeData(
      primarySwatch: Colors.grey,
      primaryColor: Colors.black,
      brightness: Brightness.dark,
      backgroundColor: const Color(0xFF212121),
      dividerColor: Colors.black54,
      floatingActionButtonTheme: const FloatingActionButtonThemeData(
        backgroundColor: Colors.white,
      ),
      textButtonTheme: TextButtonThemeData(
        style: ButtonStyle(
          foregroundColor: MaterialStateProperty.all(Colors.white),
        ),
      ),
      textTheme: const TextTheme(
        subtitle1: TextStyle(color: Colors.white),
      ),
      bottomNavigationBarTheme: const BottomNavigationBarThemeData(
          backgroundColor: Colors.grey, unselectedItemColor: Colors.white),
    ),

    //
    //

    AppTheme.lightTheme: ThemeData(
      primarySwatch: Colors.grey,
      primaryColor: Colors.white,
      brightness: Brightness.light,
      backgroundColor: const Color(0xFFE5E5E5),
      dividerColor: const Color(0xff757575),
      floatingActionButtonTheme: const FloatingActionButtonThemeData(
        backgroundColor: Colors.black,
        foregroundColor: Colors.white,
      ),
      textButtonTheme: TextButtonThemeData(
        style: ButtonStyle(
          foregroundColor: MaterialStateProperty.all(Colors.black),
        ),
      ),
      textTheme: const TextTheme(
        subtitle1: TextStyle(color: Colors.black),
      ),
      bottomNavigationBarTheme: const BottomNavigationBarThemeData(
          backgroundColor: Colors.grey,
          selectedItemColor: Colors.black,
          unselectedItemColor: Colors.white),
    ),
  };
}

Building Bloc

State

// ignore_for_file: public_member_api_docs, sort_constructors_first
part of 'switch_bloc.dart';

@immutable
class SwitchState {
  final bool switchValue;
  const SwitchState({
    required this.switchValue,
  });
}

class SwitchInitial extends SwitchState {
  const SwitchInitial({required bool switchValue})
      : super(switchValue: switchValue);
}

Events

part of 'switch_bloc.dart';

@immutable
abstract class SwitchEvent {}

class SwitchOnEvents extends SwitchEvent {}

class SwitchOffEvents extends SwitchEvent {}

Bloc

import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';

part 'switch_event.dart';
part 'switch_state.dart';

class SwitchBloc extends Bloc<SwitchEvent, SwitchState> {
  SwitchBloc() : super(const SwitchInitial(switchValue: false)) {
    on<SwitchOnEvents>((event, emit) {
      emit(const SwitchState(switchValue: true));
    });
    on<SwitchOffEvents>((event, emit) {
      emit(const SwitchState(switchValue: false));
    });
  }
}

for the presentation layer, we have the following UI to show on the device.

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:vlogpost/Bloc/switch_bloc.dart';

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

  @override
  State<SwitchScreen> createState() => _SwitchScreenState();
}

class _SwitchScreenState extends State<SwitchScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('App Theming With Bloc'),
        centerTitle: true,
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          BlocBuilder<SwitchBloc, SwitchState>(
            builder: (context, state) {
              return SwitchListTile(
                secondary: Icon(
                    state.switchValue ? Icons.dark_mode : Icons.light_mode),
                title: Text(
                  state.switchValue ? 'Dark Mode' : 'Light Mode',
                  style: TextStyle(
                      color: state.switchValue ? Colors.white : Colors.black),
                ),
                value: state.switchValue,
                onChanged: (value) {
                  value
                      ? context.read<SwitchBloc>().add(SwitchOnEvents())
                      : context.read<SwitchBloc>().add(SwitchOffEvents());
                },
              );
            },
          ),
        ],
      ),
    );
  }
}

we wrapped our MyApp() into BlocProvider which has the following line of code.

void main() {
  runApp(BlocProvider(
     create: (context) =>
            SwitchBloc(),
     child: const MyApp(),
  ));
}

Meanwhile, when we run this application in the emulator or device, we should get the UI similar to the screenshot below:

Get the full snippet from here