RaZzLe
RaZzLe

Reputation: 2128

Retrieving Runtime Changed ThemeData Problem

My story in short is, I can successfully change app theme dynamically, but I fail when it comes to start my app with the last chosen ThemeData.

Here is the main.dart:

import "./helpers/constants/themeConstant.dart" as themeProfile;

class MyApp extends StatelessWidget {
 Widget build(BuildContext context) {
  return MultiProvider(
  providers: [
    //Several ChangeNotifierProviders
  ],
  child: Consumer<AuthenticateProvider>(
    builder: (ctx, authData, _) => ChangeNotifierProvider<ThemeChanger>(
      create: (_) {
        ThemeData themeToBeSet;

        themeProfile.setInitialTheme().then((themeData) {
          themeToBeSet = themeData;
        });

        return ThemeChanger(themeToBeSet);
      },
      child: _MaterialAppWithTheme(authData),
    )
  )
);}}

The problem is themeToBeSet variable always being null eventhough I set a ThemeData as I do below:

ThemeData selectedTheme;

Future<ThemeData> setInitialTheme() async {
 final preferences = await SharedPreferences.getInstance();

 if (!preferences.containsKey(ApplicationConstant.sharedTheme)) {
  selectedTheme = appThemeDataDark;

  final currentThemeInfo = json.encode({
  "themeStyle": ApplicationConstant.darkAppTheme
 });

 preferences.setString(ApplicationConstant.sharedTheme, currentThemeInfo);

 return selectedTheme;
}
else {
 final extractedThemeInfo = json.decode(preferences.getString(ApplicationConstant.sharedTheme)) as 
 Map<String, dynamic>;

 final chosenTheme = extractedThemeInfo["themeStyle"];

 if (chosenTheme == ApplicationConstant.lightAppTheme) {
  selectedTheme = appThemeDataLight;

  return selectedTheme;
 }
 else if (chosenTheme == ApplicationConstant.darkAppTheme) {
  selectedTheme = appThemeDataDark;

  return selectedTheme;
 }
 else {
  selectedTheme = appThemeDataDark;

  return selectedTheme;
}}}

Here, I used shared_preferences.dart package to store and retrieve ThemeData info. If I debug this block, I see that my selectedTheme variable is set one of these ThemeData successfully. But, for a reason I couldn't able to find out, themeToBeSet variable on main.dart is not assigned to the result of my setInitialTheme() method.

Is it because of being asynchronous? But, isn't Dart waiting an asynchronous method with .then()?

In order not to leave any questionmarks realated for my other sections, I'm also sharing ThemeChanger class,

class ThemeChanger with ChangeNotifier {
 ThemeData _themeData;

 ThemeChanger(
  this._themeData
 );

 getTheme() => _themeData;

 setTheme(ThemeData theme) {
  _themeData = theme;

 notifyListeners();
 }
}

And, _MaterialAppWithTheme,

class _MaterialAppWithTheme extends StatelessWidget {
  final AuthenticateProvider authData;

  _MaterialAppWithTheme(
    this.authData,
  );

  Widget build(BuildContext context) {
    final theme = Provider.of<ThemeChanger>(context);

    return MaterialApp(
      title: 'Game Shop Demo',
      theme: theme.getTheme(),
      home: authData.isLogedin ?
      HomeScreen(authData.userId) :
      FutureBuilder(
        future: authData.autoLogin(),
        builder: (ctx, authResult) => authResult.connectionState == ConnectionState.waiting ?
        SplashScreen():
        LoginScreen()
      ),
      routes: {
        //Several named routes
      },
    );
  }
}

Upvotes: 0

Views: 101

Answers (1)

RaZzLe
RaZzLe

Reputation: 2128

As I suspected, I misused .then().

I thought Dart is awaiting when you use .then() but after running into this post, I learnt that it is not awaiting..

So, I carry setInitialTheme() method to ThemeChanger class (it was in a different class previously) and call it in the constructor. Here its final version,

class ThemeChanger with ChangeNotifier {
  ThemeData _themeData;

  ThemeChanger() {
    _setInitialTheme();
  }

  getTheme() => _themeData;

  setTheme(ThemeData theme) {
    _themeData = theme;

    notifyListeners();
  }

  Future<ThemeData> _setInitialTheme() async {
    final preferences = await SharedPreferences.getInstance();

    if (!preferences.containsKey(ApplicationConstant.sharedTheme)) {
      _themeData = appThemeDataDark;

      final currentThemeInfo = json.encode({
        "themeStyle": ApplicationConstant.darkAppTheme
      });

      preferences.setString(ApplicationConstant.sharedTheme, currentThemeInfo);

      return _themeData;
    }
    else {
      final extractedThemeInfo = json.decode(preferences.getString(ApplicationConstant.sharedTheme)) as Map<String, dynamic>;
      final chosenTheme = extractedThemeInfo["themeStyle"];

      if (chosenTheme == ApplicationConstant.lightAppTheme) {
        _themeData = appThemeDataLight;

        return _themeData;
      }
      else if (chosenTheme == ApplicationConstant.darkAppTheme) {
        _themeData = appThemeDataDark;

        return _themeData;
      }
      else {
        _themeData = appThemeDataDark; //Its better to define a third theme style, something like appThemeDefault, but in order not to spend more time on dummy stuff, I skip that part

        return _themeData;
      }
    }
  }
}

Now, as you can see, ThemeChanger class is no longer expecting a ThemeData manually, but setting it automatically whenever its called as setInitialTheme() method is assigned to its constructor. And, of course, MyApp in main.dart is changed accordingly:

class MyApp extends StatelessWidget {
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
       //Several ChangeNotifierProviders
      ],
      child: Consumer<AuthenticateProvider>(
        builder: (ctx, authData, _) => ChangeNotifierProvider<ThemeChanger>(
          create: (_) => ThemeChanger(),
          child: _MaterialAppWithTheme(authData),
        )
      )
    );
  }
}

Now, app is launching just fine with the last selected ThemeData which has a pointer stored in SharedPreferences.

Upvotes: 1

Related Questions