Flutter - Obtendo dados da Web (REST)


Hoje veremos como podemos obter dados da internet e exibí-los em aplicações Flutter.

O Flutter é um SDK de aplicativo móvel do Google que ajuda a criar aplicativos móveis modernos para iOS e Android usando uma única (quase) base de código.

Se você não conhece o Flutter veja o meu artigo :  Flutter - Primeiros contatos e impressões

Obtendos dados da internet

Atualmente obter dados da internet é um requisito básico em muitas aplicações, e, o Flutter e o Dart nos fornecem ferramentas para realizar esse trabalho.

O roteiro básico a seguir é :

É isso que iremos fazer neste artigo a seguir mostrando como realizar cada uma das etapas acima.

Antes de iniciar precisamos definir qual a nossa fonte de dados, ou seja, qual a URL vamos acessar para obter dados.

Existem muitas opções, e , neste exemplo vou acessar a url : https://jsonplaceholder.typicode.com/posts que retorna dados no formato JSON conforme abaixo:

[
  {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
  }
,
  {
    "userId": 1,
    "id": 2,
    "title": "qui est esse",
    "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
  },
  {
    "userId": 1,
    "id": 3,
    "title": "ea molestias quasi exercitationem repellat qui ipsa sit aut",
    "body": "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut"
  },
  {
    ...
    ...
]

Mais informações você obtém no site : https://jsonplaceholder.typicode.com/

Para a API que vamos usar os métodos HTTP disponíveis são:

GET /posts
GET /posts/1
GET /posts/1/comments
GET /comments?postId=1
GET /posts?userId=1
POST /posts
PUT /posts/1
PATCH /posts/1
DELETE /posts/1

Criando o projeto Flutter

Vamos começar criando o projeto flutter.

Eu estou usando o Flutter versão 1.2.1, e como editor de código estou usando o VS Code com o plugin Flutter instalado.

No Visual Studio Code tecle CTRL+ SHIFT+P para abrir a paleta de comandos e a seguir selecione a opção : Fluter:New Project

A seguir informe o nome do projeto : flutter_http_web e tecle ENTER.

Na janela de diálogo a seguir, selecione a pasta onde o projeto vai ser salvo e clique em :
Select a folder to create the project in

O Flutter vai criar um projeto padrão onde todo o código da aplicação vai estar no arquivo main.dart dentro da pasta lib do projeto.

A seguir podemos abrir o projeto usando o VS Code usando a opção Open folder e escolhendo a pasta flutter_http_web.

1- Adicionar o pacote http

O pacote http fornece a maneira mais simples de buscar dados da Internet.

Para instalar o pacote http, você deve adicioná-lo à seção de dependências do arquivo pubspec.yaml do projeto Flutter. Você pode encontrar a versão mais recente do pacote http neste link.

Hoje, abril de 2019, a versão atual deste pacote é a versão :  http 0.12.0+2

Conforme a documentação temos que usar o pacote http 0.12.0+2 que contém um conjunto de funções de alto nível que torna fácil consumir recursos HTTP. Sendo independente de plataforma e podendo ser usado na linha de comando e no browser.

Para isso vamos incluir no arquivo pubspec.yaml a referência ao pacote http: ^0.12.0.0+2 conforme abaixo:

Além disso vamos ter que importar o pacote : import 'package:http/http.dart'; no arquivo main.dart.

2- Fazer uma requisição web usando o pacote http

Já sabemos que vamos acessar a API REST disponível em JSONPlaceholder, assim, um exemplo de código fazendo uma requisição HTTP com o método Get() pode ser vista abaixo: (obtido da documentação)

  Future<String> getJSONData() async {
    var response = await http.get(
        Uri.encodeFull("https://jsonplaceholder.typicode.com/posts/1"),
        headers: {"Accept": "application/json"}
    );
    setState(() {
      // otem os dados JSON
      data = json.decode(response.body)['results'];
    });
    return "Dados obtidos com sucesso";
}

No código acima você pode estranhar o uso de Future.

O que é Future ?

Um Future é um objeto Future<T>, que representa uma operação assíncrona que produz um resultado do tipo T. Se o resultado não for um valor utilizável, o tipo Future será Future<void>. Quando uma função que retorna um Future é invocada, duas coisas acontecem:

1- A função enfileira o trabalho a ser feito e retorna um objeto Future não concluído.
2- Posteriormente, quando a operação for concluída, o objeto Future será concluído com um valor ou com um erro.

E quando escrevemos código que depende de um Future podemos usar async e await.

A seguir definimos a url e o cabeçalho application/json e obtemos os dados atualizando o estado.

3- Converter a resposta em um objeto Dart personalizado

Embora seja fácil fazer uma requisição na web, trabalhar com Future<http.Response> não é muito conveniente. Para tornar nossa vida mais fácil, precisamos converter o http.Response em um objeto Dart.

A resposta da API REST quando acessarmos o endpoint terá o seguinte formato JSON:

  {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
  }

Precisamos criar uma classe que represente o modelo de domínio para esta estrutura JSON.

Podemos fazer isso manualmente ou usar o recurso disponível neste site: https://app.quicktype.io/

Essa ferramenta criará suas classes de modelo, os métodos de fábrica e os métodos de conversão.

Abaixo temos o resultado final obtido para o formato JSON retornado pela API REST:

class Post {
  final int userId;
  final int id;
  final String title;
  final String body;

  Post({this.userId, this.id, this.title, this.body});

  factory Post.fromJson(Map<String, dynamic> json) {
    return Post(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
      body: json['body'],
    );
  }
}

Agora, vamos definir um método fetchPost para retornar um Future<Post>.Para fazer isso, teremos que fazer o seguinte:

Abaixo temos o código do método fetchPost() :

Future<Post> fetchPost() async {
  final response = await http.get('https://jsonplaceholder.typicode.com/posts/1');

  if (response.statusCode == 200) {
    // se o servidor retornar um response OK, vamos fazer o parse no JSON
    return Post.fromJson(json.decode(response.body));
  } else {
    // se a responsta não for OK , lançamos um erro
    throw Exception('Failed to load post');
  }
}

Agora estamos obtendo os dados via get http do endpoint : https://jsonplaceholder.typicode.com/posts/1

4- Pegar e Exibir os dados com o Flutter;

Para buscar os dados e exibi-los na tela, você pode usar o widget FutureBuilder. Este Widget vem com o Flutter e facilita o trabalho com fontes de dados assíncronas.

Você deve fornecer dois parâmetros:

A seguir vemos o código usando o widget FutureBuilder:

FutureBuilder<Post>(
  future: fetchPost(),
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return Text('Id :' + snapshot.data.id.toString() + '\n\ntitle : ' + 
              snapshot.data.title + '\n\nbody : ' + snapshot.data.body);
    } else if (snapshot.hasError) {
      return Text("${snapshot.error}");
    }
    // exibe um spinner carregando 
    return CircularProgressIndicator();
  },
);

Embora seja conveniente, não é recomendável fazer uma chamada para uma API em um método build().

O Flutter chama o método build() toda vez que ele quer mudar alguma coisa na view, e isso acontece com frequência. Se fizermos a chamada ao método que faz a requisição para o endpoint no build(), iremos inundar a  API com chamadas desnecessárias impactando o desempenho da aplicação.

Podemos passar a chamada em um widget StateleslWidget onde o widget pai é responsável por chamar o método de fetchPost, armazenar seu resultado e, em seguida, transmiti-lo ao seu widget.

Para facilitar vamos colocar todo o código no arquivo main.dart. Veja como ficou o código completo:

import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<Post> fetchPost() async {
  final response = await http.get('https://jsonplaceholder.typicode.com/posts/2');
  if (response.statusCode == 200) {
    return Post.fromJson(json.decode(response.body));
  } else {
    throw Exception('Falha ao carregar um post');
  }
}
class Post {
  final int userId;
  final int id;
  final String title;
  final String body;
  Post({this.userId, this.id, this.title, this.body});
  factory Post.fromJson(Map<String, dynamic> json) {
    return Post(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
      body: json['body'],
    );
  }
}
void main() => runApp(MyApp(post: fetchPost()));
class MyApp extends StatelessWidget {
  final Future<Post> post;
  MyApp({Key key, this.post}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Obtendo dados da Web - Exemplo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('Pegando dados da Web'),
        ),
        body: Center(
          child: FutureBuilder<Post>(
            future: post,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Text('Id :' + snapshot.data.id.toString() + '\n\ntitle : ' 
+ snapshot.data.title + '\n\nbody : ' + snapshot.data.body),
                );
              } else if (snapshot.hasError) {
                return Text("${snapshot.error}");
              }
              return CircularProgressIndicator();
            },
          ),
        ),
      ),
    );
  }
}

Para executar podemos pressionar F5 ou no terminal de comandos, estando posicionado na pasta do projeto, basta digitar :  flutter run -d all

Executando o projeto iremos obter o resultado abaixo:

Dessa forma mostramos como fazer uma requisição http e consumir dados de uma API REST exibindo os dados em um Widget.

Pegue o projeto do arquivo main.dart aqui: main_dart_http.zip

"E, quanto fizerdes por palavras ou por obras, fazei tudo em nome do Senhor Jesus, dando por ele graças a Deus Pai."
Colossenses 3:17

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

Quer aprender os conceitos da Programação Orientada a objetos ?

Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?

Referências:


José Carlos Macoratti