Подробный обзор StatefulWidget Flutter и его жизненного цикла.

Вы собираетесь использовать StatefulWidget’s. Если вы собираетесь использовать Flutter, вы собираетесь использовать StatefulWidget’s. Много их. Вместе. Один поверх другого. Это неизбежно. По мере того, как вы изучаете Flutter все больше и больше, ваши приложения будут усложняться ... с большим количеством виджетов. Больше StatefulWidgets. Виджет без сохранения состояния никогда не меняется. Виджет с отслеживанием состояния может изменяться в ответ на действия пользователя или другие события. «Состояние» этого виджета представлено отдельным объектом класса, который называется, ну… Состояние. Объект State состоит из значений, которые могут изменяться. Объект State содержит «изменяемое состояние» связанного с ним виджета. Он хранит значения, которые могут изменяться с течением времени. Когда эти значения изменяются, чаще всего повторно создается связанный StatefulWidget.

Учиться на примере

Во многих своих статьях я пытаюсь использовать установленные примеры с собственного веб-сайта Flutter Google, чтобы передать концепцию, конкретный виджет и т. Д. В этой статье я буду использовать «пример приложения», сгенерированный для вас каждый раз, когда вы создаете Новый проект Flutter ... ». Ниже я изолировал как StatefulWidget, так и его объект State, найденные в этом сгенерированном коде. Комментарии и другой код удалены для краткости.

Мне нравятся скриншоты. Щелкните для Gists.

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

Нет движущихся изображений Нет социальных сетей

Обратите внимание, что в этой статье будут файлы gif, демонстрирующие аспекты рассматриваемой темы. Однако сказано, что просмотр таких файлов gif невозможен при чтении этой статьи на таких платформах, как Instagram, Facebook и т. Д. Помните об этом и, возможно, прочтите эту статью на medium.com.

Давай начнем.

Постройте экран

Когда вы запускаете этот сгенерированный код, вы получаете старое доброе приложение счетчика. Когда вы видите экран, подобный показанному на скриншоте выше, вы знаете, что функция build () для объекта State, _MyHomePageSate, была запущена и был возвращен виджет Scaffold. Внутри этого виджета Scaffold находится множество других виджетов. Я считаю восемь без промедления. Все они запущены и теперь отображаются на экране. Обратите внимание на красную стрелку ниже. Вы можете видеть, что после отображения одному виджету теперь назначается обработчик событий, который выполняет какие-либо действия, если и когда он был нажат.

Установить для восстановления

Знаете ли вы, что делает функция setState () объекта State? Он выполняет множество функций, но нас особенно интересует то, что он вызывает также выполнение функции build () объекта State. В результате в этом случае анонимная функция затем запускается и увеличивает переменную экземпляра объекта State с именем _counter. Затем инфраструктура Flutter получает уведомление о необходимости снова вызвать функцию build () этого объекта State, которая теперь отображает обновленное значение переменной экземпляра. Каждый раз, когда вызывается функция setState () объекта State, вскоре после этого будет вызываться его функция build ().

Подсчитайте свои сборки

Итак, если вы нажмете эту плавающую синюю кнопку, вы увидите на экране цифру 1. Снова была вызвана функция build () объекта State - для отображения свойства поля или переменной экземпляра _counter с новым целочисленным значением 1. Экран, который вы видите перед собой, буквально «перестроен» из содержимого функции build () объекта State. Понял так далеко?

Давайте посмотрим на это поближе. В код добавлен ряд функций print (). Чтобы подчеркнуть, когда создается каждый класс (т.е. когда создается каждый виджет), я явно определил конструктор как для объекта StatefulWidget, так и для объекта State, предоставив в каждом из них функцию print (). Команды печати размещены в других интересных местах, чтобы продемонстрировать «жизненный цикл» StatefulWidget и его объекта State. Нажмите на снимок экрана ниже, чтобы получить свою копию. Затем вы можете следовать.

Что происходит при запуске

Итак, с помощью этих функций print () мы можем легко увидеть, что происходит при первом запуске приложения счетчика. Как и ожидалось, глядя на экран консоли ниже, создаются два класса, составляющие StatefulWidget и объект State, а затем вызывается функция build () объекта State для отображения результирующего экрана. Пока все довольно просто.

Нажать на кнопку. Посмотрите, что происходит.

Нажмите плавающую синюю кнопку один раз, и на экране консоли появится то, что следует. Имеет смысл. Как вы знаете, вызов функции setState () объекта State приведет к повторному вызову функции build () объекта State. Хорошо выглядеть.

Вы в значительной степени поняли идею. Каждый раз, когда вы нажимаете кнопку, вы будете видеть, что функция build () объекта State вызывается снова, отображая значение переменной экземпляра объекта State, _counter. Ниже приведен снимок экрана консоли при шестикратном нажатии кнопки. Довольно просто.

Инициализировать состояние

Прежде чем продолжить, давайте немного отступим и рассмотрим этот объект State. Традиционно большая часть логики вашего приложения будет найдена, доступна и запущена в объектах State вашего приложения. Вы обнаружите, что определяете эту логику в классе, составляющем ваш объект State. Таким образом, обычным явлением для инициализации такой логики является функция initState () объекта State. Давайте продемонстрируем это сейчас, изменив наше старое приложение-счетчик и инициализируя, в данном случае, его переменную экземпляра _counter целочисленным значением 0 внутри объекта State initState ( ) функция.

Тем не менее, с помощью такого простого примера для работы я хочу передать некоторые общие практики, применяемые в более сложных приложениях, например, предоставление значений по умолчанию для таких переменных экземпляра в функции initState (). Эта функция вызывается только при первом создании объекта State, поэтому приложение будет вести себя так же, как и раньше. Обратите внимание, что команда печати также будет помещена в функцию initState ().

Все ведет себя одинаково. Однако теперь вы видите дополнительную строку на экране консоли. Опять же, вы увидите эту строку только один раз при первом создании экземпляра объекта State.

Будет сложно

Хорошо, вернемся к этому. До сих пор это было действительно просто. Это действительно простое приложение. Однако ваши приложения не будут такими до смешного простыми. Они будут более сложными. Посмотрим, смогу ли я ввести такую ​​сложность, продолжая использовать концепцию простого счетного приложения.

Два экрана; Три счетчика

А теперь я представлю вам другую версию этого счетчика. В этой версии есть три счетчика на двух отдельных экранах! В этой версии все изменилось - так, как вы могли бы видеть в более сложных приложениях. Прежде всего, по сравнению с исходным приложением счетчика, вы увидите, как StatefulWidgets запускает другие StatefulWidgets.

Ниже приведены скриншоты этого более сложного приложения счетчика. Коснитесь или щелкните снимок экрана, чтобы получить копию для себя. Вы можете видеть, что у исходного объекта State, _MyHomePageState, теперь есть собственный счетчик ?! Кроме того, новый виджет StatefulWidget под названием _FirstPage теперь отображает исходный экран!

Далее в коде вы найдете StatefulWidget «Вторая страница», который будет отображать отдельный счетчик на отдельном экране. Опять же, он приправлен командами печати, чтобы мы могли отслеживать «последовательность событий» для таких виджетов.

Итак, глядя на экран консоли, вы можете видеть, что команды печати выделяют последовательность, в которой создаются виджет и объекты состояния, и последовательность, в которой вызываются их функции. Например, при первом отображении главного экрана вы можете видеть, что задействованные объекты State сначала вызывают свои функции initState (), а затем, наконец, вызывают свои функции build (). Что касается главного экрана, то это объект State, _FirstPageState, с виджетами в его функции build (), которая отображает первый экран или первую страницу.

Следуйте за «линиями стрелок» за другими, и вы сможете увидеть логику отображения первого экрана. Каждый StatelessWidget и StatefulWidget создаются один за другим. И с каждым StatefulWidget вы можете видеть, что создается соответствующий объект State и вызываются их функции.

Ко второму

Теперь, глядя на экран консоли, если вы затем нажмете кнопку Вторая страница, вы увидите, что она переходит к третьему виджету StatefulWidget в приложении с именем SecondPage. Он создает этот виджет; он создает сопутствующий ему объект State с именем _SecondPageState. Он проходит через функцию initState () этого объекта State и, наконец, через функцию build () для отображения второго экрана. Но, как вы можете видеть в консоли, это еще не сделано.

Не обращайте внимания на первый абзац ниже. Теперь этого больше не происходит.

Но не забывайте первое

Фреймворк Flutter после отображения второго экрана снова вызывает объект State первого экрана _FirstPageState! На этот раз для вызова функции deactivate () этого объекта State. Конечно, как видите, мы еще не закончили. Затем Flutter вызывает функцию build () первого объекта State в приложении, _MyHomePageState, чтобы фактически воссоздать StatefulWidget, FirstPage. Вы можете видеть, что конструктор вызывается снова. Наконец, снова вызывается функция build () в объекте State _FirstPageState. Итак, зачем все это фреймворку Flutter ?? Мы вернемся к этому в другой раз. Вы заметили, что в этой последовательности вызовов чего-то не хватает?

Во Flutter виджеты постоянно создаются заново. Постоянно. Каждый раз, когда вы запускаете функцию build (), вы воссоздаете найденные в ней виджеты. Привыкайте к этому факту, но заметили ли вы, что не было вызвано в этой последовательности событий? Верно! Вы не видели, чтобы «первый» объект State был повторно создан и / или его функция initState () вызывалась снова! Я поместил их туда, где они должны были быть, в приведенной ниже последовательности, но на самом деле они не называются. Объект State, _FirstPageState, остался один только для того, чтобы снова запустить его функцию build (). Так почему их не позвали?

Кстати, первый объект State вызывает свою функцию build (), но не отображается из-за механизма маршрутизации Flutter. К этому времени мы уже находимся в отдельном наложении на стек маршрутов, поэтому вместо него мы представляем StatefulWidget Вторая страница и содержимое его функции build () объекта State.

Государство остается

Факт остается фактом: объект состояния «Первая страница» сохранялся даже тогда, когда его StatefulWidget был уничтожен и создан снова. Объекты состояния и, что более важно, содержимое внутри объектов состояния сохраняются, в то время как структура вокруг них перестраивается снова и снова. Это то, что я хочу, чтобы вы оценили сейчас. По задумке конструктор объекта State и initState () не запускаются снова в такой последовательности событий. Давайте посмотрим на другой пример.

Количество титулов

Обратите внимание, что в этом приложении счетчика в нижней части экрана отображается кнопка «Счетчик домашней страницы» при первом запуске. С каждым нажатием этой кнопки счетчик, отображаемый в строке заголовка приложения, будет увеличиваться. Довольно просто выглядит, правда? Однако давайте посмотрим, в чем дело.

Помните, что в этом приложении, когда вы нажимаете кнопку, вызывается функция setState (). Это означает, что вскоре после этого будет вызвана функция build (). Имейте это в виду, когда мы продолжим.

Построй свой дом

Вы можете видеть, что при нажатии кнопки «Счетчик домашней страницы» счетчик, заданный ранее в объекте State, _MyHomePageSate, увеличивается. Далее вызывается setState () этого объекта State, и вы знаете, что это означает.

Итак, вернемся к объекту State, _MyHomePageSate, его функция build () скоро будет вызвана таким образом, чтобы передать новое значение «counter» в заголовке приложения. См. Красную стрелку ниже.

Консоль знает

Опять же, когда нажата кнопка «Счетчик домашней страницы», вы снова видите на домашней странице, что вызывается функция build () ее объекта состояния (см. Снимок экрана выше) и, таким образом, обновляет строку заголовка. для приложения. Конечно, поскольку выполняется функция _MyHomePageState build (), StatefulWidget, FirstPage, создается повторно (вызывается его конструктор) и его объект State build () вызывается снова по очереди. Однако снова сам объект State не создается. В противном случае его оставляют в покое. Посмотрите, как это работает?

Подожди секунду!

Попробуем счетчик на втором экране. Вы можете увидеть последовательность событий, которая привела нас к снимку экрана, который мы видим ниже. Приложение было запущено, и была нажата кнопка с надписью «Вторая страница». Вторая страница была выведена на экран (через класс Navigator), и ее плавающая синяя кнопка была нажата один раз. Наконец, вы можете видеть, что у объекта State, _SecondPageState, вызывается функция build (), которая затем «перерисовывает» экран для отображения числа 1.

Выглядит довольно просто, правда? При каждом нажатии кнопки у объекта State _SecondPageState по очереди вызываются функции setState () и build ().

Back To First

Ну а теперь вернемся к первому экрану. Поскольку мы использовали класс Navigator для открытия и представления второго экрана, мы можем снова использовать Navigator, чтобы вернуться назад к «стеку маршрутов виджетов» и вернуться к первому экрану с помощью команды «Navigator.pop» (контекст); ». Теперь посмотрите на экран консоли ниже.

Первый уходит

Посмотрите, что происходит, когда приложение возвращается к первому экрану! Приложение фактически заново создает StatefulWidget «Первую страницу». Сначала он снова вызывает функцию deactivate () из объекта State первой страницы, затем вызывается _HomePageState build () (чтобы снова построить StatefulWidget первой страницы ). Вы можете видеть, что это происходит потому, что затем срабатывает его конструктор. Наконец, объект State этого StatefulWidget (оставленный нетронутым) снова запускает свою функцию build (). Ого! Там много чего происходит, а? Но это не все!

Поп идет в государство

После удаления виджета «Вторая страница» из стека маршрутизации он очищается из памяти, как и его объект State. Вы можете увидеть, что вызывается функция deactivate () объекта состояния «Вторая страница» (см. Ниже), а также другая функция. Тот, который может быть для вас новым. Функция dispose (). Это говорит о том, что объект State был освобожден из памяти и, как и его аналог StatefulWidget, теперь является кандидатом на сборку мусора платформы. Обратите внимание: любые «тяжелые ресурсы», такие как открытые файлы и / или открытые потоки, должны быть явно закрыты в функции deactivate () или в функции dispose (). (Некоторые говорят, что лучше использовать deactivate () вместо dispose ()).

Итак, если вы вернетесь на вторую страницу, вы обнаружите, что счетчик снова обнуляется. StatefulWidget и сопровождающий его объект State были созданы снова, когда вы вернетесь на вторую страницу через класс Navigator.

Рассчитывать на дом

Мы можем продолжить с этим. Что произойдет, например, если счетчик домашней страницы в строке заголовка на первом экране увеличивается, пока мы находимся на второй странице? Ну что ж, посмотрим. Ниже приведена последовательность нажатия кнопки «Счетчик домашней страницы» на второй странице три раза подряд. Затем мы возвращаемся к первому экрану. Мы видим, что при каждом нажатии этой кнопки StatefulWidget первого экрана воссоздается. Конечно, это потому, что объект State домашней страницы выполняет свою функцию build () (для обновления строки заголовка). В результате повторно создается StatefulWidget первого экрана и вызывается функция build () связанного с ним объекта State. Наконец, когда мы вернемся к первому экрану, мы увидим, что строка заголовка действительно отображается со значением счетчика 3.

Ключевое состояние

Вы заметили эту дополнительную кнопку на втором экране? Эта кнопка «Новый ключ»? Вы знаете, что это значит? Хорошо, я вам скажу. Он воссоздает объект State первого экрана! Первый экран; не второй экран. Я только что закончил говорить вам, что объект State на первом экране остался один, и теперь я вам это говорю. Ты можешь в это поверить?

Итак, зачем вам «очищать» объект State на экране? Потому что, и я могу сказать это по опыту, вы можете захотеть сбросить «изменяемые значения» в объекте State по той или иной причине - и Flutter легко позволяет это.

Когда вы нажимаете кнопку «Новый ключ», вы можете видеть ниже в коде, что происходит то, что частной переменной присваивается новое уникальное значение ключа. Это даст желаемый эффект. Возвращаясь к первому экрану, счетчик возвращается к нулю. Это потому, что объект State был воссоздан.

Эта переменная является переменной высокого уровня, определенной в файле dart, а не в каком-либо конкретном классе. Теперь для этого нет особых причин. Фактически, ее можно было бы так же легко определить как переменную экземпляра в объекте State, _MyHomePageState. В любом случае обратите внимание, что он определяется в файле во время компиляции, а затем назначается StatefulWidget, _FirstPage. Ниже вы можете увидеть последовательность, которая происходит после нажатия кнопки «Новый ключ».

Как видно на снимке экрана выше, когда вы нажимаете кнопку «Новый ключ», виджет StatefulWidget на первом экране, FirstPage, создается заново, как обычно. Однако что-то изменилось. Также повторно создается объект State _FirstPageState. Последовательность, выделенная красными стрелками, немного сбивает с толку, но мы рассмотрим ее. Начиная с первой красной стрелки, мы видим «исходный экземпляр» объекта State, вызывающий его функцию deactivate (). Следующие три стрелки показывают «новый экземпляр» создаваемого объекта State и вызывающего его функцию build (). Последняя стрелка указывает на «исходный экземпляр» объекта State, поскольку он вызывает функцию dispose () и готовится к повторному использованию.

Обратите внимание, что функция dispose () может появиться в любой момент после вызова функции deactivate () - сразу после или намного позже. Вот почему люди предлагают вам убрать "временные ограничения" в функции deactivate (), поскольку она последовательно и предсказуемо вызывается, когда объект State заменяется новым экземпляром.

Итак, когда кнопка Первая страница нажата для возврата к первому экрану, мы знаем, что StatefulWidget первого экрана всегда создается заново. Однако на этот раз ему назначается новый уникальный ключ. Таким образом, фреймворк удаляет исходный объект State, чтобы создать новый. Итак, поехали. Помимо тестирования виджетов, есть еще одна причина для назначения клавиш виджетам. У команды Flutter, конечно же, есть видео о том, когда использовать ключи, и он, вероятно, предоставит больше информации.

Получите жизненный цикл

Обратите внимание, что бесплатная статья Жизненный цикл приложения Flutter отлично читается. Он представляет класс WidgetsBindingObserver, который вы будете использовать для реализации дальнейших обработчиков событий жизненного цикла. Те из вас, у кого есть опыт работы с Android, узнают эти обработчики событий как те, что находятся в классе Activity. Ниже приведен снимок экрана некоторых из этих обработчиков событий обратного вызова, которые в то время доступны вам.

На данный момент этого достаточно. Некоторые вещи, о которых следует помнить при работе с множеством ваших StatefulWidgets - и их состояниями.

Ваше здоровье.

→ Другие рассказы Грега Перри