Что такое Extension-методы в Dart и как их применять во Flutter?

«Что такое Extension-методы в Dart и как их применять во Flutter?» — вопрос из категории Dart Core, который задают на 36% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Extension-методы (расширения) в Dart — это мощный механизм, позволяющий добавлять новую функциональность к существующим классам, даже к тем, исходный код которых вы не можете изменить (например, классы из стандартной библиотеки или внешних пакетов). Это делается без создания подкласса и без использования утилитарных классов.

Базовый синтаксис:

extension MyStringExtensions on String {
  // Добавляем метод capitalize() ко всем строкам
  String capitalize() {
    if (isEmpty) return this;
    return '${this[0].toUpperCase()}${substring(1).toLowerCase()}';
  }

  // Добавляем геттер
  bool get isPalindrome => reversed == this;
}

void main() {
  print('dart'.capitalize()); // Вывод: Dart
  print('racecar'.isPalindrome); // Вывод: true
}

Практическое применение во Flutter-разработке:

  1. Упрощение работы с контекстом:

    extension ContextExtensions on BuildContext {
      // Быстрое получение размеров экрана
      Size get screenSize => MediaQuery.of(this).size;
      double get screenWidth => screenSize.width;
      double get screenHeight => screenSize.height;
    
      // Упрощённый вызов навигации
      void popToRoot() => Navigator.of(this).popUntil((route) => route.isFirst);
    }
    
    // Использование в виджете
    @override
    Widget build(BuildContext context) {
      if (context.screenWidth > 600) {
        return _buildTabletLayout();
      }
      return _buildMobileLayout();
    }
  2. Расширение встроенных типов для предметной области:

    extension DateTimeExtensions on DateTime {
      // Форматирование даты для отображения в UI
      String toDisplayFormat() {
        return DateFormat('dd.MM.yyyy HH:mm').format(this);
      }
    
      // Проверка, является ли дата сегодняшним днём
      bool get isToday {
        final now = DateTime.now();
        return year == now.year && month == now.month && day == now.day;
      }
    }
    
    // Использование
    Text(DateTime.now().toDisplayFormat());
  3. Добавление вспомогательных методов к коллекциям:

    extension ListExtensions<T> on List<T> {
      // Безопасное получение элемента или null
      T? elementAtOrNull(int index) => (index >= 0 && index < length) ? this[index] : null;
    
      // Разделение списка на подсписки фиксированного размера (чанки)
      List<List<T>> chunk(int size) {
        return [
          for (var i = 0; i < length; i += size)
            sublist(i, i + size > length ? length : i + size),
        ];
      }
    }

Важные ограничения и особенности:

  • Расширения не могут добавлять поля (переменные экземпляра) в класс, только методы и геттеры/сеттеры.
  • Они работают на этапе компиляции и не влияют на производительность во время выполнения.
  • Имя расширения (например, MyStringExtensions) используется для разрешения конфликтов, если к одному классу применяются несколько расширений с одинаковыми именами методов.
  • Расширения должны быть явно импортированы в файл, где вы хотите их использовать (import 'extensions/string_extensions.dart';).