0

I am new to Flutter, and I have been trying to make a very basic example: changing the theme at runtime from dark to light.

So far so good, it works using ChangeNotifier, but now I'd like to initialize my _isDarkMode variable at startup, by using SharedPreferences.

My solution feels like a hack, and is completely wrong: it seems to load from the preferences, but the end result is always dark mode.

This is what I did. First, I modified the class with an init function, and added the necessary calls to SharedPreferences:

class PreferencesModel extends ChangeNotifier {
  static const _darkModeSetting = "darkmode";

  bool _isDarkMode = true; // default, overridden by init()

  bool get isDarkMode => _isDarkMode;

  ThemeData get appTheme => _isDarkMode ? AppThemes.darkTheme : AppThemes.lightTheme;

  void init() async {
    final prefs = await SharedPreferences.getInstance();
    final bool? dark = prefs.getBool(_darkModeSetting);
    _isDarkMode = dark ?? false;
    await prefs.setBool(_darkModeSetting, _isDarkMode);
  }

  void setDarkMode(bool isDark) async {
    print("setting preferences dark mode to ${isDark}");
    final prefs = await SharedPreferences.getInstance();
    await prefs.setBool(_darkModeSetting, isDark);
    _isDarkMode = isDark;
    notifyListeners();
  }
}

Then, in the main I call the init from the create lambda of the ChangeNotifierProvider:

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (context) {
          var prefs = PreferencesModel();
          prefs.init(); // overrides dark mode
          return prefs;
        })
      ],
      child: const MyApp(),
    )
  );
}

The State creating the MaterialApp initializes the ThemeMode based on the preferences:

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return Consumer<PreferencesModel>(
      builder: (context, preferences, child) {
        return MaterialApp(
          title: 'MyApp',
          home: MainPage(title: 'MyApp'),
          theme: AppThemes.lightTheme,
          darkTheme: AppThemes.darkTheme,
          themeMode: preferences.isDarkMode ? ThemeMode.dark : ThemeMode.light,
        );
      }
    );
  }
}

Of course if I change the settings in my settings page (with preferences.setDarkMode(index == 1); on a ToggleButton handler) it works, changing at runtime from light to dark and back. The initialization is somehow completely flawed.

What am I missing here?

senseiwa
  • 2,369
  • 3
  • 24
  • 47
  • init method is a Future, try getting `_darkModeSetting` first and then pass it to init method as an argument – Mohammed Alfateh Dec 01 '22 at 18:26
  • @MohammedAlfateh I've tried but it raises an exception. I also [have found another question](https://stackoverflow.com/questions/57711050/load-data-asynchronously-into-changenotifier-model-in-flutter) but I cannot decipher it... the model has a constructor with a context, but the provider creation has no context... maybe you can understand better than me! – senseiwa Dec 02 '22 at 16:36

1 Answers1

1

Unconventionally, I answer my own question.

The solution is to move the preferences reading to the main, changing the main to be async.

First, the PreferencesModel should have a constructor that sets the initial dark mode:

class PreferencesModel extends ChangeNotifier {
  static const darkModeSetting = "darkmode";

  PreferencesModel(bool dark) {
    _isDarkMode = dark;
  }

  bool _isDarkMode = true;
  // ...

Then, the main function can be async, and use the shared preferences correctly, passing the dark mode to the PreferencesModel:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final prefs = await SharedPreferences.getInstance();
  final bool dark = prefs.getBool(PreferencesModel.darkModeSetting) ?? false;
  print("main found dark as ${dark}");

  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (context) => PreferencesModel(dark))
      ],
      child: const RecallableApp(),
    )
  );
}

Please note the WidgetsFlutterBinding.ensureInitialized(); call, otherwise the shared preferences won't work and the app crashes.

senseiwa
  • 2,369
  • 3
  • 24
  • 47