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.
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.
- Injeção de Dependências.
- Controle de Rotas Dinâmicas.
- Modularização de Código.
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
- models : Classes que representam as informações da API
- pages : Uis e controladores (regras)
- repositories : Serviço de requisição a API
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 e 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é +.