Flutter theme with Riverpod (2.0 generator)

awaik
2 min readJul 23, 2023

In this article, we are going to build a Flutter app that stores a user-selected theme in its settings and allows it to be switched on the fly, using Riverpod 2.0 and a generator.

In brief, the complete example is available on Github https://github.com/awaik/flutter_swith_theme_riverpod

So, let’s get started. Firstly, we need to create a SharedPrefs class to retain the user’s selected theme between app runs.

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

class AppSharedPrefs {
static AppSharedPrefs? _instance;
static SharedPreferences? _prefs;

factory AppSharedPrefs() {
if (_instance == null) {
throw Exception('AppSharedPrefs is not initialized. '
'Please call AppSharedPrefs.ensureInitialized before.');
}
return _instance!;
}

const AppSharedPrefs._();

static ensureInitialized() async {
_prefs ??= await SharedPreferences.getInstance();
_instance ??= const AppSharedPrefs._();
}

static const _themeKey = 'theme';

ThemeMode themeMode() {
final themeValue = _prefs!.getInt(_themeKey);
if (themeValue == null) return ThemeMode.system;

return ThemeMode.values[themeValue];
}

Future<void> updateThemeMode(ThemeMode theme) async {
await _prefs!.setInt(_themeKey, theme.index);
}
}

Next, we should initialize Shared Preferencies at the start. We do it by adding one line to the main.dart

await AppSharedPrefs.ensureInitialized();

Next, we create a simple riverpod class, to manage the theme state

import 'package:flashcards/core/shared_prefs/app_shared_prefs.dart';
import 'package:flutter/material.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'theme_provider.g.dart';

@riverpod
class ThemeState extends _$ThemeState {
@override
ThemeMode build() {
return AppSharedPrefs().themeMode();
}

ThemeMode getTheme() {
return state;
}

void setTheme(ThemeMode mode) {
state = mode;
AppSharedPrefs().updateThemeMode(state);
}
}

and generate providers with the command

 dart run build_runner build --delete-conflicting-outputs 

This is pretty much it. But let's dive into what is going on here.

First, we can read the documentation https://docs-v2.riverpod.dev/docs/concepts/about_code_generation and understand different defining a provider syntax. In our case, we use a Class-Based synchronous provider to have the ability to mutate a state.

As we have only one state — we return ThemeMode as a result. But in case we need to manage a lot of states for some screens, we can use ViewModel class with our custom variables and manage complicated states, animations and etc. But this is a topic for a separate article.

So, we made two methods

  1. getTheme — we can use it to get a state once.
  2. setTheme — to change it and save the state to SharedPrefs.

Now is the time to put it into the app. And it is a simple and beautiful code

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_swith_theme_riverpod/core/shared_prefs/app_shared_prefs.dart';
import 'package:flutter_swith_theme_riverpod/core/theme_state/theme_provider.dart';

void main() async {
WidgetsFlutterBinding.ensureInitialized();
await AppSharedPrefs.ensureInitialized();

runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends ConsumerWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
final themeMode = ref.watch(themeStateProvider);
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(useMaterial3: true),
darkTheme: ThemeData.dark(useMaterial3: true),
themeMode: themeMode,
home: Scaffold(
appBar: AppBar(
title: Text(themeMode.name),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'The current theme is:',
),
Text(
themeMode.name,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(themeStateProvider.notifier).setTheme(),
tooltip: 'Change theme',
child: const Icon(Icons.color_lens_outlined),
), // This trailing comma makes auto-formatting nicer for build methods.
),
);
}
}

This code is clean and suited for production projects.

In case of any questions feel free to ask them on GitHub. The full code is here https://github.com/awaik/flutter_swith_theme_riverpod

--

--