Проблема Lifting State Up

Что такое InheritedWidget и зачем он нужен

InheritedWidget — это фундаментальный механизм управления состоянием во Flutter, который позволяет эффективно передавать данные вниз по дереву виджетов без необходимости явной передачи через конструкторы у каждого виджета.

Этот инструмент лежит в основе многих популярных решений (например, Provider) и используется во встроенных виджетах Flutter: Theme, MediaQuery, Localizations.

До сих пор во всех наших примерах мы передавали данные (свойства и методы) через конструктор в каждом виджете, пока не доходили до нужного. Это неэффективно для больших приложений с глубокой иерархией:

  1. Легко ошибиться и «потерять» данные на пути.
  2. Много рутины и повторяющегося кода.
  3. Падает производительность — излишние перестроения (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
            

Пошаговая инфографика

Слайды с пояснениями процесса передачи состояния и его влияния на перестроения.

Шаг 1
Шаг 2
Шаг 3
Шаг 4
Шаг 5
Шаг 6
Шаг 7
Шаг 8
Шаг 9
Шаг 10
Шаг 11

Показать весь код

Файл 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),  
    );  
  }  
}