Какие ключевые абстракции использует Flutter?

«Какие ключевые абстракции использует Flutter?» — вопрос из категории Flutter виджеты, который задают на 29% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Основные абстракции Flutter образуют трёхуровневую архитектуру рендеринга:

  1. Widget — декларативное описание части UI. Все элементы, от кнопки до целого экрана, являются виджетами. Они бывают:

    • StatelessWidget — для статического контента.
    • StatefulWidget — для динамического контента с внутренним состоянием.
  2. Element — "живой" экземпляр виджета в дереве элементов. Связывает виджет с RenderObject и управляет жизненным циклом. При перестроении виджета (build()), элемент сравнивает новый виджет со старым и обновляет поддерево, если это необходимо.

  3. RenderObject — отвечает за вычисление layout (размеры и позиции) и непосредственную отрисовку (painting). Это низкоуровневая абстракция, с которой обычно не работают напрямую.

  4. BuildContext — это ссылка на Element. Используется для:

    • Навигации (Navigator.of(context)).
    • Доступа к данным темы или локализации через InheritedWidget.
    • Получения метрик экрана (MediaQuery.of(context)).
  5. InheritedWidget — специальный виджет для эффективной передачи данных вниз по дереву. Позволяет виджетам-потомкам подписываться на данные без явной передачи через конструкторы (prop drilling).

Пример InheritedWidget для темы:

class AppTheme extends InheritedWidget {
  const AppTheme({
    required this.primaryColor,
    required super.child,
  });

  final Color primaryColor;

  // Статический метод для удобного доступа из любого места дерева
  static AppTheme of(BuildContext context) {
    final result = context.dependOnInheritedWidgetOfExactType<AppTheme>();
    assert(result != null, 'No AppTheme found in context');
    return result!;
  }

  // Метод определяет, нужно ли уведомлять подписчиков при обновлении виджета
  @override
  bool updateShouldNotify(AppTheme oldWidget) {
    return primaryColor != oldWidget.primaryColor;
  }
}

// Использование в виджете-потомке:
Text(
  'Hello',
  style: TextStyle(color: AppTheme.of(context).primaryColor),
)