Theme of Flutter app changes only after Hot Restart

66 Views Asked by At

I'm trying to implement a feature whose job is to switch beetween light and dark mode using BLoC and SharedPreferences. Through debug prints I know that the DarkModeCubit is working properly. However the MainApp doesn't receive the new state immediately but only after hot restart. I don't know why.

Here is my Main App

void main() async {
  WidgetsFlutterBinding.ensureInitialized(); // essential
  await Firebase
      .initializeApp(); // needed for Firebase, included Authentication and Firestore
  //
  // Initialization of all blocs used by most pages
  //
  runApp(
    MultiBlocProvider(
      providers: [
        BlocProvider(create: ((context) => AuthenticationBloc())),
        BlocProvider(create: ((context) => RefreshUserBloc())),
        BlocProvider(create: ((context) => GetCurrentUserDocCubit())),
        BlocProvider(create: ((context) => DarkModeCubit(PrefsState())))
      ],
      child: MainApp(),
    ),
  );
}

//
// Class of the Material App, the top of the widget tree
//
class MainApp extends StatefulWidget {
  const MainApp({super.key});

  @override
  State<MainApp> createState() => _MainAppState();
}

class _MainAppState extends State<MainApp> {
  final AppRouter _appRouter = AppRouter();
  @override
  void initState() {
    super.initState();
    //
    // When the app starts we check if the user is already logged in. If so, we go to the home page, otherwise we go to the login page
    //
    WidgetsBinding.instance.addPostFrameCallback((_) {
      BlocProvider.of<AuthenticationBloc>(context).add(AuthenticationStarted());
    });
  }

  @override
  Widget build(BuildContext context) {
    //
    // Build method of the Material App
    //
    return BlocBuilder<DarkModeCubit, ThemeMode>(
      builder: (context, themeMode) {
        debugPrint('MainApp: $themeMode');
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          //
          //  Theme of the app, with the colorScheme and the textTheme
          //
          theme: AppTheme.lightTheme,
          darkTheme: AppTheme.darkTheme,
          themeMode: themeMode,
          //
          // onGenerateRoute is the chosen routing system. It is defined in the app_router.dart file.
          // When a route is called via Navigator.of(context).pushNamed('/routeName'), the onGenerateRoute method is called,
          // and it returns the page associated with the routeName.
          //
          onGenerateRoute: _appRouter.onGenerateRoute,
          //
          //  The initial route is the first route that is shown when the app starts.
          //  It is set to a BlocBuilder that listens to the AuthenticationBloc, and it redirects to the login page if the user is not logged in,
          //  otherwise it redirects to the home page.
          //
          initialRoute: '/',
        );
      },
    );
  }
}

Here is the Cubit

class DarkModeCubit extends Cubit<ThemeMode> {
  final PrefsState prefs;

  DarkModeCubit(this.prefs) : super(ThemeMode.light) {
    _loadTheme();
  }

  ///
  /// Method that loads the current {isDarkTheme}
  ///
  void _loadTheme() async {
    final isDarkTheme = await prefs.getThemePreference();
    emit(isDarkTheme ? ThemeMode.dark : ThemeMode.light);
  }

  ///
  /// Method that changes the {isDarkTheme}
  ///
  void toggleTheme({required bool isDarkTheme}) async {
    await prefs.setThemePreference(isDarkTheme);
    emit(isDarkTheme ? ThemeMode.dark : ThemeMode.light);
    debugPrint('DarkModeCubit: $state');
  }

  
}

Here is my PrefsState

class PrefsState {
  /// Key that stores the location of the dark theme.
  static const _isDarkThemeKey = 'isDarkTheme';

  /// Method that retrieves the bool that indicates whether the theme is dark or light.
  Future<bool> getThemePreference() async {
    final prefs = await SharedPreferences.getInstance();
    return prefs.getBool(_isDarkThemeKey) ?? false;
  }

  /// Method that set the {isDarkTheme} property in the local storage.
  Future<void> setThemePreference(bool isDarkTheme) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setBool(_isDarkThemeKey, isDarkTheme);
  }
}
3

There are 3 best solutions below

2
EdwynZN On

After reading your code it seems that your problem is with how you create the bloc providers in the widget tree:

BlocProvider(create: ((context) => DarkModeCubit(PrefsState())))

/// or for readibility:
BlocProvider(
  create: (
    (context) => DarkModeCubit(PrefsState())
  )
)

That parenthesis wrapping your create make it seems that there is a function within a function, remove it from all your providers so they initialize correctly.

BlocProvider(create: (_) => DarkModeCubit(PrefsState()))
0
AHMAD_AR On

I can provide you with an easier solution I use it with BLoC

 child: BlocBuilder<ThemeCubit, ThemeState>(
      builder: (context, state) => MaterialApp(
        theme: themes[state.theme],
        debugShowCheckedModeBanner: false,
        home: LocalData.containsKey('opened')
            ? HomeScreen()
            : const OnboardingScreen(),
        onGenerateRoute: RouteGenerator.generatedRoute,
      ),
    );

    class ThemeInitState extends ThemeState {
     ThemeInitState()
      : super(
            theme: LocalData.getData(key: 'theme') == null
                ? "whiteTheme"
                : LocalData.getData(key: 'theme')!);
}

  class ChangeThemeState extends ThemeState {
     ChangeThemeState({required super.theme});
}

themes used in the beginning is a JSON map that contains the Theme objects and I call them based on the key stored in local storage

if you need to view the full code you can check my app source code on GitHub using the following link Gameaway Source Code

0
Antonio Lentini On

[RESOLVED] I made a stupid mistake in providing the cubit. It seemed that the Material App and the page where I changed the theme through a switch had two different instances of the DarkModeCubit, so the first was not allowed to listen for state changes.