Поймите, как использовать унаследованные виджеты на примере

Рассмотрим вариант использования, когда вы хотите обновить какой-либо элемент в дереве виджетов, когда подключение к Интернету недоступно. Один из возможных способов добиться этого — использовать виджет Inherited, он решает такие случаи использования, когда вы хотите получить доступ к данным из любого места выше дерева.

Возможно, вы уже использовали унаследованный виджет неосознанно во Flutter. Поскольку платформа Flutter использует его внутри для решения нескольких вариантов использования, таких как создание тем, определение размера устройства, маршрутизация и т. д.

Но что такое унаследованный виджет?

У вас есть несколько виджетов, вложенных в дерево виджетов, и вы пытаетесь получить доступ к данным, которые находятся где-то вверху дерева.

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

Если ваше дерево виджетов является потомком унаследованного виджета, вы можете получить доступ к данным в любом месте дерева виджетов.

Мы рассмотрим шаг за шагом и посмотрим, как использовать унаследованные виджеты?

Итак, чтобы использовать виджет Inherited, вы должны расширить его в своем классе. Например:

class InheritedNetworkHandler extends InheritedWidget { 
}

Но как только вы расширяете InheritedWidget, компилятор жалуется: «Отсутствует конкретная реализация InheritedWidget.updateShouldNotify». Если вы перейдете к определению унаследованного виджета (cmd + click), вы увидите, что он определен как класс Abstract с двумя методами:

  • createElement, который является переопределенным методом.
  • updateShouldNotify, который является нереализованным методом.

Мы рассмотрим этот метод, пусть исправят ошибку, -

class InheritedNetworkHandler extends InheritedWidget {
  @override
  bool updateShouldNotify(InheritedWidget oldWidget) {
    return true;
  }
}

Тип возвращаемого значения.
Возвращаемое значение определяет, должен ли Flutter перерисовывать виджеты при изменении данных в унаследованном виджете.

Примечание. В случае перерисовки будет перерисован только тот виджет, который использует унаследованный виджет.

Параметр:
Но что означает имя параметра OldWidget?
OldWidget — это унаследованный виджет со старыми данными, прежде чем он будет перестроен с новыми.

Что это за данные, о которых я говорил в последних нескольких строках?

Прежде чем мы доберемся до этого, вы все еще можете увидеть, что компилятор жалуется на какую-то ошибку. Мы обязуемся выполнить предложение исполнителя, и не забудьте передать дочерний параметр суперклассу, так как это обязательный параметр. Дочерний элемент — это дерево, которое будет потреблять данные унаследованного виджета.

Обновленный код выглядит следующим образом:

class InheritedNetworkHandler extends InheritedWidget {
  const InheritedNetworkHandler({required Widget child}) :      super(child: child);
  @override
  bool updateShouldNotify(covariant InheritedWidget oldWidget) {          return true;
  }
}

Итак, возвращаясь к данным, это поле, определенное в унаследованном виджете. Доступ к этому полю можно получить через дерево дочерних виджетов. Мы добавим поле в наш унаследованный виджет:

class InheritedNetworkHandler extends InheritedWidget {
  // Data hold by inherited widget
  final bool isNetworkAvailable;
  const InheritedNetworkHandler({required this.isNetworkAvailable, required Widget child}): super(child: child);
  @override
  bool updateShouldNotify(covariant InheritedWidget oldWidget) { return true;
  }
}

Итак, здесь мы определили свойство isNetworkAvailable в InheritedNetworkHandler.

Примечание. isNetworkAvailable помечен как окончательный, поскольку виджет Inherited является неизменяемым, а это означает, что эти данные не изменятся в жизненном цикле приложения.
Единственный способ изменить их — это перестроить. унаследованный виджет.

Теперь мы можем получить доступ к значению унаследованного виджета в любом из дочерних виджетов,

class ChildWidget extends StatelessWidget {
  const ChildWidget({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
  
    // Something new?
    final bool? isNetworkAvailable =   context.dependOnInheritedWidgetOfExactType<InheritedNetworkHandler>()?.isNetworkAvailable;
    return Container();
  }
}

Мы используем метод dependOnInheritedWidgetOfExactType. Как следует из названия, он пытается найти конкретную реализацию данного типа T вверх по дереву виджетов.

Но не слишком ли это долго для использования в виджетах?

Да, это так, мы упростим это. Мы добавим его как вспомогательный метод к InheritedNetworkHandler:

class InheritedNetworkHandler extends InheritedWidget {
  ...
  static InheritedNetworkHandler? of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<InheritedNetworkHandler>();
}

Мы определили статический вспомогательный метод of, который принимает BuildContext в качестве параметра и возвращает InheritedNetworkHandler. Давайте используем его в дочернем виджете.

class ChildWidget extends StatelessWidget {
  const ChildWidget({Key? key}) : super(key: key);
@override
  Widget build(BuildContext context) {
  
    // Updated version
    final bool? isNetworkAvailable = InheritedNetworkHandler.of(context)?.isNetworkAvailable;;
   return Container();
  }
}

Сталкивались ли вы раньше с методом of?

  • MediaQuery.of(context)
  • Navigator.of(context)

Видите ли, я говорил вам, что вы уже использовали унаследованный виджет, поэтому, если вы перейдете к определению метода of, он также использует dependOnInheritedWidgetOfExactType. Теперь каждый раз, когда вы используете .of(context), вы знаете, что он делает.

Мы соединим кусочки вместе и увидим весь код.

Ради этой реализации я использовал подключаемый модуль connectivity_plus.
Также я оберну наш InheritedWidget вокруг виджета с отслеживанием состояния, чтобы любые изменения в состоянии этого виджета перестраивали InheritedWidget.

Это реализация унаследованного виджета, теперь мы увидим его использование:

Вы можете получить доступ к унаследованному виджету в контексте, в котором он определен.

В приведенном выше примере контекст может использоваться только поддеревом ChildWidget.
Если вы перейдете к новому маршруту, вы не сможете получить доступ к виджету Inherited при изменении контекста.

Если вы хотите получить доступ к унаследованному виджету во всем приложении, оберните MaterialWidget своим унаследованным виджетом (вариант 2 в приведенном выше фрагменте).

Если вам интересно, нужно ли перестраивать все дерево при обновлении данных в унаследованном виджете, ответ — нет.

Flutter выполняет определенную оптимизацию и перестраивает только тот виджет, который использует данные из унаследованного виджета.

Ресурсы

Некоторые полезные ресурсы по унаследованному виджету от команды Flutter,