Você já ouviu falar de Clean Architecture (Arquitetura Limpa)? Já utilizou em um contexto Flutter?
Te desejo boas-vinda a mais um curso de arquitetura e Clean Code! Meu nome é Matheus e serei seu instrutor nesta jornada!
Audiodescrição: Matheus se autodescreve como um homem branco, de cabelos curtos e lisos na cor castanho escuro. Veste uma camiseta azul marinho com a logo da Alura em branco e está sentado sob uma cadeira gamer na cor preta. Ao fundo, uma parede iluminada sob tons de roxo e azul. À sua direita, uma estante com artigos decorativos.
Neste curso, vamos desenvolver um aplicativo Hyrule, que é um compêndio de informações do jogo Zelda Breath of the Wild. Mas qual é o resultado que vamos atingir? Teremos uma tela com várias categorias de itens, como monstros e criaturas. Ao clicar em uma dessas categorias, baixaremos entradas vindas de uma API. Para fazer essa conexão com a API, vamos utilizar a biblioteca dio.
Clicando em uma entrada, temos a possibilidade de adicionar essa entrada no nosso banco de dados. Para fazer essa conexão entre a tela e o banco de dados, vamos usar a biblioteca Floor. Essa biblioteca vai permitir que nós também façamos inserção e remoção de dados no banco de dados, além da visualização de todas as entradas em uma tela de favoritos. Podemos também deslizar uma entrada para excluí-la do banco de dados.
Para acompanhar neste curso, é importante que você já tenha conhecimentos prévios de Flutter, saiba construir layouts, entenda de persistência de dados e consiga fazer transições de telas, passando informações de uma para a outra, além de fazer requisições HTTP.
Com todo esse conhecimento pronto, podemos prosseguir e começar a desenvolver o aplicativo Hyrule. Vejo você no próximo vídeo!
Antes de começar, é importante que você já tenha baixado os arquivos do projeto disponibilizados na atividade anterior de preparação do ambiente.
Vale ressaltar que esse curso foi inteiramente planejado, pensado e inspirado pelo Clean Code (Código Limpo) e também pelo Clean Dart (Dart Limpo), que é um padrão de projeto desenvolvido pelo time do Fluterando. Todas as decisões que serão tomadas durante o desenvolvimento do projeto são adaptações minhas, como instrutor, inspiradas nessas ferramentas. Vale reforçar que o projeto não deve se adaptar à nossa arquitetura, nossa arquitetura é que precisa se adaptar ao nosso projeto.
Com esse esclarecimento resolvido, vamos ao desenvolvimento do projeto!
Nós vamos desenvolver um aplicativo chamado Hyrule, que será um estilo compendium do jogo Zelda Breath of the Wild. Este aplicativo servirá como uma ferramenta para as pessoas jogadoras terem acesso às informações de itens, monstros e outras características do jogo.
Analisando rapidamente o layout que temos no Figma, nós temos quatro telas para desenvolver, onde teremos uma categoria para escolher, as informações da categoria selecionada que será buscada de uma API, os detalhes e, finalmente, uma tela de itens salvos, onde as informações ficarão armazenadas dentro do dispositivo.
O aplicativo terá uma lista de requisitos que inclui uso da API do Hyrule Compendium, disponível também na descrição do projeto no GitHub. Nós vamos buscar as informações de acordo com a categoria selecionada pela pessoa usuária. Esta pessoa também deve ser capaz de visualizar todas as entradas daquela categoria, adicionar e remover uma entrada aos favoritos e, além disso, todas as informações precisam ser salvas localmente no dispositivo. Assim, teremos acesso à API e ao nosso banco de dados.
Tendo em vista todas essas informações, por onde vamos começar? Pensando no que o cliente solicita, nosso aplicativo é dividido em várias camadas. Vamos iniciar pelo núcleo, onde temos nosso domínio, modelos, entidades e regras de negócio.
É importante lembrar, falando de arquitetura, que o desenvolvimento móvel, de forma geral (Android, iOS, Flutter), já trabalha com arquitetura em camadas naturalmente. O Clean será apenas um padrão a mais que vamos adicionar a isso.
Vamos então definir nossas camadas através das pastas dentro do nosso projeto.
No Visual Studio Code, que é a IDE escolhida para desenvolver esse aplicativo, dentro da pasta lib
, criaremos uma pasta chamada domain
, onde ficarão nossos domínios. Dentro da pasta domain
, criaremos outra pasta chamada models
, pois poderemos ter mais de um modelo no futuro. Nela, criaremos nosso arquivo de entrada, entry.dart
.
Este arquivo conterá a estrutura das informações do aplicativo, que inclui o nome, título do cartão, ID e uma sequência de informações que esse cartão precisa. Para saber como montar nossa entrada, podemos visualizar o resultado da API e usar isso como referência para definir nosso modelo.
Vamos abrir o resultado de uma API que contém categorias de monstros:
'data": [
{
"category": "monsters",
"common_locations": [
"Eldin Mountains",
"Tabantha Frontier"
],
"description": "A spirit of fire has taken the form of this giant dragon. Making its home in the Eldin region, it's said to have served the Spring of Power since ancient times. An old saying goes, \"The dragon ascends to the heavens as the sun begins to set,\" but nobody has witnessed this in the current age. The flames that coat its body make it dangerous to get near, but Dinraal bears no ill will toward people.",
"dlc": false,
"drops": null,
"id": 153,
"image": "https://botw-compendium.herokuapp.com/api/v3/compendium/entry/dinraal/image",
"name": "dinraal"
Note que ispomos de algumas informações como category
(categoria), common_locations
(localização comum), description
(descrição), id
(ID), image
(imagem) e name
(nome). Essas são as seis informações que precisamos.
Vamos começar de baixo para cima, ou seja, pelo ID
, assim seguimos uma ordem de importância. Vamos alternar entre essa página e o editor de código, para que possamos usá-la como uma espécie de referência para escrever esse modelo de entrada.
No editor de código, definiremos uma classe Entry
. Ela precisa ter um int
, que será nosso id
. Também precisamos de uma string para o nome, uma para a imagem e uma para a descrição.
class Entry {
int id;
String name;
String image;
String description;
}
Temos também commonLocations
. Mas há uma ressalva: temos uma lista de strings. Contudo, considerando que temos que salvar essas informações localmente no banco de dados, e sabendo que no banco de dados não podemos salvar um vetor ou uma lista, mas apenas strings, não conseguiremos lidar com isso diretamente.
Portanto, embora a gente declare commonLocations
como string, posteriormente teremos que transformar isso em um objeto JSON e de JSON para uma lista novamente.
class Entry {
int id;
String name;
String image;
String description;
String commonLocations
}
Por último, temos a string de category
(categoria).
class Entry {
int id;
String name;
String image;
String description;
String commonLocations
String category;
}
Bem, concluímos a escrita da nossa entrada, mas ainda faltam algumas informações. Estamos recebendo erros porque falta adicionar nosso construtor e nossa estrutura de dados da classe. Mas podemos começar a usar ferramentas para facilitar nosso trabalho ao invés de fazer tudo manualmente.
À medida que desenvolvemos aplicativos maiores ou aumentamos a frequência de desenvolvimento, ganhamos familiaridade com a forma como o código é realmente escrito. É normal usarmos essas ferramentas, afinal não vale a pena fazer tudo manualmente toda vez se já temos maneiras de facilitar nosso trabalho.
A primeira ferramenta que vamos implementar é um plugin do Visual Studio Code, que vai gerar para nós a nossa estrutura de dados. O nome do plugin que vamos instalar é "Data Class Generator", criado pelo hzgood, que será capaz de gerar todas as informações da estrutura de dados no Dart. Vamos pesquisá-lo na aba de extensões vou instalá-lo.
Após instalado, podemos fechar a aba e reiniciar o Visual Studio Code. O atalho "Command + Shift + P" reinicia o VS Code no MAC; já no Windows é possível reiniciar a IDE com o comando "Ctrl + Shift + P". Feito isso, digitamos reload
e selecionamos a opção "Developer: Reload Window" para recarregar a janela do VS Code. Com isso temos acesso ao novo plugin que acabamos de instalar.
Note que o editor está emitindo vários erros na nossa entrada. Se colocarmos o cursor em cima de id
e pressionarmos "Command + ponto" ou "Ctrl + ponto", temos acesso às opções de que gerar a classe de dados, o construtor, a serialização JSON e várias outras. Queremos nosso construtor, então clicaremos em "Generate constructor".
Ao clicar, o construtor é gerado:
// ignore_for_file: public_member_api_docs, sort_constructors_first
class Entry {
int id;
String name;
String image;
String description;
String commonLocations;
String category;
Entry({
required this.id,
required this.name,
required this.image,
required this.description,
required this.commonLocations,
required this.category,
});
}
Agora, posicionamos o cursor sobre Entry
, usamos o atalho "Command + ponto" ou "Ctrl + ponto" e clicamos na opção "Generate JSON serialization" para gerar a serialização JSON:
// ignore_for_file: public_member_api_docs, sort_constructors_first
class Entry {
int id;
String name;
String image;
String description;
String commonLocations;
String category;
Entry({
required this.id,
required this.name,
required this.image,
required this.description,
required this.commonLocations,
required this.category,
});
Map<String, dynamic> toMap() {
return <String, dynamic>{
'id': id,
'name': name,
'image': image,
'description': description,
'commonLocations': commonLocations,
'category': category,
};
}
factory Entry.fromMap(Map<String, dynamic> map) {
return Entry(
id: map['id'] as int,
name: map['name'] as String,
image: map['image'] as String,
description: map['description'] as String,
commonLocations: map['commonLocations'] as String,
category: map['category'] as String,
);
}
String toJson() => json.encode(toMap());
factory Entry.fromJson(String source) => Entry.fromMap (json.decode(source) as Map<String, dynamic>);
}
Todo o código já foi gerado. Portanto, agora temos a função toMap()
, da nossa entrada, fromMap()
e também toJson()
, encode()
e fromJson()
, com todas as informações que precisamos.
Ainda falta um detalhe: o commonLocations
que teremos que converter de uma string para uma lista e salvar no banco de dados para então transformá-lo em uma string novamente. Faremos isso no próximo vídeo!
Um detalhe muito importante a ser lembrado é que escrevemos commonLocations
, dentro da nossa entrada, utilizando Camel Case. Ou seja, as palavras são delimitadas por letras maiúsculas. Assim, "commons" começa com a letra minúscula e "Locations" com a letra maiúscula.
Contudo, se observarmos a API, veremos que a chave de mesmo nome está escrita como Snake Case. O que significa que a palavra é separada por um underscore (sublinhado): common_locations
.
'data": [
{
"category": "monsters",
"common_locations": [
"Eldin Mountains",
"Tabantha Frontier"
],
Então, quando formos buscar essa informação na API, teremos um problema, afinal, nós usamos o padrão Dart de escrever como Camel Case. Portanto, temos um ajuste a ser feito ao extrair as informações da API.
Voltando ao VS Code, abaixo da linha 41, criaremos uma função que irá converter nossa string em uma lista de strings. Esta função será chamada commonLocationsConverter()
e irá retornar um jsonDecode()
, pois queremos transformar os dados originados do JSON, que serão armazenados no banco de dados, em uma lista de strings que possa ser usada posteriormente em outras partes da API.
Nossa source será nossa própria commonLocations
dentro da entrada. A partir disso, precisamos converter isso para uma lista usando .as List
. Como será uma lista e será um JSON, teoricamente, temos que dar seu tipo como dynamic
.
factory Entry.fromMap(Map<String, dynamic> map) {
return Entry(
id: map['id'] as int,
name: map['name'] as String,
image: map['image'] as String,
description: map['description'] as String,
commonLocations: map['commonLocations'] as String,
category: map['category'] as String,
);
}
List<String> commonLocationsConverter() {
return (jsonDecode(commonLocations) as List<dynamic>)
}
Depois disso, precisamos mapear, pois agora que é uma lista, temos que pegar cada um desses itens, convertê-los em uma string e depois retornar. Então, será e as String
. No final de tudo isso, faremos um toList()
.
List<String> commonLocationsConverter() {
return (jsonDecode(commonLocations) as List<dynamic>).map((e) => e as String).toLidt();
}
Essa função está convertendo o JSON recebido do banco de dados em uma lista de string para que possamos usar em outras partes do projeto.
Agora, para corrigir commonLocations
, precisamos transformar esse o map
para outra coisa.
Na linha 38, temos map['commonLocations']
. Esse map[]
, com essa chave, é a partir de onde a busca na API acontece. Mas temos que alterar isso, pois precisamos usar a chave correta. Além disso, ao recebermos a informação da API, também precisamos convertê-la para algo que o banco consiga salvar, que será o objeto JSON.
Faremos um jsonEncode()
. O objeto que receberemos será o map[]
, passando a chave. Só que a chave agora, ao invés de ser 'commonLocations'
, em Camel Case, será 'common_locations'
, em Snake Case.
factory Entry.fromMap(Map<String, dynamic> map) {
return Entry(
id: map['id'] as int,
name: map['name'] as String,
image: map['image'] as String,
description: map['description'] as String,
commonLocations: jsonEncode(map['common_locations']),
category: map['category'] as String,
);
}
Aqui, temos um detalhe interessante a ser realizado, que é validar se receberemos uma localização ou não. Ao analisar a API, esse commonLocations
pode vir vazio e não queremos que um erro ocorra ou que nossa aplicação quebre por falta de informação.
Por isso, adicionaremos duas interrogações (??
) para um valor padrão a ser retornado. Isso faz parte do recurso que nos permite validar se algo pode vir nulo ou não. Retornaremos uma string contendo o texto "Sem localização". Assim, se não tivermos esse parâmetro dentro da API, receberemos a mensagem "Sem localização" e nossa aplicação não quebrará.
factory Entry.fromMap(Map<String, dynamic> map) {
return Entry(
id: map['id'] as int,
name: map['name'] as String,
image: map['image'] as String,
description: map['description'] as String,
commonLocations: jsonEncode(map['common_locations'] ?? ['Sem localização']),
category: map['category'] as String,
);
}
Com isso, basicamente terminamos toda a nossa entrada e podemos prosseguir para a nossa regra de negócios!
O curso Flutter: praticando e adaptando arquitetura limpa possui 110 minutos de vídeos, em um total de 58 atividades. Gostou? Conheça nossos outros cursos de Flutter em Mobile, ou leia nossos artigos de Mobile.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.