Context - как это работает во Flutter

Егор Долгов · 23.01.2021
Статья написана для старой версии Flutter и сейчас обновляется. Часть кода может не компилироваться на актуальной версии.

Перевод статьи Flutter in Context автора Greg Perry

Детальный разбор класса BuidContext (Эта статья является частью серии Decode Flutter Series )

Вы уже знакомы с контекстными объектами? Я имею в виду объекты класса BuildContext с именем context , который постоянно передается функции build() , а также является необходимым параметром для множества статических функций:

build(BuildContext context)
Theme.of(context)
Scaffold.of(context)
MediaQuery.of(context)
Navigator.of(context)

Они помогают идти вверх или сквозь «дерево рендеринга» (или же «дерево виджетов»). Мы подробно рассмотрим эти объекты в этой статье, заглянем «под капот» фреймворка Flutter и выясним, из чего именно состоит этот объект BuiltContext под названием context . А это означает, что мы «пойдем» сквозь код. Собственно, не буду долго тянуть и держать вас в неведении и прямо сейчас рассажу вам, что именно это за объект: это элемент.

Нажмите на скриншот, чтобы открыть отрывок кода.

Я всегда предпочитаю использовать скриншоты в своих статьях для отображения самого концепта, а не просто кода. К тому же, я считаю, что с ними проще работать. Однако, вы можете кликнуть на изображение, чтобы открыть сам код в gist или в Github. Также эту статью лучше читать не на мобильном устройстве, а через компьютер.

А теперь начнем!

Not In Context

Так уж получилось, что мы не собираемся подробно рассматривать сам класс BuildContext . Хоть он и является ключевым во фреймворке Flutter, просто представления о том, что он делает в приложении, будет вполне достаточно. К тому же, это абстрактный класс – вы должны создать подкласс и реализовать все поля и функции, что он содержит. Ниже предоставлен скриншот класс со всей его документацией и удаленными устаревшими функциями – просто чтобы дать вам представление об его роли во Flutter. Вы даже можете узнать некоторые функции и даже удивиться тому, в каком классе они находятся. Затем мы определим точный подкласс, который использует BuildContext. Правда, эту загадку я уже раскрыл выше.

Исходник на GitHub

Элементы виджетов

Отойдем немного назад и посмотрим сперва на класс StatelessWidget . Ниже расположен скриншот одного такого класса со всей его документацией, чтобы вы могли рассмотреть, из чего он состоит. Не очень-то много всего, правда? Это абстрактный класс, и его подкласс, конечно, должен реализовывать метод build() – это мы уже знаем. Однако, что насчет метода createElement() ? Он создает другой класс под названием StatelessElement и,фактически, ссылается на себя в качестве параметра.

Исходник на GitHub

Я решил оставить всю немногочисленную документацию класса StatelessElement . Обратите внимание, указанный в конструкторе параметр widget класса StatelessWidget передается родительскому классу ComponentElement , туда мы и перейдем:

Исходник на GitHub

Класс ComponentElement становится более обширным, так что я сделал скриншот начала этого класса. Это тоже абстрактный класс, что имеет смысл, поскольку он содержит тот самый необходимый для выполнения метод build() . Сейчас мы возвращаемся назад через иерархию классов, однако, мы еще посетим эти «промежуточные» классы через некоторое время. Сейчас мы должны обратить внимание на класс Element .

Исходник на GitHub

Опять-таки, мы смотрим только на начало этого класса. Он гораздо больше тех, что мы рассматривали ранее: в целом, он состоит из 90 методов и полей. В самом деле, довольно важный «элемент». Однако, мы пришли к цели. Что вы видите?

Исходник на GitHub

Класс Element реализует другой класс, BuildContext . В отличие от, к примеру, Java , любой класс в языке программирования Dart может быть использован, как интерфейс – вы просто добавляете его название в конце декларации класса сразу после ключевого слова implements .

Конечно, если только ваш класс не является абстрактным, вы должны реализовать все методы и поля, составляющие этот класс. В случае с классом Elements , мы реализуем класс BuildContext со всей его составляющей – не удивительно, что теперь мы видим огромное количество полей и методов. После выполнения этой непростой задачки, мы наконец-то можем заключить, что, как и во всех ориентированных на объект языках, объект типа Element может быть передан функциям или конструкторам как объект типа BuildContext .

Документация Flutter поясняет, что BuildContext было решено использовать в качестве интерфейса с целью препятствия прямому манипулированию объектами Element – что бы это ни значило. К тому же, кто вообще захочет разбираться с 90 методами и полями класса?

Так что, теперь вы знаете, что при размещении контрольной точки в своей любимой IDE прямо в строке метода build() , параметр контекста (context), переданный этму методу (в случае StatelessWidget ) – тот самый объект StatelessElement , создание которого мы видели в скриншоте StatelessWidget . Обратите внимание, что это касается и StatefulWidget – только с объектом StatefulElement . Оба эти типа виджетов представлены ниже:

Исходник на GitHub

Помните, что у каждого виджета есть свой объект Element (или же объект BuildContext ). Element/BuildContext может принадлежать только одному виджету. Когда вы только начинали изучать Flutter , скорее всего, вы пришли к выводу, что все виджеты, составляющие ваше приложение, расположены в древообразном порядке, и это правда, только не совсем. Это их сообщающиеся объекты Element , связанные друг с другом. Каждый элемент (контекст) содержит отсылку на свой «родительский» элемент, который создает тот самый элемент (контекст) при запуске приложения Flutter . Можно сказать, что приложение Flutter состоит из связанных друг с другом объектов BuildContext , которые и составляют «древо объектов BuildContext ». Они остаются неизменными в течение всего цикла приложения, в то время как их аналоги-«виджеты» могут быть удалены и воссозданы вновь и вновь.

Поместим это в контекст

Я воспользуюсь приложением из другой статьи для демонстрации «процесса наполнения», задействованного при запуске приложения и того, как каждый виджет включает в себя свой собственный аналог элемента (контекста). Домашний экран приложения со всем его кодом представлен ниже. На первый взгляд, мы имеем десять виджетов:

main.dart

Скриншоты ниже показывают, что происходит при вызове метода createElement для StatelessWidget . Как только это происходит, в «родительском» методе элемента, inflateWidget() , создается соответствующий объект элемента виджета. На среднем скриншоте отображена эта функция и показано, как недавно созданный объект элемента вызывает свою собственный метод mount() . Это происходит там же, где и «родительский» элемент привязывается к полю дочернего экземпляра parent , добавляясь к древу рендеринга. Каждому виджету соответствует свой элемент/контекст объекта.

Исходник на GitHub

А теперь с Context

При помощи ссылки на объекты BuildContext у вас появляется все необходимое для возврата к дереву, чтобы получить все «выше» созданные экземпляры Widgets и объекты State . К примеру, все эти функции «of» перечислены в начале этой статьи, и каждая из них содержит функцию из BuidContext , реализованную и определенную в классе Element:

context.dependOnInheritedWidgetOfExactType<_InheritedTheme>();
context.findAncestorStateOfType();
context.dependOnInheritedWidgetOfExactType();
context.dependOnInheritedWidgetOfExactType<_LocalizationsScope>();
context.findRootAncestorStateOfType();

Тема оформления

Напоследок, разберем еще парочку более популярных исполнений. Работая с Flutter , вы обнаружите, что статичная функция Theme.of() используется несколько раз при работе фреймворка. Ниже слева есть скриншот, показывающий весьма распространенное действие – получение объекта ThemeData для применения определенного стиля для виджета Text . На скриншоте справа отображен определенный в классе BuidContext метод dependInheritWidgetOfExactType , используемый для поиска определенного «типа InheritedWidget » в объекте Map под названием inheritedWidgets .

Исходник на GitHub

Ищем Scaffold

В приложениях Flutter также весьма распространен метод Scaffold.of() . Он содержит прочие методы, определенные в классе BuildContext и реализованные в классе Element . Также он идет вверх через весь список связанных объектов Element , чтобы найти ранее определенный объект State . Практически целый цикл связан с возвращением через связанный список объектов Element ( каждый из них связан со своим «родителем») в целях поисках виджета определенного типа. В нашем случае выполняется поиск Scaffold , определенного ранее в древе рендеринга, а затем возвращается его связанный объект State под названием ScaffoldState .

Исходник на GitHub

Пока что остановимся на этом. Класс Element реализует класс BuildContext , и, возможно, чуть позже мы обратимся к ряду других функций, с помощью которых можно извлекать множество «элементов» вашего приложения, если они уже были созданы и упомянуты как часть древа рендеринга.

До встречи!

Другие статьи автора оригинального текста Greg Perry