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;
}
}
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:
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);
}
}
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;
}
}
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,
),
);
}
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.