# Flutter Dynamic Themes with cubit and hydrated cubit

Hello guys,
In this tutorial, we will gonna learn, how to change themes in flutter apps dynamically with the help of cubit and hydrated cubit.

You can get the full source code [here](https://github.com/Alex-911/flutter_dynamic_theme)

## 0: Create New Flutter Project

We will start by creating a brand new flutter project.
Make sure you've installed the flutter SDK & android studio from the official website
if not then please install those things first from the link below.

-  [flutter SDK](https://flutter.dev/docs/get-started/install) 
-  [android studio](https://developer.android.com/studio) 
-  [git scm](https://git-scm.com/)

After you've installed everything on your machine now it's time to run the flutter command and wait for it to complete.


```
flutter create dynamic_theme_demo
``` 


---

## 1: Setup ( Installing Dependencies )

Now let's open the project folder in VSCode or any IDE of your choice and install the required dependencies we will be needing in our project.

Go to the `pubspec.yaml` file on the root directory of the project and paste these packages to the dependencies list and after that run the` flutter pub get` command if required in VSCode it will be done automatically when you will save the file.
```
  bloc: ^7.2.1
  flutter_bloc: ^7.3.1
  google_fonts: ^2.1.0
  hydrated_bloc: ^7.1.0
  path_provider: ^2.0.5
``` 

## 2: Making Themes Presets
After installing all the dependencies, now it's time to make some theme presets.

Create new file named `themes.dart` inside themes folder under lib folder
`lib/themes/themes.dart`

There are many ways to achieve this but we will take the path which makes sense to us. We will create an `enum` to represent different theme presets. I've used 6 themes presets but you can use as many as you want.


```
enum AppTheme {
  redDark,
  redLight,
  blueLight,
  blueDark,
  greenDark,
  greenLight,
}

``` 

Below that, we will create a `Map<AppTheme, ThemeData>`, so that we can map the correct theme preset using the enum we've created above.


```

final Map<AppTheme, ThemeData> appThemeData = {
  AppTheme.redDark: ThemeData(
    brightness: Brightness.dark,
    primaryColor: Colors.red,
    primarySwatch: Colors.red,
    textTheme: GoogleFonts.dancingScriptTextTheme(),
  ),
  AppTheme.redLight: ThemeData(
    brightness: Brightness.light,
    primaryColor: Colors.red,
    primarySwatch: Colors.red,
    textTheme: GoogleFonts.oswaldTextTheme(),
  ),
  AppTheme.blueLight: ThemeData(
    brightness: Brightness.light,
    primaryColor: Colors.indigo,
    primarySwatch: Colors.indigo,
    textTheme: GoogleFonts.ubuntuTextTheme(),
  ),
  AppTheme.blueDark: ThemeData(
    brightness: Brightness.dark,
    primaryColor: Colors.indigo,
    primarySwatch: Colors.indigo,
    textTheme: GoogleFonts.irishGroverTextTheme(),
  ),
  AppTheme.greenDark: ThemeData(
    brightness: Brightness.dark,
    primaryColor: Colors.green,
    primarySwatch: Colors.green,
    textTheme: GoogleFonts.architectsDaughterTextTheme(),
  ),
  AppTheme.greenLight: ThemeData(
    brightness: Brightness.light,
    primaryColor: Colors.green,
    primarySwatch: Colors.green,
    textTheme: GoogleFonts.permanentMarkerTextTheme(),
  ),
};

``` 
With that done, your `themes.dart` file must look like this.


```

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

enum AppTheme {
  redDark,
  redLight,
  blueLight,
  blueDark,
  greenDark,
  greenLight,
}

final Map<AppTheme, ThemeData> appThemeData = {
  AppTheme.redDark: ThemeData(
    brightness: Brightness.dark,
    primaryColor: Colors.red,
    primarySwatch: Colors.red,
    textTheme: GoogleFonts.dancingScriptTextTheme(),
  ),
  AppTheme.redLight: ThemeData(
    brightness: Brightness.light,
    primaryColor: Colors.red,
    primarySwatch: Colors.red,
    textTheme: GoogleFonts.oswaldTextTheme(),
  ),
  AppTheme.blueLight: ThemeData(
    brightness: Brightness.light,
    primaryColor: Colors.indigo,
    primarySwatch: Colors.indigo,
    textTheme: GoogleFonts.ubuntuTextTheme(),
  ),
  AppTheme.blueDark: ThemeData(
    brightness: Brightness.dark,
    primaryColor: Colors.indigo,
    primarySwatch: Colors.indigo,
    textTheme: GoogleFonts.irishGroverTextTheme(),
  ),
  AppTheme.greenDark: ThemeData(
    brightness: Brightness.dark,
    primaryColor: Colors.green,
    primarySwatch: Colors.green,
    textTheme: GoogleFonts.architectsDaughterTextTheme(),
  ),
  AppTheme.greenLight: ThemeData(
    brightness: Brightness.light,
    primaryColor: Colors.green,
    primarySwatch: Colors.green,
    textTheme: GoogleFonts.permanentMarkerTextTheme(),
  ),
};


``` 
---

## 3: Creating New Cubit

Using the [bloc extension](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc) create a new cubit in the lib directory. In case you don't know what cubit is? I'd suggest you get familiar with [bloc library](https://bloclibrary.dev/#/). You can also get great videos on youtube on bloc & cubit.

So, with that out of the way, we will create a new cubit namely **dynamic_theme** with the help of bloc extension. That will create two files under the cubit folder.

- dynamic_theme_cubit.dart
- dynamic_theme_state.dart

### Dynamic Theme State

This is what it will look like at first


```
part of 'dynamic_theme_cubit.dart';

@immutable
abstract class DynamicThemeState {}

class DynamicThemeInitial extends DynamicThemeState {}

``` 

As we will be dealing with a single state, we don't need to have multiple State classes. And we also don't need the abstract class. So, delete unnecessary classes and also abstract keywords from the main state class.

After performing those deletions, your file should look like this.


```
part of 'dynamic_theme_cubit.dart';

@immutable
class DynamicThemeState {}

``` 

After this, we will create a final field of type **AppTheme (enum)** and also create a constructor to initialize that field.


```
part of 'dynamic_theme_cubit.dart';

@immutable
class DynamicThemeState {
  final AppTheme theme;
  const DynamicThemeState({
    required this.theme,
  });
}
``` 
With that, we are done with the state file. yeah! that's that simple.

### Dynamic State Cubit

Now the cubit file will look something like this at first.


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

part 'dynamic_theme_state.dart';

class DynamicThemeCubit extends Cubit<DynamicThemeState> {
  DynamicThemeCubit() : super(DynamicThemeInitial());
}

``` 

Now the real fun begins. Instead of extending **Cubit Abstract Class** we will extend **HydratedCubit** Class. Then we need to override two methods **toJson** and **fromJson**. This is because Hydrated Cubit will persist the state as JSON in the device storage media. So we have to implement the state conversion ourselves. And also we will provide the initial state for the cubit. I'll pick the **redDark** theme as a initial state.



```
import 'package:flutter_dynamic_theme/themes/themes.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:meta/meta.dart';

part 'dynamic_theme_state.dart';

class DynamicThemeCubit extends HydratedCubit<DynamicThemeState> {
  DynamicThemeCubit()
      : super(
          const DynamicThemeState(theme: AppTheme.redDark),
        );

  @override
  DynamicThemeState? fromJson(Map<String, dynamic> json) {
    // TODO: implement fromJson
    throw UnimplementedError();
  }

  @override
  Map<String, dynamic>? toJson(DynamicThemeState state) {
    // TODO: implement toJson
    throw UnimplementedError();
  }
}

``` 

Now Lets implement the **toJson** function. This function takes State as a parameter which is basically **AppTheme (enum)**, We will convert the enum value to string and create a map with the key **theme** and return it.


```
@override
  Map<String, dynamic>? toJson(DynamicThemeState state) {
    final theme = {
      'theme': state.theme.toString(),
    };

    return theme;
  }

``` 

With that done, now we will implement the **fromJSON** method, this function takes **Map<String, dynamic> json** as a parameter. we will extract the **theme** value out of that map and assign it to the theme variable. 

After that, we will create an AppTheme variable, and using the **firstWhere()** function to the **values** property will check for the matching string and assign the appropriate value to it.


```
  @override
  DynamicThemeState? fromJson(Map<String, dynamic> json) {
    final theme = json['theme'];

    AppTheme currentTheme =
        AppTheme.values.firstWhere((e) => e.toString() == theme);

    return DynamicThemeState(theme: currentTheme);
  }
``` 

With that done, now finally we will create a function that changes the theme. It will take AppTheme as a parameter and emit a new state including that AppTheme variable.


```
  void changeTheme({required AppTheme theme}) =>
      emit(DynamicThemeState(theme: theme));
``` 

With everything done, your **dynamic_theme_cubit.dart** file will look something like this.


```
import 'package:flutter_dynamic_theme/themes/themes.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:meta/meta.dart';

part 'dynamic_theme_state.dart';

class DynamicThemeCubit extends HydratedCubit<DynamicThemeState> {
  DynamicThemeCubit()
      : super(
          const DynamicThemeState(theme: AppTheme.redDark),
        );

  @override
  DynamicThemeState? fromJson(Map<String, dynamic> json) {
    final theme = json['theme'];

    AppTheme currentTheme =
        AppTheme.values.firstWhere((e) => e.toString() == theme);

    return DynamicThemeState(theme: currentTheme);
  }

  @override
  Map<String, dynamic>? toJson(DynamicThemeState state) {
    final theme = {
      'theme': state.theme.toString(),
    };

    return theme;
  }

  void changeTheme({required AppTheme theme}) =>
      emit(DynamicThemeState(theme: theme));
}


``` 
---

## 4: Creating Pages
we will take the bottom-up approach here. There will be two pages altogether

- Home Page
- Settings Page

`Home Page` will contain some text to demonstrate the Theme Change.

`Settings Page` will contain all the themes as a list which when pressed will trigger a function that will change the app theme.

### Settings Page


```
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dynamic_theme/cubit/dynamic_theme_cubit.dart';
import 'package:flutter_dynamic_theme/themes/themes.dart';

class SettingPage extends StatelessWidget {
  const SettingPage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('App Settings'),
        backgroundColor: Theme.of(context).primaryColor,
      ),
      body: BlocBuilder<DynamicThemeCubit, DynamicThemeState>(
        builder: (context, state) {
          return ListView.builder(
            itemCount: AppTheme.values.length,
            itemBuilder: (context, index) {
              final itemAppTheme = AppTheme.values[index];
              return Card(
                elevation: 5,
                color: appThemeData[itemAppTheme]!.primaryColor,
                child: ListTile(
                  onTap: () => context
                      .read<DynamicThemeCubit>()
                      .changeTheme(theme: itemAppTheme),
                  title: Text(
                    itemAppTheme.toString(),
                    style: appThemeData[itemAppTheme]!.textTheme.bodyText1,
                  ),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

``` 

### Home Page


```
import 'package:flutter/material.dart';
import 'package:flutter_dynamic_theme/pages/settings_page.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Dynamic Theme Demo'),
        backgroundColor: Theme.of(context).primaryColor,
      ),
      body: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'Home Page',
              style: Theme.of(context).textTheme.headline3!.copyWith(
                    color: Theme.of(context).primaryColor,
                  ),
            ),
            Text(
              'Dynamic Theme Demo With Hydrated Cubit',
              style: Theme.of(context).textTheme.subtitle1!.copyWith(
                    color: Theme.of(context).primaryColor,
                  ),
            )
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        backgroundColor: Theme.of(context).primaryColor,
        onPressed: () => Navigator.of(context).push(MaterialPageRoute(
          builder: (context) => const SettingPage(),
        )),
        child: const Icon(Icons.settings),
      ),
    );
  }
}
``` 

### Root Widget


```
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dynamic_theme/cubit/dynamic_theme_cubit.dart';
import 'package:flutter_dynamic_theme/pages/home_page.dart';
import 'package:flutter_dynamic_theme/themes/themes.dart';

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

  @override
  Widget build(BuildContext context) {
    return BlocProvider<DynamicThemeCubit>(
      create: (context) => DynamicThemeCubit(),
      child: BlocBuilder<DynamicThemeCubit, DynamicThemeState>(
        builder: (context, state) {
          return MaterialApp(
            debugShowCheckedModeBanner: false,
            theme: appThemeData[state.theme],
            home: const HomePage(),
            initialRoute: '/',
          );
        },
      ),
    );
  }
}
``` 
---

## 6: main.dart file


```
import 'package:flutter/material.dart';
import 'package:flutter_dynamic_theme/pages/root_widget.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:path_provider/path_provider.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  HydratedBloc.storage = await HydratedStorage.build(
    storageDirectory: await getTemporaryDirectory(),
  );
  runApp(const RootWidget());
}
``` 








