Home > Net >  Switching theme with ThemProvider causes the widget to reset its state
Switching theme with ThemProvider causes the widget to reset its state

Time:07-25

I'm working on a small quiz application, and I would like to add a switch to toggle light/dark mode.

I was following this tutorial, but here's the problem:

When I tap the switch to change the theme, the whole widget gets rebuild (I've printed the hashes and they're different each time I toggle the switch), and so its state and variables. But since in that page I load some resources and update the widget components (for instance, I load the quiz questions from a file, and update some texts saying how many questions there are), those get reset. The problem is that I just want the theme to change, leaving the widget and its variables as they are, but I can't find any solutions/workarounds or explaination on why it happens.

Here's a gif of what happens: Debug test

Those are the involved files and classes:

main.dart

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => ChangeNotifierProvider(
      create: (context) => ThemeProvider(),
      builder: (context, _) {
        final themeProvider = Provider.of<ThemeProvider>(context);

        return MaterialApp(
          title: 'ROquiz',
          themeMode: themeProvider.themeMode,
          theme: MyThemes.themeLight,
          darkTheme: MyThemes.themeDark,
          home: ViewMenu(),
        );
      });
}

themes.dart

class ThemeProvider extends ChangeNotifier {
  ThemeMode themeMode = ThemeMode.light;

  bool get isDarkMode => themeMode == ThemeMode.dark;

  void toggleTheme(bool isOn) {
    themeMode = isOn ? ThemeMode.dark : ThemeMode.light;
    notifyListeners();
  }
}

class MyThemes {
  static final themeLight = ThemeData(
    colorSchemeSeed: Colors.blue,
    brightness: Brightness.light,
    //iconTheme: IconThemeData(color: Colors.blue[900]),
    elevatedButtonTheme: ElevatedButtonThemeData(
        style: ButtonStyle(
            shape: MaterialStateProperty.all<RoundedRectangleBorder>(
                RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(30.0),
    )))),
  );

  static final themeDark = ThemeData(
      scaffoldBackgroundColor: Colors.indigo[700],
      brightness: Brightness.dark,
      colorScheme: const ColorScheme.dark(),
      elevatedButtonTheme: ElevatedButtonThemeData(
          style: ButtonStyle(
              shape: MaterialStateProperty.all<RoundedRectangleBorder>(
                  RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(30.0),
                      side: BorderSide(color: Colors.blue))))));
}

change_theme_button_widget.dart

class ChangeThemeButtonWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final themeProvider = Provider.of<ThemeProvider>(context);

    return Switch.adaptive(
        value: themeProvider.isDarkMode,
        onChanged: (value) {
          final provider = Provider.of<ThemeProvider>(context, listen: false);

          provider.toggleTheme(value);
        });
  }
}

ViewMenu.dart

class ViewMenu extends StatefulWidget {
  // [...]
}

class ViewMenuState extends State<ViewMenu> {
  @override
  Widget build(BuildContext context) {
    final themeProvider = Provider.of<ThemeProvider>(context);
    
    return Scaffold(
      // [...]
      ChangeThemeButtonWidget(),
      // [...]
    );
  }
}

CodePudding user response:

I think one of the solution for this is store the progress on your local storage. you can use shared preferences https://pub.dev/packages/shared_preferences

CodePudding user response:

I've found the solution and I'm posting it there cause I think it might be useful to someone else.

What I did wrong is that I was using the widget to store some state (and access it with widget.). So each time I used the switch to change the theme, the widget would get rebuilt and so it's internal variables. Instead, by moving each variable to the state class, it works just fine, and the state remain unchanged.

class ViewMenu

class ViewMenu extends StatefulWidget {
  ViewMenu({Key? key}) : super(key: key);
  
  // I hasome state there

  @override
  State<StatefulWidget> createState() => ViewMenuState();
}

class ViewMenuState

class ViewMenuState extends State<ViewMenu> {

  // The state should be all there for the theme changer to work properly

  @override
  Widget build(BuildContext context) {
    final themeProvider = Provider.of<ThemeProvider>(context);
    
    return Scaffold(
      // [...]
      ChangeThemeButtonWidget(),
      // [...]
    );
  }
}

It appears that with ThemeProvider, flutter rebuilds the widget but not its state, so in order for it to work, with a StatefulWidget, you have to store everything in the class that extends State.

  • Related