Como obter dados da internet no Flutter usando HTTP
Introdução
Quando trabalhamos com desenvolvimento front-end (parte da aplicação com a qual o usuário interage e vê - em geral é a tela), seja ele mobile ou web, é comum que precisemos nos comunicar com o back-end (de onde vêm os dados, o que torna a aplicação funcional de fato - em geral, um banco de dados), e uma das primeiras comunicações que aprendemos é a obtenção de dados da internet.
Neste artigo, vamos aprender como obter dados da internet no Flutter utilizando a biblioteca HTTP, entendendo da requisição para a internet até a aplicação final exibindo os dados na tela para o usuário.
E o melhor de tudo: vamos aprender tudo isso na prática - com um projeto real! Essa aplicação obtém, da internet, uma lista de filmes (com título e imagem) e a mostra para o usuário. Veja:
Conhecimentos prévios
Para que aproveite bem o conteúdo deste artigo, é interessante que você domine algumas habilidades e técnicas em Flutter (aliás, temos cursos que ensinam esses conhecimentos aqui na Alura):
- Saiba como configurar ambiente e criar um projeto novo
- Entenda o que são Widgets, Stateless e Stateful; e
- Tenha noções de assincronismo no Dart
- Conheça os princípios e técnicas de armazenamento de dados local em um projeto Flutter
Entendendo nosso projeto: O aplicativo Filmes Studio Ghibli
Vamos utilizar um projeto prático para aprender a habilidade de obter dados da internet e mostrá-los em um aplicativo Flutter! Você pode acessar o projeto inicial neste link do GitHub.
Imagine que você precisa fazer um projeto de aplicativo Flutter com as seguintes características:
- A pessoa usuária consegue ver uma lista de filmes do Studio Ghibli;
- Essa lista é um tipo de dado disponível apenas de forma online, ou seja, o celular e o app precisam se conectar à internet e pegar esses dados para depois exibi-los na tela para a pessoa usuária.
Como podemos, então, fazer com que o app acesse esses dados disponíveis na internet (mais especificamente em um banco de dados)?
Bom, precisamos seguir uma série de passos:
- Instalar o pacote HTTP;
- Usar o pacote HTTP para fazer uma requisição GET que obtém os dados dos filmes (disponíveis na internet);
- Converter os dados obtidos do tipo “JSON” em objetos;
- Mostrar os dados dos filmes.
Veremos cada um dessas etapas! A primeira envolve um tal de pacote HTTP - mas o que é isso?
Conhecendo o pacote HTTP
Para obter dados da internet (no nosso caso, informações sobre filmes do estúdio Ghibli), precisamos de um pacote HTTP que vai fazer a comunicação com a internet. Vamos ver isso com mais detalhes!
Em sua documentação, o Flutter oferece o pacote HTTP
, que faz requisições HTTP que são mensagens enviadas do cliente para um servidor - o cliente pode ser um app, por exemplo.
Ou seja, a função do pacote HTTP é enviar “mensagens” para a internet e pedir os dados para um servidor. O nome técnico dessas mensagens/pedidos é requisições HTTP.
Sobre as requisições HTTP, essas mensagens enviadas para o servidor podem ser de vários tipos ou verbos, sendo os mais conhecidos:
GET
: Usado para obter dados da internet (que podem ser mostrados na tela depois);POST
: Usado para enviar dados novos (para salvar uma informação, por exemplo);PUT
: Usado para substituir dados já existentes (que edita um dado já existente); eDELETE
: Usado para apagar dados.
Além disso, quando enviamos uma requisição (request, em inglês) ao servidor, vamos receber de volta uma resposta (response), e essa resposta pode conter o que esperávamos receber de volta ou, por exemplo, a informação de algum erro ocorrido.
Em geral, junto da resposta do servidor, vem um código chamado Status Code, e ele pode ser de diferentes valores:
- Respostas informativas (100 a 199);
- Respostas bem-sucedidas (200 a 299);
- Mensagens de redirecionamento (300 a 399);
- Respostas de erro do cliente (400 a 499);
- Respostas de erro do servidor (500 a 599).
Quando obtemos sucesso na requisição (recebemos o que esperávamos), o valor do Status Code é
200
!
Em Flutter, e com o pacote HTTP
, não é diferente! Nós enviamos uma request
e recebemos uma response
.
E agora que você já sabe o que é o pacote HTTP
, pode começar a praticar. Vamos lá?
Passo 1: Instalando o pacote
Para instalar o pacote HTTP
em nosso projeto, vamos usar o seguinte comando no terminal:
flutter pub add http
Isso deve adicionar a seguinte linha no seu arquivo pubspec.yaml
:
dependencies:
HTTP: ^0.13.5
A versão utilizada durante a produção deste artigo é a
^0.13.5
, mas pode ser que quando esteja lendo, já se tenha uma versão mais atualizada (mas para que funcione bem, exatamente como explico no artigo, é importante usar a mesma versão que utilizei). E se tiver interesse em conhecer mais sobre a biblioteca, acesse o site oficial.
Pronto, tudo instalado, e agora podemos pôr a mão no código!
Passo 2: Obtendo dados da Web com GET
Para começar a implementar a funcionalidade na nossa aplicação Filmes Studio Ghibli, vamos precisar da lista de filmes (dados que estão disponíveis apenas na internet).
Aprendemos que, para obter esses dados da internet em um projeto Flutter, precisamos de um pacote como o HTTP que se comunica com a internet e permite que a gente use um verbo do tipo GET
que vai fazer uma solicitação a uma API (veremos o que é isso já já).
Em resumo, quando pegamos um dado da internet, o app se comunica com uma API (e não com o servidor/banco de dados diretamente).
Uma API, de maneira bem simples, pode ser vista como um meio de comunicação e integração entre o front-end e o back-end, como um telefone entre eles!
No caso, vamos simular uma API com um arquivo hospedado no GitHub com uma lista de filmes que queremos exibir. Usaremos, então, uma Fake API que contém os dados dos filmes que queremos exibir.
Faz sentido?
A seguir, precisamos acessar essa API simulada. Para fazer isso, em nosso projeto, vamos criar um arquivo com uma função chamada getFilmes
. Essa função vai fazer uma requisição GET, ou seja, um pedido dos dados (como se o app dissesse para a API “Oi, me vê os dados aí, por favor!”).
Vejamos como fica a função getFilmes
:
import 'package:http/http.dart' as http;
Future<void> getFilmes() async {
final response = await http.get(Uri.parse(
'https://raw.githubusercontent.com/alura-cursos/obtendo_dados_com_flutter_http/main/filmes.json'));
print(response.body);
}
Chamaremos o arquivo acima de
repository.dart
.
Com apenas uma função, fazemos a requisição GET para obter os dados! Veja uma representação desse processo:
Como vemos na imagem, o dispositivo com o projeto Flutter, graças ao pacote HTTP, faz uma requisição GET - no caso, a função getFilmes
.
Vamos entender os detalhes da implementação dessa função:
1) Para começar, importamos o pacote http
:
import 'package:http/http.dart' as http;
2) Depois, veja que adotamos uma função assíncrona - visto que estamos lidando com informações da internet e esses dados podem demorar para chegar - e sem retorno - pois, por enquanto, queremos apenas imprimir a resposta no terminal. Daí a necessidade de usar as palavras Future<void>
, async
e await
;
3) Como a requisição nos retorna uma resposta, um valor, criamos uma variável chamada response
, que irá guardar esse resultado recebido; e
4) Na requisição, usamos http.get()
, cujo conteúdo dentro desse comando é exatamente um URI (identificador de recurso), daí a necessidade transformarmos o nosso link nisso, com a instrução uri.parse
.
Assim, a requisição é enviada para a API (que faz a ligação entre o dispositivo/aplicativo e o servidor). Por sua vez, a API pede os dados para o servidor:
Em seguida, o banco responde a API:
Por último, a API faz a resposta para o dispositivo, enviando o dado recebido em formato JSON:
Ufa! Finalmente! E o resultado da nossa requisição GET é a seguinte lista, em formato JSON, com nomes e imagens de filmes:
Se você quiser conferir, o código segue abaixo:
[
{
"nome": "A Viagem de Chihiro",
"imagem": "HTTPs://br.web.img3.acsta.net/pictures/210/527/21052756_20131024195513383.jpg"
},
{
"nome": "Ponyo",
"imagem": "HTTPs://m.media-amazon.com/images/I/61ry7KsRfIS._AC_SL1000_.jpg"
},
{
"nome": "Meu Amigo Totoro",
"imagem": "HTTPs://i0.wp.com/studioghibli.com.br/wp-content/uploads/2020/04/meu-amigo-totoro-dvd-vers%C3%A1til.jpg"
},
{
"nome": "Princesa Mononoke",
"imagem": "HTTPs://i0.wp.com/studioghibli.com.br/wp-content/uploads/2020/04/Princesa-Mononoke-DVD-capa-vers%C3%A1til.jpg"
},
{
"nome": "Castelo Animado",
"imagem": "HTTPs://capas.nyc3.cdn.digitaloceanspaces.com/650-5000245618508.jpg"
},
{
"nome": "A Viagem de Chihiro",
"imagem": "HTTPs://br.web.img3.acsta.net/pictures/210/527/21052756_20131024195513383.jpg"
},
{
"nome": "As Memórias de Marnie",
"imagem": "HTTPs://www.itaucinemas.com.br/_img/_filmes/1859_capa.jpg"
}
]
Entendendo os dados que recebemos
Você pode estar se perguntando o tipo de informação que recebemos da API simulada e o conteúdo exato dela, pois falamos de um tal de JSON. Vamos entender tudo isso!
Como dito anteriormente, estamos usando um arquivo no GitHub (para simular uma API), que está em um formato específico, bastante utilizado na internet quando trabalhamos com APIs: o JSON.
O JSON é um formato leve, utilizado na internet para troca de dados entre diferentes sistemas. No Flutter, vamos precisar converter esses dados em objetos para que possamos exibir em nossa aplicação.
Passo 3: Convertendo dados em objetos
Ao recebermos dados em formato de JSON, é necessário convertê-los em um model (já veremos o que é isso com detalhes), ou seja, um modelo de representação desses dados. Em nosso código, estamos recebendo filmes com nomes e imagens, e podemos criar o seguinte model:
class Filme {
String nome;
String imagem;
Filme({
required this.nome,
required this.imagem,
});
Filme.fromJson(Map<String, dynamic> json)
: nome = json['nome'].toString(),
imagem = json['imagem'].toString();
}
Chamaremos o arquivo acima de
filme.dart
.
Este model nada mais é do que uma classe Filme
, com os atributos recebidos no JSON (nome
e imagem
), e estamos usando dois construtores:
Um construtor comum que recebe duas Strings (
nome
eimagem
); eUm construtor nomeado e de redirecionamento, chamado
fromJson
(“do json”, em português) que recebe umMap
chamadojson
- é esse construtor de redirecionamento que vai fazer a conversão dos dados do tipo JSON (que obtivemos da internet) para um objeto no Flutter.
Se quiser saber mais sobre o construtor nomeado, de redirecionamento e diversos outros tipos, recomendo a leitura do artigo Construtores no Dart: conhecendo tipos de construtores e como usá-los.
Agora que temos o model e o construtor que vai realizar a conversão (pense como uma “tradução” de um tipo de dado para outro), precisamos aplicar o model que criamos.
Vamos lá?
Aplicando o model que criamos
Para converter o arquivo JSON que recebemos em uma lista de objetos do tipo Filme
, faremos da seguinte forma no arquivo repository.dart
:
import 'package:http/http.dart' as http;
import 'dart:convert';
import '../models/filme.dart';
Future<List<Filme>> getFilmes() async {
final response = await http.get(Uri.parse(
'https://raw.githubusercontent.com/alura-cursos/obtendo_dados_com_flutter_http/main/filmes.json'));
final json = jsonDecode(response.body);
return List<Filme>.from(json.map((elemento) {
return Filme.fromJson(elemento);
}));
}
Entendendo melhor o que alteramos na nossa função getFilmes
:
1) Importamos, agora, a biblioteca dart:convert
para utilizarmos o jsonDecode
, além de importarmos o arquivo filme.dart
, que contém o model de Filme
:
import 'dart:convert';
import '../models/filme.dart';
2) Agora, invés de usar um Future<void>
(sem retornar nada), vamos ter uma função Future<List<Filme>>
, pois vamos retornar uma lista de objetos do tipo Filme
:
Future<List<Filme>> getFilmes() async {
//…
}
3) Guardamos, em uma variável chamada json
, a conversão do JSON em um objeto:
final json = jsonDecode(response.body);
4) Retornamos uma lista (List
) com objetos do tipo Filme
, realizando um map
de cada elemento do objeto JSON, transformando um a um em objetos Filme
:
return List<Filme>.from(json.map((elemento) {
return Filme.fromJson(elemento);
}));
Legal! Agora que concluímos nossa função getFilmes
, podemos utilizá-la para exibir o resultado para o usuário. Ou seja, o app já converte o JSON recebido da internet em uma lista de objetos do tipo Filme
.
Tratando exceções na aplicação
Se você tiver testado o código anterior, pode ter percebido que, caso o link esteja incorreto, teremos uma exceção, ou seja, um erro que ocorreu enquanto o aplicativo estava executando. Essa exceção acontece pois há um erro na requisição que fizemos e não recebemos nenhum dado, portanto, precisamos tratá-la de alguma forma, e é isso que vamos fazer!
Para tratar, precisamos utilizar um condicional if-else
que verifique se a requisição ocorreu com sucesso ou não. Podemos usar o response.statusCode
neste caso, pois o valor dele vai indicar sucesso (se for 200
) e algo diferente (se for um valor diferente de 200
). Veja:
if (response.statusCode == 200) {
/…
} else {
/…
}
E implementando essa verificação no nosso código, ficaria da seguinte maneira:
import 'package:http/http.dart' as http;
import 'dart:convert';
import '../models/filme.dart';
Future<List<Filme>> getFilmes() async {
final response = await http.get(Uri.parse(
'https://raw.githubusercontent.com/alura-cursos/obtendo_dados_com_flutter_http/main/filmes.json'));
if (response.statusCode == 200) {
final json = jsonDecode(response.body);
return List<Filme>.from(json.map((elemento) {
return Filme.fromJson(elemento);
}));
} else {
return Future.error("Ops! Um erro ocorreu.");
}
}
Em que:
- Verificamos se ocorreu tudo certo (se
response.statusCode
é igual a 200); e - Caso tenha ocorrido tudo certo, retornamos a lista de filmes, como vimos anteriormente;
- Caso algo de errado tenha acontecido e não tenhamos dados, retornamos um objeto
Future
com um erro e a seguinte mensagem: “Ops! Um erro ocorreu”.
Tudo certo! Conseguimos colocar, dentro do dispositivo, os dados dos filmes do Studio Ghibli e cuidamos para que nenhuma exceção prejudique seu funcionamento. Bora mostrá-los na tela?
Passo 4: Exibindo resultados na tela
Para exibir os filmes na tela, vamos criar um novo arquivo chamado home.dart
, no qual vamos criar um Widget do tipo Stateful chamado Home
, ou seja, um Widget mutável, que possui estado.
Se quiser relembrar o que é um Widget Stateful, recomendo a leitura do artigo Flutter - Qual é a diferença entre stateless e stateful widget?
E escreveremos o seguinte código dentro de _HomeState:
class _HomeState extends State<Home> {
Future<List<Filme>>? futureFilmes;
@override
void initState() {
futureFilmes = getFilmes();
super.initState();
}
//…
}
Vamos entender os detalhes do que fizemos!
1) Estamos criando uma lista de filmes chamada futureFilmes
, e ela é nullable, pois começa sem um valor;
Future<List<Filme>>? futureFilmes;
2) O initState
é um método de Widgets Stateful que é chamado quando iniciamos uma tela, e estamos inicializando dentro dele a lista futureFilmes
, usando a nossa função getFilmes
:
@override
void initState() {
futureFilmes = getFilmes();
super.initState();
}
Temos uma lista de objetos do tipo Filme
, a futureFilmes
, podemos agora utilizá-la e mostrar ao usuário o resultado na tela. Assim, usaremos o FutureBuilder
, uma classe de Widget que utiliza o resultado de um Future para construir a si mesmo:
FutureBuilder(
future: futureFilmes,
builder: (context, snapshot) {
if (snapshot.hasData) {
final filmes = snapshot.data as List<Filme>;
return ListaFilmes(filmes: filmes);
} else if (snapshot.hasError) {
return Center(
child: Text(
snapshot.error.toString(),
style: const TextStyle(
fontSize: 16,
),
),
);
}
return const Center(
child: CircularProgressIndicator(),
);
},
),
Entendendo melhor o que fizemos:
- Estamos usando o
FutureBuilder
, e dentro dele temos:- 1.1. O
future
: que é a nossa listafutureFilmes
; - 1.2. O
builder
que retorna (constrói) o Widget;
- 1.1. O
- Em
builder
temos uma função que recebecontext
esnapshot
, em que osnapshot
é o que nos interessa, pois ele é o conteúdo que recebemos; - Por fim, verificamos se há algum dado (
snapshot.hasData
) ou se há algum erro (snapshot.hasError
);- 3.1. Se houver dados, passamos eles por parâmetro para um Widget personalizado que eu construí;
- 3.2. Se tiver havido um erro, mostramos a mensagem contida na variável
snapshot.error
(que nesse caso é ”Ops! Um erro ocorreu.”); - 3.3. E caso esteja carregando, mostramos um indicador de progresso circular.
E o código completo da nossa página fica da seguinte maneira:
import 'package:flutter/material.dart';
import '../components/lista_filmes.dart';
import '../models/filme.dart';
import '../repositories/repository.dart';
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
Future<List<Filme>>? futureFilmes;
@override
void initState() {
futureFilmes = getFilmes();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Filmes Studio Ghibli"),
centerTitle: true,
),
body: FutureBuilder(
future: futureFilmes,
builder: (context, snapshot) {
if (snapshot.hasData) {
final filmes = snapshot.data as List<Filme>;
return ListaFilmes(filmes: filmes);
} else if (snapshot.hasError) {
return Center(
child: Text(
snapshot.error.toString(),
style: const TextStyle(
fontSize: 16,
),
),
);
}
return const Center(
child: CircularProgressIndicator(),
);
},
),
);
}
}
Acima o nosso arquivo
home.dart
completo.
Perfeito! Agora temos a nossa aplicação final, recebendo dados da internet, convertendo-os e mostrando para o usuário. Abaixo você pode conferir o resultado final que obtemos:
Concluímos nossa tarefa! Agora, se tiver curiosidade, você pode entender melhor como o projeto Filmes Studio Ghibli está organizado!
Estrutura do projeto e Widgets criados
Vamos ver a árvore de widgets do nosso projeto completo? Como você pode ver, utilizamos outros Widgets:
Na imagem acima, vemos a árvore do aplicativo, em que podemos observar que temos um Scaffold
que contém um FutureBuilder
, que, por sua vez, exibe um Widget chamado ListaFilmes
(criado por mim). O ListaFilmes
contém um GridView
que mostra uma lista de Widgets CardFilme
(também criado por mim). Repare que, a partir do CardFilme
, a estrutura da árvore se repete, pois se trata de uma lista que repete o mesmo widget de cartão que apresenta cada filme.
E, além dos códigos que demonstrei aqui, utilizei de mais dois arquivos, os quais deixo para você - os Widgets ListaFilmes
e CardFilme
:
Arquivo
lista_filmes.dart
:
import 'package:flutter/material.dart';
import '../models/filme.dart';
import 'card_filme.dart';
class ListaFilmes extends StatelessWidget {
const ListaFilmes({
Key? key,
required this.filmes,
}) : super(key: key);
final List<Filme> filmes;
@override
Widget build(BuildContext context) {
return GridView.builder(
padding: const EdgeInsets.all(16),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 12,
mainAxisSpacing: 10,
childAspectRatio: 3 / 4,
),
itemCount: filmes.length,
itemBuilder: ((context, index) {
final filme = filmes[index];
return CardFilme(
nome: filme.nome,
imagem: filme.imagem,
);
}),
);
}
}
Arquivo
card_filme.dart
:
import 'package:flutter/material.dart';
class CardFilme extends StatelessWidget {
final String nome;
final String imagem;
const CardFilme({
Key? key,
required this.nome,
required this.imagem,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
image: DecorationImage(
fit: BoxFit.cover,
image: NetworkImage(imagem),
),
boxShadow: const [
BoxShadow(
color: Colors.blueGrey,
blurRadius: 4,
offset: Offset(0, 2),
)
]),
),
),
const SizedBox(height: 8.0),
Text(
nome,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
],
);
}
}
Veja também como ficou a estrutura do projeto:
├── lib
│ ├── components
│ │ └── card_filme.dart
│ │ └── lista_filmes.dart
│ ├── models
│ │ └── filme.dart
│ ├── repositories
│ │ └──repository.dart
│ ├── screens
│ │ └──home.dart
│ └── main.dart
E se quiser ver a aplicação completa, baixar e testar, deixo também o repositório do projeto no GitHub.
Conclusão
Meus parabéns por ter concluído a leitura!
Agora você já deve ser capaz de obter dados da internet utilizando o pacote HTTP
de Flutter, além de exibi-los para a pessoa usuária da aplicação.
Vimos todo o fluxo de requisição e resposta para obter dados da internet, do dispositivo até o servidor e, também, passando pela API.
No nosso caso, usamos uma API simulada com um arquivo do Github, pois APIs na internet podem variar e mudar ao longo do tempo e, assim, garantimos que você possa praticar as técnicas ensinadas neste artigo sem se preocupar se a API funciona ou foi desativada.
Abaixo, deixamos um infográfico que resume bem todo o processo de requisição e resposta para obter dados da internet:
Se quiser se aprofundar ainda mais no assunto, recomendo os cursos Flutter com WebAPI: integrando sua aplicação e Flutter com Web API: evoluindo na integração da aplicação, ambos da nossa formação Flutter.
Até a próxima e bons estudos!