Home > Enterprise >  Load sharedpreferences on startup with multiprovider, no conusmer
Load sharedpreferences on startup with multiprovider, no conusmer

Time:11-25

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;
  }
}

CodePudding user response:

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.

CodePudding user response:

No sure if it is needed for your app, but what I do in main.dart is check whether a user is logged in.

Depending on the login state (true or false) I redirect the user to a login screen or a startup screen. In the initialization of the startup screen I call an initialization function inside one of my providers. When initialization inside startup screen is finished the user is redirected to the home screen. Because the screens have the same background image, it is a very smooth initialization experience.

For your code:

ChangeNotifierProvider(create: (_) => SettingsProvider().loadSettings())

should be:

ChangeNotifierProvider(create: (_) => SettingsProvider())

The loadSettings() from the provider can be called in the initialization of your first screen.

  • Related