Olá! Meu nome é Priscila Brambila, sou desenvolvedora mobile há alguns anos, e atualmente trabalho principalmente entendendo e melhorando a arquitetura de aplicativos.
Audiodescrição: Priscila é uma mulher branca, com cabelo castanho claro e comprido. Ela veste uma blusa azul e está em um ambiente de escritório, com uma parede clara ao fundo e uma estante com livros à direita.
Quando criamos um novo aplicativo do zero, as coisas parecem muito simples: há uma tela, uma navegação, um botão e tudo mais. No entanto, conforme o aplicativo cresce, percebemos que as coisas podem começar a ficar um pouco complexas, o código se embaralha e assim por diante.
Hoje, vamos entender algumas noções de como uma boa arquitetura pode nos ajudar a tornar nosso aplicativo escalável. Por isso, vamos explorar um pouco sobre Clean Architecture (Arquitetura Limpa) em Flutter.
Vamos desenvolver um aplicativo chamado Meu Explorer. Qual é a finalidade desse aplicativo? Ele é basicamente um aplicativo onde buscamos receitas.
Na nossa página inicial, começamos com uma receita aleatória. Ao clicar, conseguimos ver os detalhes dessa receita para entender como ela pode ser feita. Também podemos adicionar ou remover dos favoritos. É possível buscar outras receitas, por exemplo, ao inserir "meat" para carne. Em seguida, vemos os resultados, e a página interna se mantém igual à forma como já vimos anteriormente, com detalhes da receita, opção de salvar e remover dos favoritos, entre outros.
Isso nos permite observar a parte que se comunica externamente com a API. Para casos de dados salvos localmente, podemos ver nossos favoritos. A ideia é que os favoritos sejam salvos localmente, e assim entendemos a diferença na hora de buscar esses dados em relação à busca na API. O comportamento aqui é o mesmo: vemos as carnes, clicamos nelas e visualizamos os detalhes da receita.
Se gostaram, continuem aqui e vamos entender juntos como alcançar esse resultado.
Vamos discutir sobre Clean Architecture em Flutter. Vamos introduzir o assunto considerando um cenário comum: começamos um projeto de forma simples, com poucas telas, algumas requisições para APIs e uma navegação básica. Com o tempo, o projeto cresce e o código se torna complexo. Precisamos adicionar mais navegações, telas e comunicações com APIs, resultando em um emaranhado de elementos misturados. Com o aumento das regras de negócio, surgem dependências entre serviços, requisições e telas, tornando a manutenção difícil e os testes complicados.
Para resolver isso, é necessário definir uma arquitetura para o aplicativo. Ao definir uma arquitetura, conseguimos dividir as responsabilidades, desacoplando telas, gerenciamento de estado, regras de negócio, requisições de API, entre outros. Existem várias abordagens, como MVC e MVVM, e a escolha depende da necessidade, do tamanho do projeto e da familiaridade com a arquitetura. Hoje, vamos focar na Clean Architecture, que isola as regras de negócio da interface.
Na Clean Architecture, temos diferentes camadas. A camada de domínio contém as regras de negócio, incluindo as entidades e os casos de uso. Por exemplo, buscar receitas ou salvar favoritos são casos de uso. A camada de dados lida com o acesso aos dados e APIs, seja por comunicação remota ou armazenamento local, e faz a conversão dos dados para algo consumível pelo aplicativo. A camada de apresentação é responsável pelo que é visível para o usuário e pelo gerenciamento de estado, determinando, por exemplo, qual caso de uso será chamado com base na ação do usuário.
Detalhando mais, a camada de domínio inclui os casos de uso, que são as operações que o usuário realiza no aplicativo, como buscar uma receita ou salvar nos favoritos. As entidades, como a receita, possuem parâmetros como ingredientes, instruções, nome e foto, formando uma classe definida durante o desenvolvimento do aplicativo.
Na camada de dados, temos os repositórios, models (modelos) e data sources (fontes de dados), que podem ser locais ou remotos. Eles são responsáveis pela obtenção e transformação dos dados. Os repositórios são utilizados para decidir se a consulta de dados será feita por uma API local ou remota. Por exemplo, se o aplicativo possui um cache ou uma forma de consulta offline, é nessa camada que decidimos exibir dados do armazenamento local quando não há conexão com a internet.
Na camada de Presentation (Apresentação), mostramos a interface com o usuário e gerenciamos o estado de acordo com as ações do usuário ou com o que a tela está esperando receber. Enquanto o usuário realiza uma busca, essa camada é responsável por definir o estado como "a busca está carregando". Dentro do gerenciamento de estado, cuidamos disso, separando-o da lógica da tela em si.
Agora, vamos mostrar a estrutura geral das pastas da nossa aplicação. Temos a divisão das camadas de dados, domínio e apresentação. Pode surgir a dúvida sobre a presença de repositórios tanto na camada de domínio quanto na de dados. Isso ocorre porque, na camada de domínio, criamos apenas a abstração desses repositórios, ou seja, o contrato do método que será chamado nos repositórios. Definimos qual será o método, para qual chamada ele será feito e o que ele retornará, sem mais detalhes. Já nos repositórios da camada de dados, fazemos a implementação e todas as operações, incluindo consultas à API externa e ao armazenamento interno local.
Na camada de Presentation, as pastas dividem as páginas (telas inteiras), os widgets (componentes) de forma isolada e a pasta chamada State (Estado), que segue o nome do gerenciador de estado utilizado. Por exemplo, se escolhermos Provider, chamamos de Provider; se for Block, chamamos de Block. O gerenciador de estado é flexível, desde que o gerenciamento de estado permaneça isolado das outras camadas.
Na camada de Presentation, o ponto principal é garantir que ela seja responsável por traduzir a ação do usuário na tela para decidir qual caso de uso será chamado posteriormente. O gerenciador de estado escolhido deve seguir essa regra específica. Hoje, trabalharemos com o Qubit, que é uma simplificação do Block, facilitando o entendimento de seu funcionamento.
Falando sobre os benefícios de utilizar a CleanARC, temos o código e a manutenção facilitados, pois a estrutura é bem dividida. Consequentemente, os testes são feitos de forma isolada, seguindo a mesma divisão de responsabilidades, permitindo o uso organizado de ferramentas como o Mox. A lógica é reutilizável. Por exemplo, em um caso de uso onde o usuário quer alterar o número de telefone, a tela que chama esse caso de uso está em uma feature específica com sua própria camada de apresentação e gerenciador de estado, que não devem se misturar. No entanto, o caso de uso pode ser reutilizado em outra feature dentro do mesmo aplicativo, pois a partir da camada do use case, tudo está isolado de acordo com o caso de uso.
No aplicativo Meal Explorer, teremos algumas features, como buscar receitas, exibir uma receita aleatória ao carregar o aplicativo, salvar receitas nos favoritos e consultá-las posteriormente em uma aba específica de favoritos, conforme mostrado na prévia anterior. Na próxima aula, começaremos a desenvolver esse aplicativo juntos para entender como funciona cada camada e como podemos desenvolver e testar cada parte separadamente.
Entendemos um pouco sobre o conceito inicial de Clean Architecture. Agora, vamos mostrar o projeto inicial, de onde começaremos, e o que temos até agora. Observando a aplicação, nosso foco hoje não está nas telas, design ou interface de usuário. Vamos começar com um modelo já pronto no nosso aplicativo, apenas na parte visual. Atualmente, há apenas uma receita pré-definida, sem busca por API ou integração similar. O mesmo ocorre na lista de favoritos, que também não realiza nenhuma ação, apenas contém dados pré-definidos, sem integração com nossas APIs. Isso é proposital, pois a única coisa que queríamos deixar pronta era a interface de usuário. A parte das lógicas e demais funcionalidades, vamos implementar juntos.
Explicando um pouco sobre a estrutura do projeto, temos um pacote chamado "core components", que contém tokens e alguns estilos padrão do aplicativo, além dos widgets padrão que utilizamos, como o ícone de favorito, o cartão de receita, botão, entre outros. Essa divisão em pacotes visa organizar melhor o aplicativo. Dentro da nossa lib, temos apenas as telas e widgets, também separados por responsabilidades, mas sem lógica, apenas widgets codificados puramente na interface de usuário.
Dito isso, voltando ao nosso aplicativo, o que temos aqui? Vamos começar implementando nossa entidade dentro da camada de domínio, que é nossa regra de negócio. Por que começamos pela entidade? Porque é o tipo de dado que precisamos manipular dentro do aplicativo para realizar operações, como exibir resultados e ver detalhes. Atualmente, com os dados pré-definidos, percebemos que temos, pelo menos, a imagem, o título, os ingredientes e as instruções. Isso significa que nossa entidade precisa conter essas informações.
Para criar essa estrutura no aplicativo, vamos à pasta lib e criaremos "features", que é um padrão de estrutura.
features
Como nosso aplicativo é pequeno, trabalharemos com uma única "feature", mas, se necessário, poderíamos criar várias pastas com diferentes "features". Dentro dessa pasta, criaremos a pasta "domain", conforme a estrutura mencionada anteriormente.
meal_recipes
domain
Dentro dela, a pasta "entities", onde teremos as entidades que utilizaremos.
entities
A primeira entidade que criaremos será chamada de mealDetail, que conterá os dados completos da receita, incluindo a URL da imagem, o título, os ingredientes e as instruções.
meal_detail
Ao criar essa entidade, começamos com a classe MealDetail e definimos os parâmetros.
class MealDetail {
final String id;
final String name;
final String imageUrl;
final String instructions;
final List<Ingredient> ingredients;
}
Agora, vamos criar o construtor para a classe MealDetail, onde todos os parâmetros são obrigatórios.
const MealDetail({
required this.id,
required this.name,
required this.imageUrl,
required this.ingredients,
required this.instructions,
});
A entidade de ingredientes ainda não existe, mas "ingredientes" é o termo correto. Vamos criar essa entidade também.
ingredient.dart
class Ingredient {
final String name;
final String measure;
const Ingredient({required this.name, required this.measure});
}
Pensando no futuro, criamos a classe e, no Dart, mesmo que tenhamos dois objetos iguais, como dois mealDetails que sejam a mesma receita com a mesma ID e nome, para o Dart, são duas instâncias diferentes, pois estão armazenadas em pontos distintos da memória. Isso pode causar erros nos testes, pois objetos iguais nunca serão considerados iguais. Para corrigir isso, utilizamos um pacote do Flutter chamado Equatable, que permite igualar objetos de instâncias diferentes, evitando bugs no gerenciamento do aplicativo.
flutter pub add equatable
Adicionamos esse pacote e alteramos nossas classes para que estendam essa implementação.
import 'package:equatable/equatable.dart';
class Ingredient extends Equatable {
final String name;
final String measure;
const Ingredient({required this.name, required this.measure});
@override
List<Object?> get props => [name, measure];
}
Precisamos replicar essa implementação no nosso MealDetail, trazendo o import e aplicando as referências específicas dessa classe.
class MealDetail extends Equatable {
final String id;
final String name;
final String imageUrl;
final String instructions;
final List<Ingredient> ingredients;
const MealDetail({
required this.id,
required this.name,
required this.imageUrl,
required this.ingredients,
required this.instructions,
});
@override
List<Object?> get props => [id, name, imageUrl, ingredients, instructions];
}
Com essas entidades criadas e estendendo do Equatable, podemos realizar testes posteriormente.
Na próxima aula, vamos entender mais sobre casos de uso e a abstração dos repositórios.
O curso Flutter: construindo com Clean Architecture possui 485 minutos de vídeos, em um total de 62 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.
2 anos de Alura
Matricule-se no plano PLUS 24 e garanta:
Jornada de estudos progressiva que te guia desde os fundamentos até a atuação prática. Você acompanha sua evolução, entende os próximos passos e se aprofunda nos conteúdos com quem é referência no mercado.
Mobile, Programação, Front-end, DevOps, UX & Design, Marketing Digital, Data Science, Inovação & Gestão, Inteligência Artificial
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ê participa de eventos exclusivos, pode tirar dúvidas em estudos colaborativos e ainda conta com mentorias em grupo com especialistas de diversas á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.
2 anos de Alura
Todos os benefícios do PLUS 24 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.
Para estudantes ultra comprometidos atingirem seu objetivo mais rápido.
2 anos de Alura
Todos os benefícios do PRO 24 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.
Conecte-se ao mercado com mentoria individual personalizada, vagas exclusivas e networking estratégico que impulsionam sua carreira tech para o próximo nível.