Menu
CoddyTech

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): setState in 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 icon

Challenge

Hard

Make the dark mode switch change the whole app:

  • Add final darkModeProvider = StateProvider((ref) => false); at the top.
  • Turn MyApp into a ConsumerWidget whose themeMode follows the provider.
  • Turn SettingsScreen into a ConsumerWidget: 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;
              });
            },
          ),
        ],
      ),
    );
  }
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in State Management

Practice on your own: Online Flutter compiler