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:
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(),
);
});
}
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))))));
}
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);
});
}
}
class ViewMenu extends StatefulWidget {
// [...]
}
class ViewMenuState extends State<ViewMenu> {
@override
Widget build(BuildContext context) {
final themeProvider = Provider.of<ThemeProvider>(context);
return Scaffold(
// [...]
ChangeThemeButtonWidget(),
// [...]
);
}
}
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, aswell as its internal variables (i.e. the state). Instead, by moving each variable to the state class, it works just fine, and the state remains unchanged when switching.
class ViewMenu extends StatefulWidget {
ViewMenu({Key? key}) : super(key: key);
// I hasome state there
@override
State<StatefulWidget> createState() => 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.