Choosing Where State Lives
Part of the State Management section of Coddy's Flutter journey. Lesson 21 of 27.
The dark mode switch on the Settings screen moves, but nothing turns dark. It keeps its value with setState, and only the switch can see it. The theme is set far away, in MyApp. This value belongs to the whole app.
Every value in an app needs a home. Ask who uses it:
- One widget (a field's text, an open panel, a tab index):
setStatein that widget. - A few widgets close together (a button and the label next to it): lift it to their parent and pass it down.
- Many widgets or several screens (the user, a cart, the settings): a shared model with Provider or Riverpod.
Provider or Riverpod? Both do the job. Provider is a bit simpler and puts models in the widget tree, found through context. Riverpod keeps providers outside the tree, so a value needs no context, and providers can watch each other. Pick one for an app and use it everywhere.
Here a StateProvider fits: one bool, read by MyApp to pick the theme, and changed by the switch:
final darkModeProvider = StateProvider((ref) => false);
// in MyApp, now a ConsumerWidget:
themeMode: ref.watch(darkModeProvider) ? ThemeMode.dark : ThemeMode.light,Challenge
HardMake the dark mode switch change the whole app:
- Add
final darkModeProvider = StateProvider((ref) => false);at the top. - Turn
MyAppinto aConsumerWidgetwhosethemeModefollows the provider. - Turn
SettingsScreeninto aConsumerWidget: the switch shows the provider's value and sets it when changed.
Try it yourself
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: ThemeMode.light,
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Notes'),
actions: [
IconButton(
icon: const Icon(Icons.settings),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SettingsScreen()),
);
},
),
],
),
body: const Center(child: Text('No notes yet')),
);
}
}
class SettingsScreen extends StatefulWidget {
const SettingsScreen({super.key});
@override
State<SettingsScreen> createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> {
bool dark = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Settings')),
body: ListView(
children: [
SwitchListTile(
title: const Text('Dark mode'),
value: dark,
onChanged: (value) {
setState(() {
dark = value;
});
},
),
],
),
);
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in State Management
1Lifting State Up
Pass State to a ChildPass a Callback to a ChildCallbacks That Send a ValueLift State Up4Riverpod
Riverpod: ProviderScopeConsumerWidget and ref.watchRiverpod: StateProviderRiverpod: NotifierProviders That Watch ProvidersChoosing Where State LivesPractice on your own: Online Flutter compiler