Подробный обзор виджета FutureBuilder от Flutter

Так что же такое виджет FutureBuilder?

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

Итак, что такое виджет FutureBuilder? Как оказалось, виджет FutureBuilder - это StatefulWidget. В этой статье мы рассмотрим, как это работает.

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

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

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

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

Мы будем использовать пример из Поваренной книги Flutter, получение данных из Интернета, чтобы продемонстрировать FutureBuilder в действии. Ниже приведен снимок экрана части этого примера, отображающего StatelessWidget и то, как его параметр, post, предоставляется виджету FutureBuilder как экземпляр объекта Future.

В документации FutureBuilder ‹T› class сразу подчеркивается, что указанное Future должно быть получено раньше и уже инстанцировано. Его не следует создавать прямо с FutureBuilder - когда FutureBuilder вызывается и создается. В противном случае этот объект Future будет создаваться с помощью FutureBuilder снова и снова при каждом новом вызове функции build (), содержащей их, в некоторых случаях, возможно, не позволяя завершить асинхронную операцию.

Будущее в дженериках

Глядя на первую строку самого класса FutureBuilder, мы видим, что общие типы данных используются с прописной буквой T и ‹…› нотацией. Он используется, чтобы указать, какой тип данных возвращается в результате асинхронной операции. В примере с поваренной книгой тип данных - это класс типа Post. Обратите внимание, что при использовании FutureBuilder типом может быть любой из встроенных типов, предлагаемых Flutter.

Как это происходит

На скриншоте из приведенного выше примера видно, что виджет FutureBuilder вызывается как «дочерний» аргумент виджета Center. Давайте сделаем это в «замедленной съемке» и посмотрим, что будет дальше с вызовом FutureBuilder в этот момент. Что обычно занимает миллисекунды, мы перейдем к следующему шагу.

Все дело в инициализации

Помните, FutureBuilder - это StatefulWidget. Таким образом, вскоре будет вызвана функция initState () сопутствующего объекта State объекта StatefulWidget. Именно здесь все начинает катиться по-настоящему. На скриншоте ниже видно, что объект типа AsyncSnapshot создается в функции initState ().

Обратите внимание на то, что передается в конструктор AsyncSnapshot: перечислимый тип со значением ConnectionState.none и значение initalData parameter FutureBuilder, если оно есть. Перечислимый тип ConnectionState используется как виджетом FutureBuilder, так и виджетом StreamBuilder для передачи информации о ходе выполнения их респектабельных асинхронных операций. Мы поговорим об этом дальше, а пока что это за widget.initialData?

Дайте начальное значение

Ниже приведен снимок экрана другого примера, в котором FutureBuilder предоставляется значение «начальных данных» - не класс типа, Post, а логический тип данных, false . Это значение затем передается конструктору AsyncSnapshot в функции initState (), указанной выше как widget.initialData.

В нашем примере с поваренной книгой такое начальное значение не предоставляется, поэтому значение параметра initialData будет установлено равным нулю. Фактически, этот параметр является свойством класса FutureBuilder. Он включает в себя тип данных Generics, T, и поэтому в данном случае способен хранить объект класса типа Post.

Однако, если такой параметр был передан, FutureBuilder, скорее всего, построит виджет на основе этого значения. Этот виджет будет отображаться до тех пор, пока связанная асинхронная операция не завершится и не предоставит свое результирующее значение указанному объекту Future. Вы так далеко следите? Не волнуйтесь, я еще раз пройду через это. Мы играем в замедленной съемке, помнишь?

У будущего есть класс

Опять же, возвращаясь к примеру с поваренной книгой, конкретный объект Future является классом типа Post и предоставляется методом fetchPost (). Этот метод вызывается сразу в функции main () и предоставляется как параметр для StatelessWidget, MyApp.

На снимке экрана ниже вы можете увидеть, что метод fetchPost () возвращает объект типа Future ‹Post›. Именно в этом методе вызывается веб-сайт.

Веб-сайт был вызван для получения данных, а это требует времени. Следовательно, в примере с поваренной книгой реализован FutureBuilder - «ждать» завершения выборки данных. При обнаружении команды await путь выполнения будет выходить за пределы метода в этот момент, возвращая «неполный» объект Future. Затем приложение продолжает вызывать FutureBuilder, который получает этот «неполный» объект Future в качестве указанного объекта Future.

Все начинается со сборки

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

Обратите внимание, что функция build (), в свою очередь, вызывает свойство builder StatefulWidget. StatefulWidget - FutureBuilder. На снимке экрана ниже, показывающем пример поваренной книги, в котором сначала определяется виджет FutureBuilder, видно, что это свойство является именованным параметром, которому назначен анонимный метод. Вы можете видеть, что этот метод принимает в качестве параметров объект BuildContext и объект AsyncSnapshot.

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

Круговой прогресс вначале

Итак, с этой первой сборкой дерева виджетов в центре экрана появляется «счетчик загрузки». Итак, почему это так? Помните, что «неполный» объект Future типа Post был передан FutureBuilder в начале всего этого, и когда функция build () FutureBuilder затем выполняется этот анонимный метод.

Как следствие, значение snapshot.hasData, будет установлено в false. Это потому, что не было указано initialData в качестве параметра, а объект Future является «неполным». Впоследствии, поскольку ошибки не было (snapshot.hasError), вызывается виджет CircularProgressIndicator.

Null не имеет данных

Опять же, при первой сборке значение snapshot.hasData является ложным и приводит к отображению счетчика загрузки в центре экрана. Ниже вы можете увидеть, что это логическое выражение snapshot.hasData происходит от «получателя» в объекте AsyncSnapshot , который проверяет, имеет ли свойство data значение NULL.

Данные типа T

При использовании Generics объект AsyncSnapshot, определенный в функции initState () FutureBuilder, будет иметь свойство с именем data, определенное как класс типа Post . Опять же, если бы в FutureBuilder было передано значение initialData, для свойства data было бы установлено это значение. В противном случае, как и в этом случае, для свойства data устанавливается значение null, а объект Future еще не может предоставить результирующее значение.

Есть ошибки?

Хорошей практикой является также включение в анонимный метод, назначенный параметру, builder, вызов геттера, hasError. Он также находится в объекте AsyncSnapshot и также включает проверку того, имеет ли свойство значение NULL или нет. В данном случае это свойство с именем error.

Итак, что будет дальше?

В этом примере кулинарной книги с ее первой сборкой объект Future является неполным. Что будет дальше? Что произойдет дальше, зависит от самого указанного объекта Future. В конце концов, на этом этапе асинхронная операция еще не завершена. Помните тот метод _subscribe (), который вызывается в функции initState () FutureBuilder? Именно там происходит настоящее волшебство.

Где происходят чудеса

Снимок экрана метода _subscribe () показан ниже. Именно в этом методе функция then определяется для объекта Future, изначально указанного с FutureBuilder. Итак, когда объект Future завершит свою асинхронную операцию, будет вызван анонимный метод, определенный и переданный как функция обратного вызова в функции then. В этом случае он вызывается при получении данных с веб-сайта.

Обратите внимание, что параметр этой конкретной функции обратного вызова является универсальным типом, который вы видели ранее, - типом T. Таким образом, параметр типа класса, Post, передается в функцию обратного вызова.

Обратите внимание, что дополнительный именованный параметр, определяющий функцию обратного вызова onError, также передается в функцию then. Он вызывается, если объект Future завершает свою асинхронную операцию с ошибкой. В случае ошибки в качестве параметра предоставляется объект класса. В большинстве случаев это будет тип класса Exception.

Будущее завершается

Что будет дальше? Что ж, в этом примере, пока эти данные не будут получены с веб-сайта, в центре экрана есть счетчик, выполняющий свою работу. Однако, как вы видите ниже, когда асинхронная операция завершается и данные извлекаются с веб-сайта либо успешно, либо с ошибкой, создается новый неизменяемый объект AsyncSnapshot с перечислимым типом ConnectionState.done , перешли к нему.

Конечно, из-за вызова функции setState () функция build () FutureBuilder будет запущена снова, но теперь с передачей объекта AsyncSnapshot, _snapshot с его «состоянием подключения», установленным на ConnectionState.done.. Повторно передается анонимному методу, назначенному свойству «builder». См. ниже. Это будет происходить при каждом вызове функции setState ().

Состояние подключения будущего потока

Давайте еще раз рассмотрим поток управления. При первой сборке мы знаем, что объект Future еще не готов (какое-то время мы видим счетчик в центре экрана). Следовательно, если мы посмотрим на моментальный снимок в методе «builder» в этот момент, мы обнаружим, что для параметра snapshot.connectionState установлено значение ConnectionState.waiting.

Чтобы продемонстрировать это, есть точки останова, установленные в операторе Switch Case, который теперь вставлен в функцию build () ниже. В первой сборке он останавливается там, где текущее состояние соединения установлено на ConnectionState.waiting.

Теперь, где состояние соединения изменилось с ConnectionState.none на ConnectionState.waiting? Хорошо, я вам покажу. Помните, что при использовании StatefulWidgets «одноразовый» вызов функции initState () вызывается связанным с ней объектом State. В виджетах FutureBuilder сначала создается объект AsyncSnapshot с параметром ConnectionState.none, а затем вызывается функция _subscribe ().

Установить "Мы ждем"

Именно в функции _subscribe () мы видим новое состояние подключения. По самой своей природе асинхронные операции требуют времени. В большинстве случаев объект Future еще не завершен к моменту вызова функции _subscribe (). Даем этой функции время для определения функции then объекта Future. Обратите внимание, что после определения функции then создается новый неизменяемый объект AsyncSnapshot, который заменяет старый и теперь предоставляет набор состояний подключения «ожидание».

Итак, для этого примера из поваренной книги, вот как все будет оставаться на секунду или около того: виджет CircularProgressIndicator, отображаемый в центре экрана, и объект AsyncSnapshot с состоянием подключения «ожидание». Конечно, я немного изменил ситуацию с помощью оператора Switch Case в функции build (). Вместо счетчика, отображаемого в центре экрана, у нас просто текст «ConnectionState.waiting» в центре экрана. В любом случае, когда асинхронная операция действительно завершится (данные получены с веб-сайта), все изменится.

Когда все готово, это данные

На скриншотах ниже мы видим прогресс асинхронной операции. Во время ожидания мы видим, что был вызван виджет Text для отображения строки «ConnectionState.waiting» в центре экрана. Однако мгновенно устанавливается значение snapshot.connectionState: ConnectionState.done. В этот момент там была установлена ​​точка останова.

Обратите внимание, что виджет «Текст» не сопровождается командой возврата. Вместо этого выполнение продолжит поиск оператора if с логическим выражением snapshot.hasData, установленным в значение true. Следовательно, поскольку свойство data относится к типу класса, Post, его свойство title предоставит строку JSON еще одному текстовому виджету, возвращаемому построителем. метод, а затем отобразится на экране. Это так просто.

Так что же там произошло?

После завершения асинхронной операции будет запущена функция обратного вызова в функции then объекта Future. Итак, в функции then для этого конкретного объекта Future, когда выборка данных была окончательно завершена, параметр data был передан конструктору withData для создания еще одного новый неизменяемый объект AsyncSnapshot.

Поскольку мы являемся универсальным типом данных, мы знаем, что этот параметр, data, относится к типу класса Post. Кроме того, значение ConnectionState.done также передается в конструктор. Все это выполняется в функции setState (), поэтому функция build () будет вызываться снова. На этот раз свойство snapshot.hasData будет иметь значение true, что позволит приложению, наконец, продолжить работу с текстом JSON, отображаемым на экране.

Что происходит по ошибке?

Так что было здорово, когда все сошлось и сработало. Давайте посмотрим, что произойдет, если мы введем искаженный URI в пример с поваренной книгой. Мы увидим, как виджет FutureBuilder и сопровождающий его объект AsyncSnapshot реагируют на сбой асинхронной операции.

На скриншоте ниже мы видим, что ошибка HTTP 404 Not Found присвоена response.statusCode, и вместо «полного» объекта Future типа класса Post, объект Exception с вместо этого создается сообщение "Не удалось загрузить сообщение". Асинхронная операция завершена с ошибкой.

Итак, если и когда вы сами разрабатываете «асинхронную операцию» в своих приложениях, не забывайте генерировать исключения, когда это необходимо, зная, что виджет FutureBuilder имеет средства «отлавливать» такие ошибки. Теперь вы знаете, что существует функция then с параметром onError, которому назначен метод.

Итак, в примере Cookbook в этом случае, когда выполнение функции fetchPost () завершается ошибкой, мы оказываемся в функции then указанного объекта Future. Объект исключения, показанный на скриншоте выше, теперь передается в функцию обратного вызова onError.

Опять же, при выполнении функции setState () новый объект AsyncSnaphot (типа Post) передается в функцию build () FutureBuilder. На этот раз, однако, предоставлен объект типа Exception.

Снова вызывается функция builder () и, следовательно, запускается анонимный метод. На этот раз в объекте AsyncSnapshot, снимке, "нет данных". Вместо этого выражение snapshot.hasError имеет значение true, и отображается фактическое сообщение об ошибке «Exception: Failed to load post».

Обратите внимание, что свойство error в объекте AsyncSnapshot snapshot имеет тип Object, а не конкретно тип Exception. Итак, почему это так?

Свойство error в объекте AsyncSnapshot snapshot имеет тип Object, поэтому вы можете назначить любой тип класса, кроме Exception. Класс Object, в конце концов, является базовым классом всех объектов в Dart, и поэтому разработчик может назначить любой объект, который он сочтет подходящим, если и когда асинхронная операция завершится неудачно. Это может быть объект, расширяющий класс, например Exception, или класс, который они создали сами. Это дает вам варианты. Мы любим варианты.

TL; DR.

Этого должно быть достаточно. Вы так не думаете? Остальная часть этой статьи - просто подливка.

Что с этим callbackIdentity?

В функции _subscribe () вы заметите два оператора if, включающие следующее выражение: _activeCallbackIdentity == callbackIdentity. Они находятся непосредственно перед двумя функциями setState (). Это небольшой трюк, который включает использование базового класса для всех объектов Dart, называемых… .хорошо… .Object. Этот объект определяется в начале функции и назначается переменной final: callbackIndentity = Object ();

Видите ли, с асинхронными вещами многое может произойти между моментом вызова функции _subscribe () и моментом, когда асинхронная операция, наконец, завершится. Другими словами, к моменту завершения асинхронной операции могут возникнуть обстоятельства, при которых для этих операторов if по той или иной причине может быть установлено значение false. (т.е. выражение _activeCallbackIdentity == callbackIdentity больше не соответствует действительности.)

Например, этот небольшой трюк предотвращает вызов функции setState () в объекте State, который уже был удален (т. Е. Виджет FutureBuilder был прекращен). В другом случае это предотвращает вызов функции setState () сразу после создания экземпляра нового указанного объекта Future, потому что виджет FutureBuilder был «перестроен» по той или иной причине, вызвавшей свойство _activeCallbackIdentity, чтобы установить значение null.

Например, в течение жизненного цикла самого мобильного приложения StatefulWidget, который является FutureBuilder, может быть перестроен. Это приведет к вызову функции didUpdateWidget () объекта State, которая принимает в качестве параметра копию «старого» виджета FutureBuilder.

В этой функции при просмотре первого оператора if, если указанный объект Future также каким-либо образом изменился, вызывается функция _subscribe (). Однако, если функция _subscribe () была вызвана раньше, потому что _activeCallbackIdentity! = Null, функция _unsubscribe () также вызывается так, чтобы установите для свойства _activeCallbackIdentity значение null. Затем создается экземпляр нового объекта AsyncSnapshot с теми же данными, но ему назначается состояние подключения «none», которое вскоре после этого заменяется еще одним совершенно новым объектом AsyncSnapshot с состоянием подключения «ожидание» при последующем вызове _subscribe ().

Будущее предопределено

Опять же, выражение _activeCallbackIdentity == callbackIdentity также используется, потому что может быть случай, когда асинхронная операция объекта Future завершается только для того, чтобы обнаружить, что виджет FutureBuilder был прерван и объект State утилизирован. Другими словами, выражение _activeCallbackIdentity == callbackIdentity является ложным, поскольку для свойства _activeCallbackIdentity установлено значение null.

Дополнительное будущее?

Вы заметили, что в функции _subscribe () код заключен в один оператор if? На первый взгляд, это означает, что FutureBuilder вообще не должен предоставлять объект Future своему конструктору. Однако это означает, что предоставленный объект Future может иметь значение null в тот или иной момент в течение жизненного цикла FutureBuilder.

Строитель просто необходим

Глядя на класс FutureBuilder, мы видим, что на самом деле конструктору необходимо передать свойство builder. Мы видим аннотацию @required, и оператор assert подчеркивает этот факт. Однако по самой природе именованных параметров параметры конструктора future и initialData являются необязательными. Следовательно, свойство future может иметь значение null.

Нет будущего Нет данных Нет ошибок

Что произойдет, если вы не предоставили объект Future для этого примера из поваренной книги? Надеюсь, после прочтения этой статьи вы сможете сделать вывод, что в центре экрана появится счетчик.

Почему нет будущего?

Опять же, параметр Future не является необязательным. Это необходимо для того, чтобы разрешить переданному объекту Future быть нулевым в какой-то момент времени - обычно в начале. Например, если приложение-пример поваренной книги имеет FutureBuiler в объекте State, а не в StatelessWidget, это означает, что FutureBuilder может вызываться снова и снова (виджет перестраивается снова и снова), и, таким образом, позволяя поставляемому объекту Future быть null по той или иной причине во время одного из этих вызовов.

Если и когда оно равно нулю, вы уже знаете, что произойдет. Построитель сработает, состояние соединения будет ConnectionState.waiting, а snapshot.hasData и snapshot.hasError будут ложными.

Это будущее с тогдашним

Итак, что же в двух словах о FutureButilder? По сути, FutureBuilder - это объект Future, которому назначен метод then внутри StatefulWidget. Анонимная функция, определенная в методе then, запускает функции setState (), когда асинхронная операция, наконец, завершается успешно или нет. Это, конечно, запускает функцию build () включающего объекта State, позволяя анонимной функции, предоставленной именованным параметром FutureBuilder, builder, снова запускаться, возвращая соответствующий виджет на основе результата асинхронного операция.

Красный.

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

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