Найдите нужный объект State для вызова его setState ()

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

Однако со временем, работая с Flutter, вы также обнаружите, что доступ к объекту State легче сказать, чем сделать откровенно. В настоящее время с Flutter объекты состояния не так легко доступны. Честно говоря, целые фреймворки, такие как Provider и ему подобные, были созданы для того, чтобы в той или иной степени сделать их легко доступными - все для того, чтобы вызывать их функцию setState (). Или в случае Provider, который обходит SetState () и вызывает функцию класса Element, markNeedsBuild ().

Однако я использовал пакет Dart, который собирает все объекты State приложения в объект Map, чтобы они были доступны для быстрого доступа. Конечно, мне это нравится, и я использую его постоянно - факт, что я написал пакет Dart, не имеет значения.

Опять же, функция setState () объекта состояния станет для вас чрезвычайно важной при разработке во Flutter, и я написал миксин, который вы можете присоединить к любому классу состояния, чтобы предоставить вам простой, но безопасный доступ к этой функции. . Он позволяет вам получить доступ к объекту State в любом месте вашего приложения, и если вы не можете получить к нему доступ, это только потому, что его еще нет - он еще не создан. Если это произойдет, ваша попытка вернет null. Вы должны знать свое собственное приложение - это ваше дело. Опять же, он компенсирует такие случаи, изящно терпя неудачу с нулевым значением. Я бы посоветовал проверить значение null, когда это уместно. Фактически, я покажу вам в этой статье обстоятельства, в которых вам следует это сделать. Пакет называется state_set и содержит обширный пример приложения, демонстрирующий его использование. Мы собираемся рассмотреть этот пример приложения в этой статье.

Мне нравятся скриншоты. Нажмите «Заголовок» для Gists.

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

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

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

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

Иду домой

Пример приложения показан ниже. Это очень простой пример приложения. Это старый режим ожидания - приложение счетчика запусков. Приложение, которое вы видите при создании нового проекта Flutter. Ну, этот немного другой. У него три счетчика. Фактически, в этом пакете Dart есть три примера приложений, которые вы можете просмотреть - все с тремя счетчиками. Сегодня мы просто рассмотрим одну из них.

Обратите внимание, как увеличивается счетчик на предыдущем главном экране, даже когда мы в данный момент находимся на втором экране. Во Flutter это означает, что где-то вызывается функция setState (). Пакет Dart и его класс StateSet разрешат доступ к объекту State, который ранее был создан на главном экране, - к тому самому объекту State, который создал главный экран. Это здорово! Вскоре вы поймете, почему.

Снимок экрана второго экрана также отображается ниже. Первая стрелка ниже указывает на функцию onPressed () FloatingActionButton, вызывающую собственную функцию onPressed () этого объекта State. Обратите внимание, что он дополнительно заключен в собственную функцию setState (). Таким образом, функция onPressed () увеличивает счетчик, а setState () затем отображает увеличенное значение. Опять же, как вы, возможно, уже знаете, именно setState () сообщает фреймворку Flutter снова вызвать функцию build () объекта State и, следовательно, заново построить второй экран. Вы видели это снова и снова. Совершенно нормально.

Однако виджет RaisedButton также присутствует на втором экране, что является действительно интересной частью. У него тоже есть функция onPressed (), и обратите внимание, что происходит в этой функции. Вы видите код, который позволяет также увеличивать счетчик на главном экране. Огромный. Вторая стрелка ниже указывает на статическую функцию of из класса StateSet, извлекающую объект State для StatefulWidget, HomeScreen, чтобы обновить приращение обратно на главном экране . Видишь, как это работает? Вы, наверное, думаете: «Что в этом такого?»

Показать граф

Что ж, если эта функция of () закомментирована (см. Ниже), нажатие кнопки «Домашний счетчик», по-видимому, теперь не имеет никакого эффекта, поскольку, вернувшись на главный экран, вы обнаружите, что его счетчик остается на нуле. Однако он продолжает увеличиваться - его экран просто нужно обновить. При этом приведенный ниже код был дополнительно изменен, чтобы вместо этого обновлять главный экран только при увеличении счетчика второго экрана. Вы можете увидеть это по первой стрелке ниже. Вам понадобится setState (), чтобы отражать любые изменения в интерфейсе вашего приложения. При этом вы со временем обнаружите, что работая с Flutter, получение доступа к соответствующему объекту State имеет важное значение для типичных функций, которые вы хотите использовать в своих приложениях.

Что это за состояние?

Сначала давайте немного отступим и взглянем на функцию setState (). Снимок экрана с этой функцией показан ниже с левой стороны. Как вы знаете, он принимает функцию без аргументов и обычно описывается как функция «VoidCallback», поскольку она также не возвращает никакого результата. Первая красная стрелка ниже показывает, где затем вызывается переданная функция. Однако обратите внимание: если возвращается какой-либо результат, он активно приводится как тип, динамический. Это означает, что может быть возвращено все, с любым типом данных. Хотя результата не ожидается, это делается, поэтому, если результат есть, его можно проверить с помощью следующей функции assert. Эта функция (присутствует только во время разработки) проверяет, имеет ли результат тип Future. Это потому, что результат типа Future на данном этапе не подходит. Любой «неполный» объект Future, присутствующий во время следующей запланированной «перестройки» интерфейса приложения, может вызвать непредсказуемые побочные эффекты, поэтому функция assert выдаст ошибку, если объект типа Future, был возвращен в результате.

При работе с декларативным языком программирования Flutter вполне вероятно, что через несколько микросекунд произойдет следующая запланированная «перестройка». Движок Flutter заново раскладывает и перерисовывает текущий интерфейс, и любые «грязные» (отмеченные для перестроения) элементы и, следовательно, связанные с ними виджеты в дереве виджетов, восстанавливаются. Фактически, если объект Future не был возвращен, последняя стрелка ниже показывает, что собственный связанный элемент объекта State (и, следовательно, его StatefulWidget) также помечен как «грязный», поэтому он также перерисовывается (и обновляется) при следующей запланированной перестройке. Видишь, как это работает?

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

Объявить свое состояние

Давайте быстро отметим одну из характеристик фреймворка декларативного программирования Flutter. Смотрите первый снимок экрана ниже? Будучи уверенным, что увеличение переменной counter не вернет результат типа Future, обратите внимание, что его выражение даже не передается в функцию setState (), заключенную внутри функции VoidCallback. . Вместо этого сначала увеличивается значение переменной, а затем вызывается функция setState () (передающая пустую функцию VoidCallback). Хотя рекомендуется заключать в функцию setState () операцию, которая изменит "состояние" вашего приложения, вы можете видеть, что это не обязательно. Пустая функция, переданная в функцию setState (), встречается даже в самой среде Flutter. Приведенный ниже код относится к «оригинальному» приложению счетчика, и его нет в пакете Dart. Он здесь, чтобы продемонстрировать нам кое-что прямо сейчас.

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

Получить состояние

Хорошо, давайте вернемся к нашей теме. Когда вы впервые изучали Flutter, вы могли предположить, что будет относительно легко получить ссылку на, казалось бы, важный объект State объекта StatefulWidget. Однако по замыслу это не так. Например, в каждом StatefulWidget нет общедоступного свойства класса, ссылающегося на соответствующий объект State. Вместо этого на него есть ссылка в частном поле класса под названием _state, - в совершенно другом классе.

Например, StatefulWidget на главном экране (см. Ниже) вызывает функцию createState () для создания своего объекта State, _HomeScreenState. Однако на самом деле именно объект StatefulElement объекта StatefulWidget (см. Ниже), когда он создает экземпляр, инициирует вызов функции createState (). Это тот объект «Element», который затем сохраняет ссылку на объект State в частном поле с именем _state. Таким образом, объект State недоступен. Вскоре вы обнаружите, что при работе с Flutter безопасное обеспечение легкодоступности объектов состояния становится очень желательным (опять же, целые фреймворки были написаны, чтобы сделать его доступным), и в этой статье я предлагаю еще один подход. На мой взгляд, лучший.

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

Публичный vs Частный

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

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

Контроль государства

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

Может ли второй экран получить доступ к главному экрану? Да! Виджет StatefulWidget на главном экране, HomeScreen, не имеет символа подчеркивания в начале. Проверять! Может ли второй экран увеличивать счетчик главного экрана? Да! Он знает, что нужно вызвать на главном экране «функцию счетчика приращения», onPressed, потому что она тоже не начинается с символа подчеркивания. Проверять! StatefulWidget на главном экране имеет четко определенный API для работы. Видите ли, Flutter позволяет вам снова и снова создавать экземпляры объекта StatefulWidget или объекта StatelessWidget для доступа к его внутренней работе, даже не вызывая функцию build () этого виджета. Огромный!

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

Найдите свой штат

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

При этом обратите внимание, что функция onPressed () в StatefulWidget, в свою очередь, вызывает собственную функцию onPressed () соответствующего объекта State, используя статическую функцию в из класса StateSet. Именно в объекте State мы наконец видим фактическое приращение, включающее частное целочисленное поле с именем _counter. Это та статическая функция, to, которая надежно извлекает «самый последний объект State, созданный функцией createState () этого StatefulWidget. Теперь я объясню это.

Опять же, Flutter - это платформа декларативного программирования. Это означает, что его виджеты многократно уничтожаются и восстанавливаются на протяжении типичного жизненного цикла приложения. Даже объекты State могут быть удалены (уничтожены) и воссозданы снова и снова при определенных обстоятельствах - например, если и когда новое значение Key передается соответствующему StatefulWidget. Опять же, «держать объект State вне досягаемости» во Flutter было задумано. Если вы не будете осторожны, вы можете получить доступ к «удаленному» объекту State при попытке запустить его всегда важную функцию setState (). Однако класс StateSet отслеживает все это и предоставляет вам только «активные в данный момент» объекты State в любой момент времени в вашем приложении.

Вызов статической функции StateSet to в StatefulWidget на главном экране обеспечивает получение соответствующего объекта State. Даже в этом случае обратите внимание, что оператор ?. используется в функции onPressed (). Это в случае, если по какой-то неизвестной причине объект State не был создан. Хотя, поскольку мы вызываем статическую функцию прямо внутри StatefulWidget, HomeScreen, я бы предположил, что маловероятно, что она вернет значение null. На самом деле, я скорее всего возьму этот оператор в копию репозитория Github. Вы можете не найти его, когда попробуете пакет Dart. Однако, возможно, стоит остаться. В конце концов, разработчик может забыть назначить миксин StateSet классу State (см. Ниже). Все, что нужно для того, чтобы все это работало, - это небольшая оговорка «с».

Состояние виджета

На третьем экране примера приложения есть две кнопки, которые позволяют увеличивать счетчики на главном экране и на втором экране. Снимок экрана этих двух кнопок показан ниже. Опять же, выделенный стрелками ниже, код просто демонстрирует вам средства для получения объекта State конкретного StatefulWidget. В данном случае используется статическая функция of из класса StateSet. Кстати, именно здесь оператор условного доступа к члену (?.) был бы более благоразумным, поскольку внешний виджет StatefulWidget по какой-то причине может не быть создан. Если он возвращает null, ошибки нет - функция setState () просто не срабатывает, и интерфейс не обновляется. Затем программист должен выяснить, почему нет обновления - по крайней мере, не произойдет сбоя!

Корень этого

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

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

На снимке экрана ниже объекта State, _MyAppState, видно, что его функция setState () была немного изменена. В этой функции «новый ключ» назначается полю класса _homeKey. Обратите внимание: это то же самое поле класса, которое передается в виджет StatefulWidget на главном экране, HomeScreen, выделенное первой стрелкой ниже. Вы знаете что это значит.

Во Flutter, если StatefulWidget назначается новый ключ, сопровождающий его объект State уничтожается и создается новый. Следовательно, в этом случае новый объект State означает новый счетчик. Между прочим, не имеет значения, что StatefulWidget имеет фабричный конструктор. Движок Flutter распознает новое значение Key, появившееся с момента последнего перестроения кадра, и знает, что нужно уничтожить и перестроить сопутствующий объект State. Итак, в этом случае с новым объектом State новый счетчик по умолчанию равен нулю. Видишь, как это сработало?

Карта штата

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

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

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

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

Подпишитесь на сообщество Flutter в Twitter, чтобы увидеть больше интересных статей: https://twitter.com/FlutterComm