|
|
|
# Consumindo REST APIs no Flutter
|
|
|
|
|
|
|
|
## Requisições HTTP para REST APIs
|
|
|
|
Para chamar APIs, utilizamos o pacote `http`. Este pacote já está instalado no projeto, mas caso queira instalar em outro projeto:
|
|
|
|
- Execute o comando `flutter pub add http`;
|
|
|
|
- (Para projetos Android) No arquivo `AndroidManifest.xml`, adicione a permissão para acesso à Internet: `<uses-permission android:name="android.permission.INTERNET" />
|
|
|
|
`.
|
|
|
|
|
|
|
|
Para cada verbo, há um método associado (`http.get()`, `http.post()`, etc.). Esses métodos retornam um `Future` contendo um `http.Response`. `Future` é uma classe utilizada para operações assíncronas, como é o caso de requisições HTTP, e representa um potencial valor ou erro que será retornado em algum momento no futuro. `http.Response` é uma classe utilizada para armazenar o retorno de uma requisição.
|
|
|
|
|
|
|
|
_Os códigos a seguir podem ser executados diretamente no DartPad (https://dartpad.dev)_.
|
|
|
|
```
|
|
|
|
import 'package:http/http.dart' as http;
|
|
|
|
|
|
|
|
Future<http.Response> fetchUsers() {
|
|
|
|
return http.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));
|
|
|
|
}
|
|
|
|
|
|
|
|
void users() async {
|
|
|
|
http.Response response = await fetchUsers();
|
|
|
|
String jsonBody = response.body;
|
|
|
|
print(jsonBody);
|
|
|
|
}
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
users();
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Com isso, já temos "em mãos" o retorno, porém em um formato "bruto" em uma String. No caso da API de nosso projeto, essa String contém um JSON.
|
|
|
|
|
|
|
|
Podemos decodificar o JSON utilizando um pacote nativo do Dart chamado `dart:convert`. Este pacote expõe um método `jsonDecode`, que utilizaremos a seguir:
|
|
|
|
```
|
|
|
|
import 'package:http/http.dart' as http;
|
|
|
|
import 'dart:convert';
|
|
|
|
|
|
|
|
Future<http.Response> fetchUsers() {
|
|
|
|
return http.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<http.Response> fetchUser(int id) {
|
|
|
|
return http.get(Uri.parse('https://jsonplaceholder.typicode.com/users/$id'));
|
|
|
|
}
|
|
|
|
|
|
|
|
void users() async {
|
|
|
|
http.Response response = await fetchUsers();
|
|
|
|
List<dynamic> users = jsonDecode(response.body);
|
|
|
|
print(users);
|
|
|
|
}
|
|
|
|
|
|
|
|
void user() async {
|
|
|
|
http.Response response = await fetchUser(1);
|
|
|
|
Map<String, dynamic> user = jsonDecode(response.body);
|
|
|
|
print(user);
|
|
|
|
}
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
users();
|
|
|
|
user();
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Como o JSON contém essencialmente apenas dados no formato `chave: valor` ou listas de chaves e valores, os retornos do método `jsonDecode` podem ser `Map<String, dynamic>` ou `List<dynamic>`, dependendo do caso (ou simplesmente `var` se estiver com preguiça).
|
|
|
|
|
|
|
|
|
|
|
|
## Convertendo de JSON para modelos e vice-versa
|
|
|
|
|
|
|
|
Como vimos anteriormente, é fácil de obter dados de API. Porém, não há ferramentas nativas para serialização e desserialização automática de classes modelo. Essa operação deve ser feita manualmente, o que pode ser trabalhoso e propenso a erros.
|
|
|
|
|
|
|
|
O código a seguir possui duas classes, que idealmente devem ser inseridas em arquivos diferentes, porém no formato atual pode ser executada no DartPad.
|
|
|
|
```
|
|
|
|
import 'dart:convert';
|
|
|
|
|
|
|
|
// Item.dart
|
|
|
|
class Item {
|
|
|
|
final String description;
|
|
|
|
|
|
|
|
Item({required this.description});
|
|
|
|
|
|
|
|
@override
|
|
|
|
String toString() {
|
|
|
|
return 'Item (description = ${this.description})';
|
|
|
|
}
|
|
|
|
|
|
|
|
factory Item.fromJson(Map<String, dynamic> json) {
|
|
|
|
return Item(description: json['description']);
|
|
|
|
}
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson(Item item) =>
|
|
|
|
<String, dynamic>{'description': item.description};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Person.dart
|
|
|
|
// import 'Item.dart';
|
|
|
|
class Person {
|
|
|
|
final String name;
|
|
|
|
final DateTime dob;
|
|
|
|
final num height;
|
|
|
|
final num weight;
|
|
|
|
final List<Item> items;
|
|
|
|
|
|
|
|
@override
|
|
|
|
String toString() {
|
|
|
|
return 'Person (name = ${this.name}, dob = ${this.dob}, height = ${this.height}, weight = ${this.weight}, items = ${this.items})';
|
|
|
|
}
|
|
|
|
|
|
|
|
Person({
|
|
|
|
required this.name,
|
|
|
|
required this.dob,
|
|
|
|
required this.height,
|
|
|
|
required this.weight,
|
|
|
|
required this.items,
|
|
|
|
});
|
|
|
|
|
|
|
|
factory Person.fromJson(Map<String, dynamic> json) {
|
|
|
|
return Person(
|
|
|
|
name: json['name'],
|
|
|
|
dob: DateTime.parse(json['dob'] as String),
|
|
|
|
height: json['height'],
|
|
|
|
weight: json['weight'],
|
|
|
|
items: (json['items'] as List<dynamic>)
|
|
|
|
.map((e) => Item.fromJson(e as Map<String, dynamic>))
|
|
|
|
.toList(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson(Person person) => <String, dynamic>{
|
|
|
|
'name': person.name,
|
|
|
|
'dob': person.dob,
|
|
|
|
'height': person.height,
|
|
|
|
'weight': person.weight,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
void person() async {
|
|
|
|
Map<String, dynamic> person = jsonDecode('{"name": "Uncle Bob", "dob": "1980-02-28", "height": 1.8, "weight": 75.6, "items": [{"description": "Wooden sword"}, {"description": "Vibranium shield"}]}');
|
|
|
|
print(person);
|
|
|
|
|
|
|
|
Person model = Person.fromJson(person);
|
|
|
|
print(model);
|
|
|
|
}
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
person();
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Para facilitar o desenvolvimento, utilizaremos os pacotes `json_annotation`, `json_serializable` e `build_tools`. Estes pacotes já estão instalados no projeto, mas, se quiser instalá-los, execute os seguintes comandos:
|
|
|
|
```
|
|
|
|
flutter pub add json_annotation
|
|
|
|
flutter pub add -d json_serializable build_tools
|
|
|
|
```
|
|
|
|
|
|
|
|
Agora, podemos criar as classes da seguinte forma:
|
|
|
|
```
|
|
|
|
// Item.dart
|
|
|
|
import 'package:json_annotation/json_annotation.dart';
|
|
|
|
|
|
|
|
part 'Item.g.dart';
|
|
|
|
|
|
|
|
@JsonSerializable()
|
|
|
|
class Item {
|
|
|
|
final String description;
|
|
|
|
|
|
|
|
Item({required this.description});
|
|
|
|
|
|
|
|
factory Item.fromJson(Map<String, dynamic> json) => _$ItemFromJson(json);
|
|
|
|
Map<String, dynamic> toJson() => _$ItemToJson(this);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
```
|
|
|
|
// Person.dart
|
|
|
|
import 'package:json_annotation/json_annotation.dart';
|
|
|
|
|
|
|
|
part 'Person.g.dart';
|
|
|
|
|
|
|
|
@JsonSerializable()
|
|
|
|
class Person {
|
|
|
|
final String name;
|
|
|
|
final DateTime dob;
|
|
|
|
final num height;
|
|
|
|
final num weight;
|
|
|
|
final List<Item> items;
|
|
|
|
|
|
|
|
Person({
|
|
|
|
required this.name,
|
|
|
|
required this.dob,
|
|
|
|
required this.height,
|
|
|
|
required this.weight,
|
|
|
|
required this.items,
|
|
|
|
});
|
|
|
|
|
|
|
|
factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
|
|
|
|
Map<String, dynamic> toJson() => _$PersonToJson(this);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
A linha que contém `part '[Classe].g.dart` referencia um arquivo que ainda não existe e será gerado automaticamente. Para isso, é necessário iniciar um processo, que ficará executando em background, monitorando o código. Execute o seguinte comando:
|
|
|
|
```
|
|
|
|
flutter pub run build_runner watch
|
|
|
|
```
|
|
|
|
Ao iniciar o processo, os arquivos `*.g.dart` serão criados. Se alguma classe for modificada, esses arquivos serão atualizados automaticamente, desde que o processo esteja em execução. Por isso, é recomendável reservar uma janela de terminal para ele.
|
|
|
|
|
|
|
|
É possível que o processo encontre erros e feche. Nesse caso, o erro deve ser corrigido e o processo executado novamente.
|
|
|
|
|
|
|
|
Caso ocorra um erro iniciando com `Conflicting outputs were detected and the build is unable to prompt for permission to remove them. These outputs must be removed manually or the build can be run with ``--delete-conflicting-outputs``.`, delete todos os arquivos *.g.dart e execute o processo novamente.
|
|
|
|
|
|
|
|
Ainda há bastante código para escrever ao criar uma classe. Para facilitar, existe uma extensão do VSCode chamada _Flutter Helpers_ (https://marketplace.visualstudio.com/items?itemName=aksharpatel47.vscode-flutter-helper). Esta extensão possui 2 snippets (`jsf` e `jsc`) e 2 comandos para executar o processo que gera os arquivos `*.g.dart` (um para executar uma vez e outro para iniciar o monitoramento).
|
|
|
|
|
|
|
|
| ![Criando um modelo](http://tools.ages.pucrs.br/apus-drones/apus-drones-wiki/raw/master/tutorials/flutter_json_1.gif) |
|
|
|
|
|:--:|
|
|
|
|
| *Criando um modelo* |
|
|
|
|
|
|
|
|
| ![Alterando um modelo](http://tools.ages.pucrs.br/apus-drones/apus-drones-wiki/raw/master/tutorials/flutter_json_2.gif) |
|
|
|
|
|:--:|
|
|
|
|
| *Alterando um modelo* |
|
|
|
|
|
|
|
|
## Referências
|
|
|
|
- flutter.dev - Fetch data from the internet: https://flutter.dev/docs/cookbook/networking/fetch-data
|
|
|
|
- flutter.dev - JSON and serialization: https://flutter.dev/docs/development/data-and-backend/json
|
|
|
|
- pub.dev - Response class: https://pub.dev/documentation/http/latest/http/Response-class.html
|
|
|
|
- api.flutter.dev - Future\<T\> class: https://api.flutter.dev/flutter/dart-async/Future-class.html |