flutterriverpodflutter-sharedpreferenceriverpod-generator

Persiste theme mode with Riverpod


I'm trying to persiste ThemeMode using Riverpod and shared_preferences packages Here is what I did in themeNotifier:

import 'package:flutter/material.dart';
import 'settings_repository.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'theme_notifier.g.dart';

@riverpod
class ThemeNotifier extends _$ThemeNotifier {
  @override
  ThemeMode build() {
    return ThemeMode.system;
  }

  Future themeMode() async {
    state = await ref.watch(settingsRepositoryProvider).loadThemeMode();
  }

  Future updateThemeMode(ThemeMode? newThemeMode) async {
    if (newThemeMode == null) return;
    if (state == newThemeMode) return;
    state = newThemeMode;

    await ref
        .watch(settingsRepositoryProvider)
        .updateThemeMode(theme: newThemeMode);
  }
}

and here is what I did in settingsRepository:

import 'dart:developer';

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

part 'settings_repository.g.dart';

class SettingsRepository {
  final Future<SharedPreferencesWithCache> _prefs =
      SharedPreferencesWithCache.create(
          cacheOptions: const SharedPreferencesWithCacheOptions(
               // This cache will only accept the key 'isDarkMode'.
              allowList: <String>{'isDarkMode'}));

  SettingsRepository();

  get prefs => _prefs;

  Future<ThemeMode> loadThemeMode() async {
    final SharedPreferencesWithCache prefs = await _prefs;
    final isDarkMode = prefs.getBool('isDarkMode');
    log('THEME MODE :: $isDarkMode');
    return isDarkMode == null
        ? ThemeMode.system
        : isDarkMode
            ? ThemeMode.dark
            : ThemeMode.light;
  }

  Future<void> updateThemeMode({ThemeMode? theme}) async {
    final SharedPreferencesWithCache prefs = await _prefs;

    await prefs.setBool('isDarkMode', theme == ThemeMode.dark);
    log('THEME MODE UPDATE:: $theme');
    log('isDarkMode :: ${prefs.getBool('isDarkMode')}');
  }
}

@riverpod
SettingsRepository settingsRepository(SettingsRepositoryRef ref) =>
    SettingsRepository();

then I consume my materialapp and give it the provider:

Widget build(BuildContext context) {
    
    return Consumer(
      builder: (context, ref, _) {
        return MaterialApp(
          home: const RootNavigationBar(),
          
          restorationScopeId: 'app',
          debugShowCheckedModeBanner: false,
          theme: ThemeData(),
          darkTheme: ThemeData.dark(),
          themeMode: ref.watch(themeNotifierProvider),

          
         
      },
    );
}

Everything works fine, but when I try to close the app then open it it throws this error

════════ Exception caught by widgets library ═══════════════════════════════════
The following _TypeError was thrown building Consumer(dirty, dependencies: [UncontrolledProviderScope], state: _ConsumerState#733d8):
type 'Future<dynamic>' is not a subtype of type 'ThemeMode'
The relevant error-causing widget was:
    Consumer Consumer:file:///D:/Documents/Flutter/myapp/lib/src/app.dart:27:12

Then, the app launch normally after few seconds of dark screen.


Solution

  • Problem Solved

    I finnaly figured out the solution,the problem was in themeNotifier when using ref.watch method to access the provider . and then call the themeMode function inside the build methode and not to return it from it,and actually it was the only problem (return an async func inside the build methode of the themeNotifier)

    The comment of @Dan R was helpful ,he suggested to avoid ref.watch in async context.Its a good practice.


    here is the final code solution :


    themeNotifier code

    import 'package:flutter/material.dart';
    import 'settings_repository.dart';
    import 'package:riverpod_annotation/riverpod_annotation.dart';
    
    part 'theme_notifier.g.dart';
    
    @riverpod
    class ThemeNotifier extends _$ThemeNotifier {
     @override
     ThemeMode build() {
      themeMode();
      return ThemeMode.system;
      }
    
    themeMode() async {
     final ThemeMode themeMode =
        await ref.read(settingsRepositoryProvider).loadThemeMode();
     state = themeMode;
     }
    
    Future updateThemeMode(ThemeMode? newThemeMode) async {
      if (newThemeMode == null) return;
     if (state == newThemeMode) return;
     state = newThemeMode;
    
     await ref
        .read(settingsRepositoryProvider)
        .updateThemeMode(theme: newThemeMode);
     }
    }