Desenvolvimento

6 jun, 2019

Flutter – Lista básica com ListView – IV

Publicidade
Hoje vamos continuar a apresentar como exibir uma lista de dados usando Flutter, desta vez obtendo os dados no formato JSON via Http.

Continuando a terceira parte do artigo, hoje veremos como obter dados de uma API REST no formato JSON via HTTP e exibir em um ListView criado usando o construtor ListView.builder.

Obtendos dados da internet no formato JSON

Obter dados da internet é uma tarefa comum hoje em dia, e o Flutter e o Dart permitem realizar essa tarefa de forma bem simples.

O roteiro básico a seguir é :

  • Adicionar o pacote http
  • Fazer uma requisição de rede usando o pacote http
  • Converter a resposta em um objeto Dart personalizado
  • Buscar e exibir os dados com o Flutter

É isso que iremos fazer neste artigo.

Vamos exibir no ListView os dados obtidos no formato JSON a partir de uma API REST. Existem muitas serviços REST disponíveis que oferecem serviços para obter dados.

Neste artigo vamos usar os dados disponível neste endpointhttps://unsplash.com/napi/photos/Q14J2k8VE3U/related

Abaixo temos os dados no formato JSON retornados por esta url:

 

{
   "total":20,
   "results":[
      {
         "id":"hzgs56Ze49s",
         "created_at":"2015-04-25T07:52:00-04:00",
         "updated_at":"2019-04-21T19:55:18-04:00",
         "width":5616,
         "height":3744,
         "color":"#876B59",
         "description":"party fans raised their hands",
         "alt_description":"person performing heart hand gesture",
         "urls":{
            "raw":"https://images.unsplash.com/photo-1429962714451-bb934ecdc4ec?ixlib=rb-1.2.1",
            "full":"https://images.unsplash.com/photo-1429962714451-bb934ecdc4ec?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb",
            "regular":"https://images.unsplash.com/photo-1429962714451-bb934ecdc4ec?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max",
            "small":"https://images.unsplash.com/photo-1429962714451-bb934ecdc4ec?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max",
            "thumb":"https://images.unsplash.com/photo-1429962714451-bb934ecdc4ec?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max"
         },
         "links":{
            "self":"https://api.unsplash.com/photos/hzgs56Ze49s",
            "html":"https://unsplash.com/photos/hzgs56Ze49s",
            "download":"https://unsplash.com/photos/hzgs56Ze49s/download",
            "download_location":"https://api.unsplash.com/photos/hzgs56Ze49s/download"
         },
         "categories":[
         ],
         "sponsored":false,
         "sponsored_by":null,
         "sponsored_impressions_id":null,
         "likes":1174,
         "liked_by_user":false,
         "current_user_collections":[

         ],
         "user":{
            "id":"wdFc4ZqCkWo",
            "updated_at":"2019-04-13T17:28:40-04:00",
            "username":"anthonydelanoix",
            "name":"Anthony DELANOIX",
            "first_name":"Anthony",
            "last_name":"DELANOIX",
            "twitter_username":"anthonydelanoix",
            "portfolio_url":"http://www.instagram.com/antho.dlx",
            "bio":"\u2661 If you like what you see here, follow my adventures and more on Instagram @antho_dlx \u2661\r\n25 years old, born in south of France, living in Paris. Scandinavian culture lover, working in digital marketing, and trying to travel as much as possible.",
            "location":"Paris",
            "links":{
               "self":"https://api.unsplash.com/users/anthonydelanoix",
               "html":"https://unsplash.com/@anthonydelanoix",
               "photos":"https://api.unsplash.com/users/anthonydelanoix/photos",
               "likes":"https://api.unsplash.com/users/anthonydelanoix/likes",
               "portfolio":"https://api.unsplash.com/users/anthonydelanoix/portfolio",
               "following":"https://api.unsplash.com/users/anthonydelanoix/following",
               "followers":"https://api.unsplash.com/users/anthonydelanoix/followers"
            },
            "profile_image":{
               "small":"https://images.unsplash.com/profile-1456878349584-ba7fc98ac955?ixlib=rb-1.2.1&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=32&w=32",
               "medium":"https://images.unsplash.com/profile-1456878349584-ba7fc98ac955?ixlib=rb-1.2.1&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=64&w=64",
               "large":"https://images.unsplash.com/profile-1456878349584-ba7fc98ac955?ixlib=rb-1.2.1&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=128&w=128"
            },
            "instagram_username":"antho.dlx",
            "total_collections":0,
            "total_likes":11,
            "total_photos":169,
            "accepted_tos":true
         }
      }
   ]
}

 

Vamos obter os dados das urls das imagens, descrição e likes, de forma que nosso ListView vai exibir em uma coluna a imagem, e, em uma linha vai exibir a descrição e a quantidade de likes.

Nota Se você quiser obter os objetos Dart a partir do formato JSON pode utilizar este link:https://javiercbk.github.io/json_to_dart/

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_json 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.

Como podemos acessar dados via HTTP no Flutter ?

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.

O código usado para obter dados via Http é dado a seguir:

  Future<String> getJSONData() async {
    var response = await http.get(
        Uri.encodeFull("https://unsplash.com/napi/photos/Q14J2k8VE3U/related"),
        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 do futuro será Future<void>. Quando uma função que retorna um futuro é 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.

Exibindo Imagens no ListView

Como vamos exibir imagens, para obter desempenho, vamos usar imagens em cache.

Segundo a documentação temos que usar o pacote cached_network_image 0.7.0  que usa construtores para o placeholder e o widget de erro e usa o sqflite para o gerenciamento de cache.

Para isso vamos incluir no arquivo pubspec.yaml a referência ao pacote cached_network_image 0.7.0 conforme abaixo:

Teremos também que importar o pacote : import ‘package:cached_network_image/cached_network_image.dart’; no arquivo main.dart.

Definindo o código do projeto

Abra o arquivo main.dart na pasta lib e defina os seguintes imports:

import ‘package:flutter/material.dart’;
import ‘package:http/http.dart’ as http;
import ‘dart:convert’;
import ‘package:cached_network_image/cached_network_image.dart’;

O primeiro implementa o material design, o segundo permite realizar requisições http, o terceiro permite codificar e decodificar o JSON e o último permite trabalhar com imagens no cache.

A seguir defina o ponto de entrada da aplicação no método main() e o código do widget MyApp que estende deStatelessWidget:

void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'ListView - Http e Json'),
    );
  }
}

Agora precisamos incluir o código da classe MyHomePage() onde definimos a classe privada _MyHomePageState que sobrescreve o método createState():

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

Na classe _MyHomePageState() obtemos os dados no formato JSON e invocamos o widget _criaListView():

class _MyHomePageState extends State<MyHomePage> {  
  List data;
  // Função para obter os dados JSON
  Future<String> getJSONData() async {
    var response = await http.get(
        // codifiga a url
        Uri.encodeFull("https://unsplash.com/napi/photos/Q14J2k8VE3U/related"),
        // Aceita somente resposta JSON
        headers: {"Accept": "application/json"}
    );
    setState(() {
      // Pega os dados JSON
      data = json.decode(response.body)['results'];
    });
    return "Dados obtidos com sucesso";
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: _criaListView(),
    );
}

O widget _criaListaView cria um listview usando o construtor ListView.builder e invoca o widget _criaImagemColuna()passando os dados para exibição:

Widget _criaListView() {
    return ListView.builder(
      padding: const EdgeInsets.all(16.0),
      itemCount: data == null ? 0 : data.length,
      itemBuilder: (context, index) {
        return _criaImagemColuna(data[index]);
      }
    );
}

 

O widget _criaImagemColuna define um contâiner e um boxdecoration e tem como filho uma coluna onde vai exibir a imagem usando cache, depois invoca o widget _criaLinha passando os dados para exibir:

Widget _criaImagemColuna(dynamic item) => Container(
      decoration: BoxDecoration(
        color: Colors.white54
      ),
      margin: const EdgeInsets.all(4),
      child: Column(
        children: [
          new CachedNetworkImage(
            imageUrl: item['urls']['small'],
            placeholder: (context, url) => new CircularProgressIndicator(),
            errorWidget: (context, url, error) => new Icon(Icons.error),
            fadeOutDuration: new Duration(seconds: 1),
            fadeInDuration: new Duration(seconds: 3),
          ),
          _criaLinha(item)
        ],
      ),
    );

O widget _criaLinha define um ListTile no ListView e exibe a descrição e a quantidade de likes:

Widget _criaLinha(dynamic item) {
    return ListTile(
      title: Text(
        item['description'] == null ? '': item['description'],
      ),
      subtitle: Text("Likes: " + item['likes'].toString()),
    );
  }
  @override
  void initState() {
    super.initState();
    // Chama o método getJSONData() quando a app inicializa
    this.getJSONData();
  }
}

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:

Vemos assim os dados obtidos via http no formato JSON sendo exibidos no ListView.

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

“Palavra fiel é esta: que, se morrermos com ele, também com ele viveremos;
Se sofrermos, também com ele reinaremos; se o negarmos, também ele nos negará;
Se formos infiéis, ele permanece fiel; não pode negar-se a si mesmo.”
2 Timóteo 2:11-13

Referências: