Flutter: Changing the app theme without rebuilding it entirely

34 Views Asked by At

I'm using bloc and go_router

On this example, the theme settings page handles when the user changes the theme, adding the ToggleTheme event to the ThemeBloc, but when it changes, the entire app rebuilds, leading to the initial screen. Here is the code:

class ThemeSettingsPage extends StatelessWidget {
  const ThemeSettingsPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(context.l10n.themeModePickerTitle),
      ),
      body: const _ThemeSettingsBody(),
    );
  }
}

class _ThemeSettingsBody extends StatelessWidget {
  const _ThemeSettingsBody();

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: const <Widget>[
        ThemeOption(
          themeValue: ThemeMode.system,
        ),
        ThemeOption(
          themeValue: ThemeMode.dark,
        ),
        ThemeOption(
          themeValue: ThemeMode.light,
        ),
      ],
    );
  }
}

class ThemeOption extends StatelessWidget {
  const ThemeOption({
    required this.themeValue,
    super.key,
  });

  final ThemeMode themeValue;

  @override
  Widget build(BuildContext context) {
    final currentTheme = context.watch<ThemeBloc>().state.themeMode;

    final textTheme = switch (themeValue) {
      ThemeMode.system => context.l10n.themeMode('system'),
      ThemeMode.dark => context.l10n.themeMode('dark'),
      ThemeMode.light => context.l10n.themeMode('light'),
    };

    final themeIcon = Icon(
      switch (themeValue) {
        ThemeMode.system => Icons.settings_cell_outlined,
        ThemeMode.dark => Icons.dark_mode_outlined,
        ThemeMode.light => Icons.light_mode_outlined,
      },
    );

    return RadioListTile.adaptive(
      value: themeValue,
      groupValue: currentTheme,
      onChanged: (value) =>
          context.read<ThemeBloc>().add(ToggleThemeMode(themeMode: themeValue)),
      title: Text(textTheme),
      secondary: themeIcon,
    );
  }
}

final mainNavigatorKey = GlobalKey<NavigatorState>();

class App extends StatelessWidget {
  const App({
    required this.themeRepository,
    required this.usersRepository,
    super.key,
  });

  final UsersRepository usersRepository;
  final ThemeRepository themeRepository;

  @override
  Widget build(BuildContext context) {
    return MultiRepositoryProvider(
      providers: [
        RepositoryProvider.value(
          value: usersRepository,
        ),
        RepositoryProvider.value(
          value: themeRepository,
        ),
      ],
      child: BlocProvider(
        create: (context) => ThemeBloc(themeRepository: themeRepository)
          ..add(ThemeModeRequested()),
        child: const AppView(),
      ),
    );
  }
}

class AppView extends StatelessWidget {
  const AppView({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<ThemeBloc, ThemeState>(
      builder: (context, state) {
        return MaterialApp.router(
          theme: LighTheme.theme,
          darkTheme: DarkTheme.theme,
          themeMode: state.themeMode,
          localizationsDelegates: AppLocalizations.localizationsDelegates,
          supportedLocales: AppLocalizations.supportedLocales,
          routerConfig: AppRouter.mainRouter(mainNavigatorKey),
        );
      },
    );
  }
}

class AppRouter {
  static GoRouter mainRouter(GlobalKey<NavigatorState> navigatorKey) {
    return GoRouter(
      navigatorKey: navigatorKey,
      routes: [
        GoRoute(
          path: '/',
          builder: (context, state) => const HomePage(),
          routes: [
            GoRoute(
              path: 'settings',
              builder: (context, state) => const SettingsPage(),
              routes: [
                GoRoute(
                  path: 'themeSettings',
                  builder: (context, state) => const ThemeSettingsPage(),
                ),
              ],
            ),
          ],
        ),
      ],
    );
  }
}

So, i initially tried using a simple dialog for changing the theme, but the error's still there; then I changed the theme settings to a page in order to keep the navigation stack.

I also thought about just alerting the user that this change would make the app restart, but i rlly want know how to do this

Sorry for my english, it's kind of my first question here :p

0

There are 0 best solutions below