Выбор места хранения состояния
Часть раздела Управление состоянием путешествия по 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,Задание
СложноСделай так, чтобы переключатель тёмного режима менял тему всего приложения:
- Добавь в начало
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;
});
},
),
],
),
);
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Управление состоянием
1Поднятие состояния
Передача состояния дочернему компонентуПередача callback-функции дочернему компонентуCallback-функции, передающие значениеПоднятие состояния4Riverpod
Riverpod: ProviderScopeConsumerWidget и ref.watchRiverpod: StateProviderRiverpod: NotifierПровайдеры, отслеживающие другие провайдерыВыбор места хранения состоянияПотренируйтесь самостоятельно: Онлайн-компилятор Flutter