Menu
CoddyTech

Выбор места хранения состояния

Часть раздела Управление состоянием путешествия по Flutter на Coddy. Урок 21 из 27.

Переключатель тёмного режима на экране настроек перемещается, но ничего не становится тёмным. Он сохраняет своё значение с помощью setState, и видеть его может только переключатель. Тема задаётся далеко от него, в MyApp. Это значение относится ко всему приложению.

Для каждого значения в приложении нужно найти место. Спросите себя, кто его использует:

  • Один виджет (текст поля, открытая панель, индекс вкладки): setState в этом виджете.
  • Несколько расположенных рядом виджетов (кнопка и подпись рядом с ней): поднимите состояние к их родительскому виджету и передавайте его вниз.
  • Много виджетов или несколько экранов (пользователь, корзина, настройки): общая модель с Provider или Riverpod.

Provider или Riverpod? Оба подходят. Provider немного проще и размещает модели в дереве виджетов, где их можно найти через context. Riverpod хранит провайдеры вне дерева, поэтому для значения не нужен context, а провайдеры могут следить друг за другом. Выберите один вариант для приложения и используйте его везде.

В этом случае подойдёт StateProvider: один bool, который читает MyApp, чтобы выбрать тему, и который изменяет переключатель:

final darkModeProvider = StateProvider((ref) => false);

// в MyApp, теперь ConsumerWidget:
themeMode: ref.watch(darkModeProvider) ? ThemeMode.dark : ThemeMode.light,
challenge icon

Задание

Сложно

Сделай так, чтобы переключатель тёмного режима менял тему всего приложения:

  • Добавь в начало final darkModeProvider = StateProvider((ref) => false);.
  • Преобразуй MyApp в ConsumerWidget, в котором themeMode зависит от провайдера.
  • Преобразуй SettingsScreen в ConsumerWidget: переключатель отображает значение провайдера и обновляет его при изменении.

Попробуйте сами

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 iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Управление состоянием

Потренируйтесь самостоятельно: Онлайн-компилятор Flutter