Repositório com o código-fonte da solução para o desafio técnico da Meiuca.
O aplicativo é um agregador de notícias que utiliza a News API para obter as notícias.
O desafio consiste em desenvolver componentes utilizando a folha Design Tokens fornecida pela Meiuca. Após a criação dos componentes, desenvolver um aplicativo que os utiliza para exibir as notícias obtidas pela News API.
Busquei manter a arquitetura o mais independente possível de bibliotecas externas, recorrendo a elas apenas quando absolutamente necessário. Além disso, adotei as melhores práticas de desenvolvimento de software, incluindo a implementação de testes automatizados e arquitetura baseada em Clean Architecture.
A Folha de Design Tokens é um arquivo JSON que contém informações de cores, tipografia, espaçamento e outros valores utilizados para a criação dos componentes do Design System.
Atualmente, a Amazon mantém um projeto open-source chamado style-dictionary, que permite a geração de código a partir de um arquivo de Design Tokens, simplificando seu uso para diversos frameworks e linguagens.
Realizei alguns testes utilizando o style-dictionary
para gerar código para o Flutter, mas o suporte para ele ainda é limitado, o que dificultou bastante sua utilização, necessitando de diversas configurações.
- Não transforma porcentagens em valores literais, como
$border-radius-*
e$line-height-*
; - Suporta apenas tipos primitivos e alguns do pacote
dart:ui
, não incluindoEdgeInsets
eBorderRadius
. - Reporta muitas colisões de tokens na categoria
size
quando gerado em um único arquivo; - A conversão de valores do tipo
size
é feita de maneira errônea, considerando todos como se fossemrem
, mesmo que alguns sejampx
, exigindo uma configuração pouco documentada para corrigir isso.
Também avaliei a possibilidade de usar uma biblioteca alternativa chamada style-dictionary-figma-flutter, que utiliza internamente o style-dictionary
. No entanto, sua configuração é bastante limitada, o que impossibilita a personalização. Criei um fork do projeto e realizei algumas alterações, mas ainda assim não obtive sucesso, pois isso demandaria muito mais tempo do que o disponível.
Pelo fato do suporte ao Flutter ser limitado, foi necessário criar um pacote Node.js localizado em packages/ds/generator. Este pacote inclui novas ações (actions
), transformações (transforms
) e formatos (formats
) para melhor suportar os tipos usados no Flutter e atender às necessidades do projeto. Embora os recursos ainda sejam básicos, a estrutura aplicada ao pacote permite fácil extensão para dar suporte a novos tipos e formatos.
Foram criados novos tokens com base nas referências do arquivo de Design Tokens fornecido pela Meiuca. Isso possibilitou a criação de tokens para os componentes, como heading_small
, subtitle_small
e paragraph
.
As configurações do pacote estão disponíveis em packages/ds/config.js. Os arquivos de Design Tokens usados como base para a geração de código estão localizados em packages/ds/properties.
Ao executar o comando make ds
, o pacote style-dictionary
é executado, gerando os arquivos de tokens. Em seguida, um analisador de código é aplicado para garantir a conformidade com os padrões de código e formatação.
Aqui está o resultado final do código gerado: packages/ds/lib/src/style_dictionary.dart.
O componente CardContent
é um componente que exibe o conteúdo de uma notícia. O componente é composto por um título, uma descrição, uma breve descrição e um botão para abrir a notícia no navegador.
A tela de listagem de notícias é composta por uma lista de notícias, que são exibidas utilizando o componente CardContent
.
Imagem gerada através dos golden tests.
A tela de erro é exibida quando ocorre um erro ao obter as notícias. A tela é composta por uma ilustração, uma mensagem de erro e um botão para tentar novamente. A ilustração foi obtida no site undraw.co.
Imagem gerada através dos golden tests.
O aplicativo foi desenvolvido utilizando a arquitetura baseada em Clean Architecture, com a divisão em camadas de acordo com o diagrama abaixo:
O projeto foi organizado em pacotes, visando a extração de arquivos por contexto, de modo a permitir a reutilização de componentes em diferentes partes da aplicação. A estrutura do projeto pode ser descrita da seguinte forma:
lib
: Este diretório contém o código-fonte do aplicativo.packages/core
: Este pacote contém abstrações e utilitários que geralmente são compartilhados por outros pacotes. Dentro dele, encontramos:packages/core/network
: Abstrações e utilitários relacionados à comunicação com a rede.packages/core/navigation
: Abstrações que facilitam a navegação entre telas.
packages/commons
: Aqui, estão disponíveis implementações, como funções, extensões e outros elementos que podem ser reutilizados. Este pacote se subdivide em:packages/commons/foundation
: Implementações comuns que podem ser utilizadas em diversos pacotes para criar funcionalidades.packages/commons/testing
: Este pacote contém código para simplificar a implementação de testes automatizados.
packages/ds
: Este pacote abriga os tokens do Design System.packages/features
: Este pacote engloba o código relacionado às funcionalidades do aplicativo. Internamente, ele é organizado da seguinte forma:packages/features/news
: Subpacote dedicado à funcionalidade de notícias. Este subpacote está dividido em diretórios que seguem a estrutura da Clean Architecture:lib/src/domain
: Neste diretório, encontram-se as regras de negócio, entidades e abstrações.lib/src/data
: A camada de dados, incluindo a implementação de repositórios e fontes de dados.lib/src/presentation
: A camada de apresentação, que engloba a implementação das telas, view models, widgets e outros elementos visuais.test
: Este diretório é dedicado aos testes automatizados.
Para separar a lógica de apresentação da lógica de negócio, apliquei o conceito do MVVM (Model-View-ViewModel) juntamente com a classe ChangeNotifier, que já está incluída no SDK do Flutter. A classe ChangeNotifier
é uma implementação do padrão Observer que permite que os Widgets sejam notificados sempre que o estado do objeto é alterado.
Além disso, utilizei a classe ChangeNotifierProvider da biblioteca provider para aplicar o conceito de Inversão de Controle e Injeção de Dependências na minha aplicação.
- equatable: Utilizada para comparar objetos.
- flutter_svg: Utilizada para renderização de imagens SVG, como a ilustração para o estado de erro.
- freezed: Utilizada para gerar classes imutáveis, facilitando a implementação do conceito de Value Object.
- http: Utilizada para fazer requisições HTTP.
- provider: Utilizada para injeção de dependências e inversão de controle.
- url_launcher: Utilizada para abrir URLs;
- mocktail: Mocks de funções e classes para testes unitários;
- golden_toolkit: Golden tests;
- alchemist: Facilita a criação de golden tests.
- SDK Flutter >=3.13.9
- Chave de API para a News API
- Node.js >= 14.0.0: Necessário para executar o gerador de código com o
style-dictionary
. - Opcional: make, para facilitar a execução de alguns comandos.
- Itens opcionais de acordo com a plataforma para a qual deseja compilar:
- Chrome, que permite o debug quando compilado para Web.
- XCode, para compilar para iOS ou macOS.
- SDK Android, para compilar para Android.
- JDK, para compilar para Android.
- Ninja, para compilar para Linux.
Para a configuração, copie o arquivo .env.example
para .env
(ou utilize make setup
, se preferir) e preencha a variável NEWS_API_KEY
com a sua chave de API da News API.
O conteúdo do arquivo .env
deve ser assim:
NEWS_API_URL=https://newsapi.org/v2/
NEWS_API_KEY=<sua chave de API>
Para compilar o aplicativo, você pode executar os seguintes comandos:
# Web
flutter build web --dart-define-from-file=.env
# Android
flutter build apk --dart-define-from-file=.env
# iOS
flutter build ios --dart-define-from-file=.env
# macOS
flutter build macos --dart-define-from-file=.env
Para executar o aplicativo, você pode usar os seguintes comandos:
# Web
flutter run -d chrome --dart-define-from-file=.env
# ou
flutter run -d web-server --dart-define-from-file=.env
# Android/iOS/macOS
flutter run -d <device> --dart-define-from-file=.env
Se você tiver o make
instalado, pode executar os comandos da seguinte forma: make <target>
.
Execute make help
para ver a lista de comandos disponíveis:
Uso: make [target]
target:
help Mostra todos os comandos disponíveis com uma breve descrição
setup Cria o arquivo .env e instala as dependências
ds Cria/atualiza os dicionários de estilo a partir dos arquivos de Design Tokens
run Executa o app Flutter com o arquivo .env; você pode adicionar argumentos adicionais como `make run args='-d chrome'`
run-web Executa o app Flutter Web com o arquivo .env
test-all Executa todos os testes
Imagem extraída do artigo Por que e o que é possível testar? da Alura.
A maior quantidade de testes implementados foram os testes unitários, por serem mais rápidos de executar e menos custosos de manter.
Para os testes unitários foram utilizados mocks para simular o comportamento das dependências de cada unidade. Os mocks foram criados utilizando a biblioteca mocktail.
Para os testes de UI foram utilizados os golden tests, que são testes que comparam a renderização de um Widget
com uma imagem de referência. Optei por realizar esses testes de forma integrada, somente fazendo mock da navegação e da camada de network, garantindo assim que todas as camadas estão funcionando corretamente.
Para executar todos os testes, execute o seguinte comando:
make test-all
Ou acesse a pasta de cada pacote individualmente e execute o comando flutter test
. Os pacotes que possuem testes automatizados são:
packages/core/network
packages/features/news
Possíveis evoluções para o projeto:
- Responsividade: Melhorar a experiência do usuário em dispositivos com telas maiores, como tablets e desktops;
- Tratamento de Erros: Melhorar o tratamento de erros, exibindo mensagens mais amigáveis e tratando erros específicos;
- Skeletons: Implementar skeletons para melhorar a experiência do usuário enquanto os dados são carregados;
- Icone da Aplicação: Criar um ícone para a aplicação;
- Melhorar Scroll Infinito: Aprimorar a experiência do scroll infinito, incluindo loading, feedback de erro e evitar noticias repetidas incluindo a data como parâmetro na requisição;
- Automatizar a Geração de Código a partir do Arquivo de Design Tokens: Com tempo hábil e alinhamento com o time de design e outros desenvolvedores, seria possível automatizar a geração de código a partir do arquivo exportado pelo Figma, utilizando o
style-dictionary
em uma biblioteca própria para o Flutter; - Aprimorar Gerador de StyleDictionary: Aprimorar o gerador de código para que seja possível utilizar referências entre tokens, como
$spacing-1: $spacing-2
; - Dark Mode: Implementar o dark mode;
- Internacionalização: Internacionalizar o aplicativo, substituindo as strings hardcoded por chaves;
- Design Tokens de Componentes: Criar um arquivo de Design Tokens específico para o estilo dos componentes, utilizando a sintaxe do
style-dictionary
para facilitar a geração de código; - Galeria do DS: Criar uma galeria com todos os componentes do DS, para facilitar a visualização e testes dos componentes, que pode ser automatizada;
- CI/CD: Configurar o CI/CD para executar os testes, análise estática e gerar os artefatos de compilação para as plataformas suportadas, inclusive publicando-os em suas respectivas lojas e no GitHub Pages;
- Mais Testes: Implementar mais testes unitários, de integração e de UI, inclusive para os componentes e interações;
- Firebase Crashlytics: Configurar o Firebase Crashlytics para monitorar os erros e falhas do aplicativo.
- Contribuir com o StyleDictionary: Contribuir com o projeto style-dictionary para melhorar o suporte ao Flutter.