Search code examples
flutterdartsharedpreferencesprovider

Load sharedpreferences on startup with multiprovider, no conusmer


I'm trying to load saved preferences on app load but not sure how to do it. I either get errors or end up creating a loop.

providers.dart

class SettingsProvider with ChangeNotifier {
  SharedPreferences? _prefs;
  String _savedCurrency = '£';
  String _currentTheme = 'light';

  String get currencySymbol => _savedCurrency;
  String get currentTheme => _currentTheme;

  _initPrefs() async {
    _prefs ??= await SharedPreferences.getInstance();
  }

  loadSettings() {
    _savedCurrency = '£';
    _currentTheme = 'light';

    loadSavedTheme();
    loadSavedCurrency();
  }

  // Currency
  Future<void> loadSavedCurrency() async {
    await _initPrefs();
    _savedCurrency = _prefs!.getString('currency') ?? '£';
    notifyListeners();
  }

  Future<void> saveNewCurrency(String newCurrency) async {
    await _initPrefs();
    _prefs!.setString('currency', newCurrency);
    _savedCurrency = newCurrency;
    notifyListeners();
  }

  //Theme

  Future<void> loadSavedTheme() async {
    await _initPrefs();
    _currentTheme = _prefs!.getString('theme') ?? '£';
    notifyListeners();
  }

  Future<void> saveNewTheme(String newTheme) async {
    await _initPrefs();
    _prefs!.setString('theme', newTheme);
    _currentTheme = newTheme;
    notifyListeners();
  }
}

main.dart

Future main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) => MultiProvider(
          providers: [
            ChangeNotifierProvider(create: (_) => GoogleSignInProvider()),
            ChangeNotifierProvider(
                create: (_) => SettingsProvider().loadSettings()),
          ],
          builder: (context, child) {
            String? _theme =
                Provider.of<SettingsProvider>(context, listen: true)
                    .currentTheme;
            print('theme is $_theme');
            return MaterialApp(
              home: LandingPage(),
              theme: ThemeData.light(),
              darkTheme: ThemeData.dark(),
              themeMode: applyThemeMode(_theme),
            );
          });
}

ThemeMode applyThemeMode(String? theme) {
  if (theme == null || theme.isEmpty) {
    return ThemeMode.system;
  }
  if (theme == 'light') {
    return ThemeMode.light;
  } else if (theme == 'dark') {
    return ThemeMode.dark;
  } else {
    return ThemeMode.system;
  }
}

Solution

  • A good example (without using Provider) is now available in Flutter template while creating new flutter app.

    As you are requesting the extended example with the usage of Provider plugin, I'll show you how it's done in my previous projects:

    1. Create SettingsController class to apply UI changes and listen via ChangeNotifier

      class SettingsController with ChangeNotifier {
          SettingsController(this._settingsService);
      
          // Make SettingsService a private variable so it is not used directly.
          final SettingsService _settingsService;
      
          // Make ThemeMode a private variable so it is not updated directly without
          // also persisting the changes with the SettingsService.
          late ThemeMode _themeMode;
      
          // Allow Widgets to read the user's preferred ThemeMode.
          ThemeMode get themeMode => _themeMode;
      
          late bool _showNavigationLabels;
      
          bool get showNavigationLabels => _showNavigationLabels;
      
          /// Retrieve previously saved user's settings from [SharedPreferences] using
          /// [SettingsService]
          Future<void> loadSettings() async {
             _themeMode = await _settingsService.themeMode();
             _showNavigationLabels = await _settingsService.showNavigationLabels();
      
             // Important! Inform listeners a change has occurred.
             notifyListeners();
          }
      
          /// Update and persist the ThemeMode based on the user's selection.
          Future<void> updateThemeMode(ThemeMode? newThemeMode) async {
            if (newThemeMode == null) return;
      
            // Dot not perform any work if new and old ThemeMode are identical
            if (newThemeMode == _themeMode) return;
      
            // Otherwise, store the new theme mode in memory
            _themeMode = newThemeMode;
      
            // Important! Inform listeners a change has occurred.
            notifyListeners();
      
            // Persist the changes to a local database or the internet using the
            await _settingsService.updateThemeMode(newThemeMode);
          }
      
          Future<void> updateShowNavigationLabels(bool? newShowNavigationLabels) async {
            if (newShowNavigationLabels == null) return;
      
            // Dot not perform any work if new and old ThemeMode are identical
            if (newShowNavigationLabels == _showNavigationLabels) return;
      
            // Otherwise, store the new theme mode in memory
            _showNavigationLabels = newShowNavigationLabels;
      
            // Important! Inform listeners a change has occurred.
            notifyListeners();
      
            // Persist the changes to a local database or the internet using the
            await _settingsService.updateShowNavigationLabels(newShowNavigationLabels);
          }
      }
      
    2. Create SettingsService to write logic that saves the settings on the local storage / writes them to Database:

      /// A service that stores and retrieves user settings.
      ///
      /// If you'd like to persist the user settings locally,
      /// use the shared_preferences package.
      /// If you'd like to store settings on a web server, use the http / dio package.
      class SettingsService {
        /// Loads the User's preferred ThemeMode from local or remote storage.
        Future<ThemeMode> themeMode() async {
          SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
      
          bool? darkMode = sharedPreferences.getBool('darkMode');
      
          if (darkMode == null) {
            return ThemeMode.system;
          } else {
            return darkMode ? ThemeMode.dark : ThemeMode.light;
          }
        }
      
        /// Persists the user's preferred ThemeMode to local or remote storage.
        /// Use the shared_preferences package to persist settings locally or the
        /// http package to persist settings over the network.
        Future<void> updateThemeMode(ThemeMode theme) async {
          SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
      
          await sharedPreferences.setBool(
            'darkMode',
            theme == ThemeMode.dark ? true : false,
          );
        }
      
        /// Persists the user's preferred showNavigationLabels to local or remote storage.
        Future<void> updateShowNavigationLabels(bool showNavigationLabels) async {
          SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
      
          await sharedPreferences.setBool(
            'showNavigationLabels',
            showNavigationLabels,
          );
        }
      
        /// Loads the User's preferred ShowNavigationLabels from local or remote storage.
        Future<bool> showNavigationLabels() async {
          SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
      
          bool? showNavigationLabels =
              sharedPreferences.getBool('showNavigationLabels');
      
          return showNavigationLabels ?? true;
        }
      }
      
    3. Inside of the main Function call settingsController.loadSettings() and pass the controller into the InitProviders widget:

      Future<void> main() async {
       WidgetsFlutterBinding.ensureInitialized();
      
       /// Set up the SettingsController, which will glue user settings to multiple
       /// Flutter Widgets.
       final SettingsController settingsController = SettingsController(
         SettingsService(),
       );
      
       /// Load the settings
       await settingsController.loadSettings();
      
       runApp(
         InitProviders(
           settingsController,
         ),
       );
      }
      
    1. Finally, initialize the Providers, in my case with InitProviders widget

      /// Initialize Providers to share global state throughout the app
      class InitProviders extends StatelessWidget {
       const InitProviders(
         this.settingsController, {
         Key? key,
       }) : super(key: key);
      
       final SettingsController settingsController;
      
       @override
       Widget build(BuildContext context) {
         return MultiProvider(
           providers: [
             ChangeNotifierProvider(
               create: (context) => settingsController,
             ),
           ],
           child: const InitApp(),
         );
       }
      }
      

    Notice how InitProviders was called at the very beginning of the app - on top of the widget tree. This is important for Provider plugin and for widgets that use InheritedWidget.