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
- getTheme — we can use it to get a state once.
- 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