Перевод статьи подготовлен в преддверии старта курса «Flutter Mobile Developer».
Также приглашаем записаться на открытый вебинар по теме «Пишем Flutter-приложение с использованием Redux». На вебинаре участники вместе с экспертом разберут главные особенности Redux и напишут небольшое приложение с его использованием, а также обсудят, как хорошо Redux масштабируется в перспективе. Присоединяйтесь!
Моя предыдущая статья Разборщик (Parsing Complex) JSON (JavaScript Object Notation) во Flutter получила много хороших отзывов от людей, которые начинают работать во Flutter. И одним из самых популярных, часто задаваемых вопросов (FAQ) от начинающих был: «Как сделать то же самое с API запросами?»
Твоё желание исполнилось, мой друг.
Давайте поработаем с простым API, который легко доступен без обязательной аутентификации.
HTTP методы, поддерживаемые JSONPlaceholder
. . .
GET : /POSTS/1
Давайте проверим, как будет выглядеть наш ответ, когда мы попадем на эту конечную точку API. Вы можете использовать программу Postman или просто вставить эту ссылку в ваш браузер. Помните, что если вы работаете с API, которые требуют аутентификации и передачи токенов авторизации с помощью HTTP заголовков, рекомендуется использовать программу Postman.
Давайте создадим для данной JSON-структуры модельный класс.
Вот небольшой совет. Вы можете либо использовать все знания, полученные из моей статьи о разборе JSON-комплекса (Parsing Complex JSON), либо сэкономить свое время, использовав эту маленькую утилиту-конвертер. Я только недавно его открыл и влюбился.
Помните, что надо изменить имя (Name) класса и тип источника (Source Type). Не забудьте сменить язык на Dart.
Этот инструмент создаст для вас модельные классы, заводские настройки и методы преобразования. Должен выглядеть так.
Давайте сосредоточимся на нашем postFromJson
методе.
Post postFromJson(String str) {
final jsonData = json.decode(str);
return Post.fromJson(jsonData);
}
str
— это просто наша JSON-строка. После декодирования str
, jsonData
выглядит так.
Он загружается в Post.fromJson
, чтобы этот стандартный способ мог создать для вас новый объект Post
, который вы сможете использовать в вашем приложении.
Эй, почему мы посылаем строку (Map
) в Post.fromJson
?
factory Post.fromJson(Map<String, dynamic> json){
...
}
Да, потому что Post.fromJson
требует аргумент типа Map
. А то.
Теперь давайте вызовем наш API
В-основном, инструменты вызова API находятся в другом файле, допустим, в файле services.dart
.
String url = 'https://jsonplaceholder.typicode.com/posts';
Future<Post> getPost() async{
final response = await http.get('$url/1');
return postFromJson(response.body);
}
Примечание: Не забудьте импортировать необходимые пакеты. Проверьте наличие этого файла для необходимого импорта.
Женщина, разъясни код!
Итак, до сих пор мы говорили о JSON-строке, но у нас ее еще нет. Потому что мы никогда не обращались к API. Так что давайте сначала сделаем эту работу.
Способ getPost()
вызовет конечную точку API, которая определена в url. И мы получаем JSON-строку в response.body
, которую мы должны отправить по адресу postFromJson
, чтобы он мог сделать это преобразование магическим.
Но почему возвращаемый тип Future
, а не Post
?
Ну, хорошо.
Мы делаем сетевой запрос. Очевидно, мы не получим моментального отклика сразу же в момент обращения к API. Это займет некоторое время. По определению, A Future используется для отображения потенциального значения, или ошибки, которая будет доступна в какой-то момент времени в будущем. Так как наш ответ также будет доступен через некоторое время в будущем, мы используем Futures.
Поскольку у нас есть сетевой запрос, нам, очевидно, нужен асинхронный способ вызова API. Вот где нам нужен async
and await
. Простыми словами, async
— это ключевое слово, которое делает ваш метод асинхронным. В async функции, когда мы натыкаемся на await
(ожидание), вычисляется следующее выражение, и выполняющаяся в данный момент функция приостанавливается до тех пор, пока мы не получим результат. В этом случае, до тех пор, пока мы не достигнем благоприятного исхода или произойдет ошибка.
А как насчет создания пользовательского интерфейса (UI) для полученного ответа?
Да, я дошел до этого. Очевидно, что если мы получим наш ответ в будущем, то пользовательский интерфейс, зависящий от ответа, также должен быть в будущем.
Почему?
Потому что ваш пользовательский интерфейс (UI) будет создан, как только приложение запустится, но вы не получите отклика API, как только ваше приложение запустится. Таким образом, если ваш пользовательский интерфейс зависит от значений ответа API, то это приведет к множеству ошибочных результатов.
И чтобы решить эту проблему, у нас есть…
Будущее будущих (событий) (The Future of Futures) : FutureBuilder
Проще говоря, используйте FutureBuilder
для построения виджета, когда речь идет о фьючерсах (Futures). Добавим следующие строки кода в функцию построения вашего пользовательского интерфейса (UI).
FutureBuilder<Post>(
future: getPost(),
builder: (context, snapshot) {
return Text('${snapshot.data.title}');
}
)
FutureBuilder
также является виджетом, так что вы можете либо прикрепить его непосредственно к вашему Scaffold
, либо прикрепить его в качестве дочерней программы к любому понравившемуся вам виджету.
FutureBuilder имеет два основных свойства — future
и builder
. future нужен объект future
и getPost()
, который возвращает future
.
Таким образом, future
будет использовать метод getPost()
, который сделает свой сетевой вызов по мановению волшебной палочки и вернет результат в snapshot из builder. Теперь просто создайте любой понравившийся виджет с заданным результатом. Просмотр списка? Хранилище с текстом? Да что угодно!
Внимание: Здесь FutureBuilder
имеет вид, который также является возвращаемым типом функции getPost()
. Так что какой бы тип не возвращала ваша будущая функция, это должен быть тип вашего FutureBuilder
.
А теперь, что если я хочу такого поведения. Пока я жду результатов, я хочу показать пользователям CircularProgressIndicator
и, как только будет готов результат, показать виджет Text.тFutureBuilder
также облегчает эту задачу.
if(snapshot.connectionState == ConnectionState.done)
return Text('Title from Post JSON : ${snapshot.data.title}');
else
return CircularProgressIndicator();
И предположим, что я хочу показать конкретный пользовательский интерфейс для таких ошибочных ситуаций, как отсутствие подключения к Интернету?
if(snapshot.connectionState == ConnectionState.done) {
if(snapshot.hasError){
return ErrorWidget();
}
return Text('Title from Post JSON : ${snapshot.data.title}');
}
Существуют и другие методы, такие как snapshot.hasData
и другие ConnectionStates
, например ConnectionState.waiting
, ConnectionState.active
. Я бы посоветовал вам испытать их все, чтобы создать более качественные приложения.
. . .
POST /посты
Эй, это было много подробной информации по поводу GET-запроса. Можете ли вы просто быстро сказать мне, как сделать POST-запрос?
Конечно, в POST-запросе тело способа сетевого обращения выглядело бы немного по-другому, но в остальном все почти одинаково.
Вы бы создали типовой класс отклика POST-запроса таким же образом. Аналогично будет построен и ваш FutureBuilder
. Посмотрим, какие различия существуют в методе сетевого обращения.
Future<Post> createPost(Post post) async{
final response = await http.post('$url',
headers: {
HttpHeaders.contentTypeHeader: 'application/json'
},
body: postToJson(post)
);
return postFromJson(response.body);
}
Теперь ваш http.post
будет принимать 3 параметра > url
(API конечной точки URL), headers
(HTTP Headers (заголовки); если необходимо) и body
(тело) (обязательно).
Значит, у вас может быть такой post-объект.PostToJson(post)
преобразует ваш объект post в строку JSON, готовую для отправки на сервер. Теперь используйте метод createPost
на вашем FutureBuilder
и создайте ваш Виджет!
Но я не хочу создавать пользовательский интерфейс (UI) для этого сетевого запроса.
Есть возможность такого сценария. Например, с использованием логина или просто сетевого вызова, который отправит какие-либо значения на сервер, и возвратит 200 или 400 statusCode, что является единственной вещью, которая меня беспокоит.
Так что, уже не хотите FutureBuilder?
Тогда просто используйте .then()
метод далее.
createPost(post).then(
(response){
}
)
Используйте это, когда вы хотите обратиться к API. Например, этот фрагмент может быть в onPressed
функции вашей кнопки.
Здесь response — это результат, который мы получаем, когда createPost
получает какой-либо ответ. Теперь мы можем использовать этот ответ, чтобы делать все, что захотим. Может быть, перейти на другой экран.
createPost(post).then((response){
Navigate.of(context).pushNamed('dashboard');
})
Но в этой ситуации он может перейти на другой экран, даже если statusCode равен 400, потому что в любом случае будет получен отклик. (Ошибка также является допустимым ответом).
Точка. Предположим, что мы хотим контролировать нашу логику на основе благоприятного исхода или кода ошибки. Тогда нам придется модифицировать метод createPost
.
Future<http.Response> createPost(Post post) async{
//same as previous body
return response;
}
Теперь createPost
возвращает будущее типа http.Response
. Таким образом, мы можем контролировать многое из нашего пользовательского интерфейса (UI).
createPost(post).then((response){
if(response.statusCode == 200)
Navigate.of(context).pushNamed('dashboard');
else
print(response.statusCode);
})
Посмотрите проект GitHub, чтобы проверить примеры, рассмотренные выше: PoojaB26/ParsingJSON-Flutter
Это всё для вас, новички! Но океан необъятен, продолжайте исследовать!
Узнать подробнее о курсе «Flutter Mobile Developer».
Смотреть вебинар по теме «Пишем Flutter-приложение с использованием Redux».
ookami_kb
Да уж… И оригинальная статья-то не очень качественная, но перевод – это просто кошмар. Я даже не представляю, кем он мог быть сделан: такое впечатление, что переводчик не знает ни программирования, ни русского языка (и английского, в общем, тоже); а гугл транслейт не допустил бы такого количества отсебятины. Есть идеи?