Проблема Lifting State Up
Что такое InheritedWidget и зачем он нужен
InheritedWidget — это фундаментальный механизм управления состоянием во Flutter, который позволяет эффективно передавать данные вниз по дереву виджетов без необходимости явной передачи через конструкторы у каждого виджета.
Этот инструмент лежит в основе многих популярных решений (например, Provider) и используется во встроенных виджетах Flutter: Theme, MediaQuery, Localizations.
До сих пор во всех наших примерах мы передавали данные (свойства и методы) через конструктор в каждом виджете, пока не доходили до нужного. Это неэффективно для больших приложений с глубокой иерархией:
- Легко ошибиться и «потерять» данные на пути.
- Много рутины и повторяющегося кода.
- Падает производительность — излишние перестроения (
build()) множества виджетов.
Демонстрация проблемы. Данные хранит верхний виджет MyApp (название карточки и колбэк для изменения текста), а использовать их нужно глубоко внизу по дереву. Без контекста или InheritedWidget — придётся «прокидывать» через все уровни.
Демонстрация: прокидывание пропсов через конструкторы
Файл main.dart
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
final String text = "Где Context?"; // 👉 Данные хранит 1й виджет MyApp
@override
Widget build(BuildContext context) {
debugPrint("🟢 MyApp() build");
return MaterialApp(
title: "Flutter Course 2025",
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.greenAccent),
fontFamily: "Montserrat",
),
home: Scaffold(
body: HomeWidget(
text: text, // 👉 Передаём во 2й виджет
),
),
);
}
}
class HomeWidget extends StatelessWidget {
final String text; // 👉 Принимаем во 2й виджет
const HomeWidget({super.key, required this.text});
@override
Widget build(BuildContext context) {
debugPrint("🟢 HomeWidget() build");
return Container(
width: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFFBFF098), Color(0xFF6FD6FF)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: SafeArea(
child: Center(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: TrackCard(text: text), // 👉 Передаём в 3й виджет
),
),
),
);
}
}
Файл core_6_inherited_widget.dart
import 'package:flutter/material.dart';
class TrackCard extends StatelessWidget {
final String text; // 👉 Принимаем в 3й виджет
const TrackCard({super.key, required this.text});
@override
Widget build(BuildContext context) {
debugPrint("🟢 TrackCard() build");
return Card(
color: Colors.white,
child: ListTile(
leading: Image.asset("assets/images/pro.webp"),
title: TitleWidget(text: text), // 👉 Передаём в 4й виджет
subtitle: SubtitleWidget(text: "Flutter"),
trailing: LikeButton(),
),
);
}
}
class TitleWidget extends StatelessWidget {
final String text; // 👉 Принимаем в 4й виджет
const TitleWidget({super.key, required this.text});
@override
Widget build(BuildContext context) {
debugPrint("🟢 TitleWidget() build");
return Text(
text, // 👉 И наконец-то используем здесь
style: TextStyle(fontWeight: FontWeight.bold),
);
}
}
Логи перестроений
🟢 MyApp() build
🟢 HomeWidget() build
🟢 TrackCard() build
🟢 TitleWidget() build
🟢 HomeWidget() build
Переделка: управляющий стейт вверху и колбэк вниз
Файл main.dart
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String text = "Где Context?"; // 👉 Данные хранит 1й виджет MyApp
bool isComplete = false;
void changeText() {
setState(() {
isComplete = !isComplete;
});
}
@override
Widget build(BuildContext context) {
debugPrint("🟢 MyApp() build");
return MaterialApp(
title: "Flutter Course 2025",
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.greenAccent),
fontFamily: "Montserrat",
),
home: Scaffold(
body: HomeWidget(
text: text,
isComplete: isComplete,
onChange: changeText, // 👉 Передаём callback во 2й виджет
),
),
);
}
}
class HomeWidget extends StatelessWidget {
final String text;
final bool isComplete;
final VoidCallback onChange; // 👉 Принимаем callback во 2й виджет
const HomeWidget({
super.key,
required this.text,
required this.onChange,
required this.isComplete,
});
@override
Widget build(BuildContext context) {
debugPrint("🟢 HomeWidget() build");
return Container(
width: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFFBFF098), Color(0xFF6FD6FF)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: SafeArea(
child: Center(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: TrackCard(
text: text,
isComplete: isComplete,
onChange: onChange, // 👉 Передаём callback в 3й виджет
),
),
),
),
);
}
}
Файл core_6_inherited_widget.dart
class TrackCard extends StatelessWidget {
final String text;
final bool isComplete;
final VoidCallback onChange; // 👉 Принимаем callback во 3й виджет
const TrackCard({
super.key,
required this.text,
required this.onChange,
required this.isComplete,
});
@override
Widget build(BuildContext context) {
debugPrint("🟢 TrackCard() build");
return Card(
color: Colors.white,
child: ListTile(
leading: Image.asset("assets/images/pro.webp"),
title: TitleWidget(
text: text,
isComplete: isComplete,
onChange: onChange, // 👉 Передаём callback во 4й виджет
),
subtitle: SubtitleWidget(text: "Flutter"),
trailing: LikeButton(),
),
);
}
}
class TitleWidget extends StatelessWidget {
final String text;
final bool isComplete;
final VoidCallback onChange; // 👉 Принимаем callback в 4й виджет
const TitleWidget({
super.key,
required this.text,
required this.onChange,
required this.isComplete,
});
@override
Widget build(BuildContext context) {
debugPrint("🟢 TitleWidget() build");
return GestureDetector(
onTap: () {
onChange(); // 👉 Вызываем callback из 1го виджета (MyApp)
},
child: Text(
text,
style: TextStyle(
fontWeight: FontWeight.bold,
decoration: isComplete
? TextDecoration.lineThrough
: TextDecoration.none,
color: isComplete ? Colors.red : Colors.black,
),
),
);
}
}
class SubtitleWidget extends StatelessWidget {
final String text;
const SubtitleWidget({super.key, this.text = ""});
@override
Widget build(BuildContext context) {
return Text(text.toUpperCase());
}
}
class LikeButton extends StatefulWidget {
const LikeButton({super.key});
@override
State<LikeButton> createState() => _LikeButtonState();
}
class _LikeButtonState extends State<LikeButton> {
bool isFavorite = false;
@override
Widget build(BuildContext context) {
debugPrint("🟢 HomeWidget() build");
final iconButton = IconButton(
onPressed: () {
setState(() {
isFavorite = !isFavorite;
});
},
icon: isFavorite
? Icon(Icons.favorite, color: Colors.red)
: Icon(Icons.favorite_border),
);
return iconButton;
}
}
Цена подхода Lifting State Up
И вроде всё работает… но какой ценой?
Много повторяющегося кода, сложно поддерживать, и главное — при каждом изменении состояния перерисовываются почти все виджеты на пути. Это бьёт по производительности.
🟢 MyApp() build
🟢 HomeWidget() build
🟢 TrackCard() build
🟢 TitleWidget() build
🟢 HomeWidget() build
Нажали на текст зачеркнуть/вернуть
🟢 MyApp() build
🟢 HomeWidget() build
🟢 TrackCard() build
🟢 TitleWidget() build
🟢 HomeWidget() build
Пошаговая инфографика
Слайды с пояснениями процесса передачи состояния и его влияния на перестроения.
Показать весь код
Файл main.dart
import 'package:flutter/material.dart';
import 'core_6_inherited_widget.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String text = "Где Context?";
bool isComplete = false;
void changeText() {
setState(() {
isComplete = !isComplete;
});
}
@override
Widget build(BuildContext context) {
debugPrint("🟢 MyApp() build");
return MaterialApp(
title: "Flutter Course 2025",
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.greenAccent),
fontFamily: "Montserrat",
),
home: Scaffold(
body: HomeWidget(
text: text,
isComplete: isComplete,
onChange: changeText,
),
),
);
}
}
class HomeWidget extends StatelessWidget {
final String text;
final bool isComplete;
final VoidCallback onChange;
const HomeWidget({
super.key,
required this.text,
required this.onChange,
required this.isComplete,
});
@override
Widget build(BuildContext context) {
debugPrint("🟢 HomeWidget() build");
return Container(
width: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFFBFF098), Color(0xFF6FD6FF)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: SafeArea(
child: Center(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: TrackCard(
text: text,
isComplete: isComplete,
onChange: onChange,
),
),
),
),
);
}
}
Файл core_6_inherited_widget.dart
import 'package:flutter/material.dart';
class TrackCard extends StatelessWidget {
final String text;
final bool isComplete;
final VoidCallback onChange;
const TrackCard({
super.key,
required this.text,
required this.onChange,
required this.isComplete,
});
@override
Widget build(BuildContext context) {
debugPrint("🟢 TrackCard() build");
return Card(
color: Colors.white,
child: ListTile(
leading: Image.asset("assets/images/pro.webp"),
title: TitleWidget(
text: text,
isComplete: isComplete,
onChange: onChange,
),
subtitle: SubtitleWidget(text: "Flutter"),
trailing: LikeButton(),
),
);
}
}
class TitleWidget extends StatelessWidget {
final String text;
final bool isComplete;
final VoidCallback onChange;
const TitleWidget({
super.key,
required this.text,
required this.onChange,
required this.isComplete,
});
@override
Widget build(BuildContext context) {
debugPrint("🟢 TitleWidget() build");
return GestureDetector(
onTap: onChange,
child: Text(
text,
style: TextStyle(
fontWeight: FontWeight.bold,
decoration: isComplete ? TextDecoration.lineThrough : TextDecoration.none,
color: isComplete ? Colors.red : Colors.black,
),
),
);
}
}
class SubtitleWidget extends StatelessWidget {
final String text;
const SubtitleWidget({super.key, this.text = ""});
@override
Widget build(BuildContext context) {
return Text(text.toUpperCase());
}
}
class LikeButton extends StatefulWidget {
const LikeButton({super.key});
@override
State<LikeButton> createState() => _LikeButtonState();
}
class _LikeButtonState extends State<LikeButton> {
bool isFavorite = false;
@override
Widget build(BuildContext context) {
debugPrint("🟢 HomeWidget() build");
return IconButton(
onPressed: () {
setState(() {
isFavorite = !isFavorite;
});
},
icon: isFavorite
? Icon(Icons.favorite, color: Colors.red)
: Icon(Icons.favorite_border),
);
}
}