Flutter

20 abr, 2020

Covid19 API com Flutter, Mobx e Modular (parte 1)

Publicidade

Em tempos difíceis que estamos vivendo com a epidemia do coronavírus, pode ser uma oportunidade para aprimorar habilidades e avançar nos estudos. #fiqueemcasa

A proposta deste artigo é construir uma aplicação em Flutter que consome os dados da API COVID19.

Realizaremos também a integração com MobX para gestão do estado e o Modular para injeção de dependências.

Telas do Aplicativo

Se preferir, fiz a integração dessa API com Angular. O tutorial e o código estão aqui.

Contextualizando

Comecei a programar em Flutter no final do ano passado (2019) e a princípio estranhei a forma como declaramos os Widgets e os abre e fecha de parênteses sem fim…rsrs

No entanto, o que mais me incomodou naquela época foi a discussão referente a Blocs. Como a maioria dos exemplos para iniciantes não separam as regras de negócio da view, tudo ficou um pouco mais nebuloso.

O meu objetivo com esse tutorial é mostrar que, mesmo para exemplos simples, como esse de consumir uma API, é possível trabalhar de forma mais organizada o código.

Assim, a justificativa em utilizar o Modular se dá pois a biblioteca “permite gerenciar a injeção de dependências e rotas em apenas um arquivo por módulo”.

Ainda, de acordo com a documentação, o foco da biblioteca é baseado em:

  • Gerência Automática de Memória.

Superado essa fase, outro aspecto da implementação é gerenciar o estado da aplicação, ou seja, entre outras coisas, a comunicação dos dados com a UI. Para isso a escolha se deu com o MobX, que é uma biblioteca que facilita gerenciar reativamente o estado dos aplicativos, utilizando observáveis, ações e reações.

Passos iniciais

Caso você seja iniciante no framework, recomendo o link para instalação do framework.

Após a instalação, no terminal, posicione o cursor no diretório de sua preferência e informe o seguinte comando:

Será iniciado a criação do projeto na pasta flutter_covid19 com todos os artefatos para iniciarmos a codificação da nossa aplicação.

Após o término, vamos abrir o projeto utilizando o Visual Studio Code.

Informe os seguintes comando para isso:

cd flutter_covid19
code .

Instalando as dependências

Na pasta raiz encontre o arquivo pubspec.yaml. Ele traz a relação das dependências do projeto. É nesse arquivo que incluímos, por exemplo, as bibliotecas, caminhos dos arquivos estáticos ou dependências de desenvolvimento.

dependencies:
  dio: ^3.0.9
  flutter_modular: ^1.0.0
  flutter_mobx: ^1.0.1
  mobx: ^1.0.2+1

  flutter:
    sdk: flutter
  cupertino_icons: ^0.1.3

dev_dependencies:
  mobx_codegen: ^1.0.2
  build_runner: ^1.8.0

  flutter_test:
    sdk: flutter

Incluímos 4 pacotes na seção dependencies (lembre-se de respeitar o recuo…).

dio : Responsável pelas requisições REST

flutter_modular: Injeção de dependências, rotas…

flutter_mobx e mobx: responsáveis pela implementação do MOBX

Na seção dev_dependencies incluímos o mobx_codgen que irá gerar automaticamente os artefatos com base nas anotações que codificarmos no controle e o build_runner que auxilia todo esse processo.

Ao salvar, a IDE executa o comando flutter packages get que realiza o download das dependências.

Separando os papeis

Em lib, vamos criar 4 pastas:

  • app : Salvaremos o módulo e o Widget principal

Com isso, já conseguimos definir um mínimo de organização para o projeto pensando na manutenção do código.

Modelos

Na pasta models vamos criar 2 arquivos, mundo_model.dart e pais_model.dart que respectivamente irão representar as informações da pandemia relativa aos dados globais e de um país em específico.

class Mundo {
  int casos;
  int mortes;
  int recuperados;
  int paisesAfetados;
  DateTime ultimaAtualizacao;

  Mundo(
      {this.casos,
      this.mortes,
      this.recuperados,
      this.paisesAfetados,
      this.ultimaAtualizacao});

  factory Mundo.fromJson(Map doc) {
    return Mundo(
        casos: doc['cases'],
        mortes: doc['deaths'],
        recuperados: doc['recovered'],
        paisesAfetados: doc['affectedCountries'],
        ultimaAtualizacao: new DateTime.fromMillisecondsSinceEpoch(doc['updated']));
  }
}

Após a definição dos atributos e do construtor, codificamos um método estático para mapear os dados retornados da API (fromJson) e também um ajuste para exibir a data em Dart utilizando o DateTime com o método fromMillisecondsSinceEpoch.

O código pais_model.dart classe ficou assim:

import 'package:flutter_covid19/models/mundo_model.dart';

class Pais extends Mundo {
  String nome;
  int mortesHoje;
  int criticos;
  String urlBandeira;

  Pais(
      {this.nome,
      this.criticos,
      this.mortesHoje,
      this.urlBandeira,
      int casos,
      int mortes,
      int recuperados})
      : super(casos: casos, mortes: mortes, recuperados: recuperados);

  factory Pais.fromJson(Map doc) {
    return Pais(
      casos: doc['cases'],
      mortes: doc['deaths'],
      recuperados: doc['recovered'],
      mortesHoje: doc['todayDeaths'],
      nome: doc['country'],
      criticos: doc['critical'],
      urlBandeira: doc['countryInfo']['flag'],
    );
  }
}

Na assinatura da classe herdamos de Mundo, reaproveitando o código para atributos em comum com extends Mundo. Outra diferença do código está no método construtor ao utilizamos o super para inicializar os atributos herdados.

Uma observação também para a forma como recuperamos a urlBandeira. urlBandeira: doc[‘countryInfo’][‘flag’], pois na API esse dado está aninhado em um objeto interno CountryInfo.

Mais informações sobre API em https://github.com/NovelCOVID/API.

Criando o Repositório

Codificaremos a classe que acessa o endpoint da API e os métodos.

Vamos criar o arquivo covidapi_repository.dart na pasta respositories.

import 'package:dio/dio.dart';
import 'package:flutter_covid19/models/mundo_model.dart';
import 'package:flutter_covid19/models/pais_model.dart';

class CovidApiRepository {
  final Dio _dio = Dio();
  String url = "https://corona.lmao.ninja/v2";

  Future<Mundo> getMundo() async {
    Response response = await _dio.get('$url/all');
    if (response.statusCode != 200) {
      throw Exception();
    } else {
      return Mundo.fromJson(response.data);
    }
  }

  Future<Pais> getPais({String pais}) async {
    Response response = await _dio.get('$url/countries/$pais');
    if (response.statusCode != 200) {
      throw Exception();
    } else {
      return Pais.fromJson(response.data);
    }
  }
}

Instanciamos o objeto _dio e declaramos o endereço da API para o atributo url.

No método getMundo criamos um objeto response, invocamos o método HTTP get, passando a url e concatenando com ‘/all’, conforme a documentação do endpoint.

Em seguida verificamos o código do status para decidir uma exceção em caso de diferente de 200, lançamos o erro.

Senão, mapeamos o conteúdo da resposta para o método estático definido no modelo em Mundo.fromJson(response.data).

A implementação do método getPais é bem parecida. Informamos um parâmetro opcional com as chaves em {String pais} e concatenamos com a url.

No entanto, dessa vez mapeamos o retorno da requisição para país em Pais.fromJson(response.data)

Dessa forma, finalizamos a classe responsável pelas requisições.

Mapeamento de Rotas e Injeção

Vamos finalizar essa primeira parte escrevendo as classes que definem as rotas e o start da aplicativo.

Na pasta app crie os arquivos app_module.dart e app_widget.dart.

Em app_widget codificamos a classe AppWidget

import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';

class AppWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      initialRoute: "/",
      navigatorKey: Modular.navigatorKey,
      onGenerateRoute: Modular.generateRoute,
    );
  }
}

Nesse Widget setamos algumas configurações globais, como retirar aquele banner de debug (debugShowCheckedModeBanner) e informar que o Modular gerenciará as rotas em navigatorKey onGenerateRoute.

Em app_module.dart fazemos:

import 'package:flutter/material.dart';
import 'package:flutter_covid19/respositories/covidapi_repository.dart';
import 'package:flutter_modular/flutter_modular.dart';

import 'app_widget.dart';

class AppModule extends MainModule {

  /
  @override
  List<Bind> get binds => [
      Bind((i) => CovidApiRepository()),      
  ];

  // rotas do módulo
  @override
  List<Router> get routers => [
    Router("/", child: (_, args) => SplashPage())        
  ];

  //widget principal   
  @override
  Widget get bootstrap => AppWidget();
}

Estendemos a classe AppModule para MainModule, assim devemos implementar 3 métodos.

binds => Classes que vamos fazer a injeção de dependência. Já incluímos o repositório da API.

routers => Definiremos as rotas. A ‘/’ é a rota inicial. Colocamos uma tela de abertura SplashPage() que será apresentada na parte 2.

bootstrap => O widget principal. No nosso caso, AppWidget(), que é foi a classe criada anteriormente.

Na parte 2, vamos finalizar o desenvolvimento, construindo as interfaces UI e o controlador.

Até +.

Acesse aqui o repositório do Github