Padrões de projeto no Design System - Flutter
Conteudo completo: Artigo
⏳ Necessidade
Atualmente, passamos pela necessidade de criar um padrão para o design system, de forma a descomplicar as regras de tela e definir responsabilidades para nossos componentes. Devemos desenvolver formas de testar, documentar, acelerar e garantir consistência na qualidade do desenvolvimento.
De início, garanto que não vamos falar apenas do Widgetbook como um pacote. O termo "Widgetbook" que estamos idealizando é mais do que um pacote, é um padrão de projeto que, juntamente com outros padrões a serem documentados aqui, formará um padrão para um novo Design System, com esse padrão você pode:
- Desenvolver e testar componentes de forma isolada.
- Documentar os componentes de interface do usuário.
- Acelerar o desenvolvimento.
- Garantir consistência e qualidade.
⏳ Padrão de projeto - Design Atômico
O Atomic Design é uma metodologia de design que descreve a organização de componentes de interface do usuário em níveis hierárquicos crescentes de complexidade.
A estrutura do Atomic Design é baseada em cinco níveis hierárquicos de complexidade, que incluem:
-
Átomos: são os blocos de construção mais básicos de um sistema de design. Eles podem ser simples, como uma cor ou um ícone, ou mais complexos, como um campo de formulário. Os átomos geralmente não têm nenhuma funcionalidade interativa e são usados para construir componentes maiores.
-
Moléculas: são componentes compostos de dois ou mais átomos que trabalham juntos para formar um elemento de interface do usuário mais complexo. Por exemplo, um botão é uma molécula composta por um ícone e uma etiqueta de texto.
-
Organismos: são componentes mais complexos que consistem em várias moléculas e átomos trabalhando juntos para criar uma seção completa da interface do usuário. Um exemplo de organismo pode ser um formulário que contém vários campos de entrada e botões de envio.
-
Templates: são esqueletos de página que definem a estrutura de um layout. Eles são compostos de organismos e moléculas e podem ser usados para criar várias páginas do mesmo tipo.
-
Páginas: são as visualizações finais que os usuários veem no navegador. As páginas são construídas a partir de uma combinação de organismos, moléculas e átomos, e são responsáveis por fornecer a funcionalidade interativa do aplicativo.
-
Estruturas(extra): são componentes de layout/animação, normalmente utilizados para desempenhar uma função de organização de componentes mais complexos.
Exemplos de Widgets em seus níveis hierárquicos:
- Átomos: GAIcon, GAButton e GAText
- Moléculas: GMBackButton, GMCardEmpty, GMFilterButton, GMMenuButton, GMDateTitulo e GMMarkTitulo
- Organismos: GOCardTitulo, GOBottomBarTitulo e GOAppBarTitulo
- Templates: GTTituloList
- Páginas: TitulosPage
- Estruturas(extra): GEActiveRefreshChild, GEAppBar
Ao organizar os componentes de interface do usuário em níveis hierárquicos crescentes de complexidade, o Atomic Design permite que as equipes de design e desenvolvimento criem um sistema de design escalável e consistente. Isso ajuda a garantir que todos os componentes sejam criados usando as mesmas diretrizes e padrões de design, resultando em uma experiência do usuário coesa e harmoniosa em todo o aplicativo.
⏳ Padrão de projeto - UI Variables
A padronização das UI Variables (variáveis, constantes e funções padrões para controlar a UI) na aplicação é de suma importância. Dessa forma, foi arquitetada uma estrutura de divisão relacionada abaixo:
Extensions: Toda e qualquer função que não tiver relação com regra de negócio e for muito utilizada na aplicação deve estar nos arquivos de extensão.
Fonts: Armazena todas as fonts do sistema.
Formatters: Toda e qualquer formatação de inputs (ex: CEP, Aniversário, Telefone).
Icons: Os ícones personalizados (pelo time de design) devem estar em formato de fonte e referenciados em GFonts.
Imagens: Por padrão, os ícones devem sempre estar em GIcons. Entretanto, para estruturas complicadas com várias cores, devemos utilizar imagens no formato .png.
Responsive: Trata-se de um InheritedWidget, no qual ele tem breakpoints para informar a tela que tem que atualizar quando passar por esse breakpoint.
⏳ Padrão de projeto - Theme
A padronização da parte de theme possui alguns pontos importantes que devem ser notados. Em primeiro lugar, ela não está limitada apenas a temas específicos, mas também inclui toda a estrutura de cores, sombras, tamanhos e estilos de texto. Além disso, abordaremos a geração de ThemeExtension e sua integração com o Widgetbook em outro tópico. Portanto, abaixo estão os temas de forma geral antes de abordá-los no Widgetbook:
-
GColors: Na aplicação, temos cores padrão. Essas cores, por sua vez, são todas geradas pelo Figma. Dessa forma, é importante que o Figma tenha todas as cores padronizadas e com os nomes corretos. Ou seja, não é possível adicionar cores que não estejam no Figma. Por quê? Simplesmente porque se trata de um padrão. Dessa forma, apenas os designers ditam as regras de cores. Caso haja alguma cor que não esteja especificada nas variáveis do Figma ou por algum motivo ainda não tenha sido adicionada, converse com a equipe de design e o líder técnico do seu time. Imortante lembrar que ao criar um componente, é necessário fazer referência ao GColor em vez do Color. Isso garante que o desenvolvedor utilize apenas as cores predefinidas.
-
GShadow: Assim como os GColors, os GShadows não podem ser modificados, a não ser que haja uma alteração nas sombras do figma, conforme explicado anteriormente. E, assim como nos casos de GColors, é sempre necessário referenciar o GShadow em vez do BoxShadow.
-
GSizes: O "GSize" é um super componente que realiza cálculos e gera automaticamente as formatações de tamanhos para padding, borderRadius, SizedBoxs e double. Sendo sincero, a construção dele é um pouco complexa e não será abordada aqui. Apenas entenda que os tamanhos são divididos em scaleFactor (xxs = 1, xs = 2, sm = 3, md = 4, lg = 5, xl = 6, xxl = 7) e pode ser necessário criar outros com valores intermediários como 0.5, por exemplo. Peço apenas que seja uma decisão tomada em conjunto com os responsáveis pelo projeto e o time de design.
O funcionamento é simples: esse scaleFactor é multiplicado com o spacerValue (valor definido no projeto como 8) e, devido à construção dele, já tem predefinidas as funcionalidades de (left, top, right, bottom, topleft, topright, bottomright, bottomleft, x, y, all, size). Isso, atrelado a um tamanho, por exemplo "xxs", te dá acesso às propriedades de padding, size ou borderRadius.
Gsize.all.xxs.padding ou GSize.x.xs.borderRadius ou GSize.size.sm.w (gera um sizedbox com sm de width) ou Gize.size.sm.value
-
GTextStyle: Idêntico ao GShadow e GColors, o GTextStyle vem do Figma e não pode ser adicionado a não ser que esteja presente no Figma. Além disso, deve ser sempre utilizado para referenciar o TextStyle em componentes criados, a fim de forçar o desenvolvedor a utilizar apenas os GTextStyle já definidos.
-
Theme: Por padrão, o Theme é gerado com base nas cores do GColors. Na verdade, é impossível que você tenha que mexer nesse arquivo se ele já foi criado uma vez. A única coisa que poderia ser modificada é o ThemeExtensions desses themes. No entanto, como isso é feito de forma automática utilizando o ThemeTailor, não será preciso modificar nada nesse arquivo.
Entenda que aqui você deve definir os ThemeData padrão da sua aplicação.
- WidgetbookBaseTheme: Trata-se do principal e pai de todos os ThemeExtensions de nosso projeto. Nele, vamos especificar os ThemeComponents mais genéricos, como o TitulosTheme ou SharedTheme. Qual o motivo dele? Simples: cores não serão mais adicionadas no HardCode ou referenciando o GColors. Elas devem ser adicionadas utilizando as cores que estiverem nos ThemeExtensions, de forma que você não precise se preocupar com trocas de themes. No próximo tópico, vou falar sobre o WidgetbookBaseTheme e o ThemeTailor de forma mais aprofundada.
⏳ Padrão de projeto - Theme Tailor
Em Flutter, temos um padrão de Themes muito bem estruturado com o Material, porém, ele é fechado para a solução do Material Design. Para sairmos desse problema, utilizamos o Theme Extension, porém, para utilizá-lo fica muito verboso e complicado. Dessa forma, no projeto, utilizamos o Theme Tailor. Com ele, é possível modularizar os themes extensions e gerar o código de forma automatizada apenas passando as variáveis para ele.
IMPORTANTE: sempre referenciar qualquer variável do Theme Extension utilizando GColor, GShadow, GTextStyle.
Abaixo irei mostrando exemplos práticos:
Imagine a seguinte situação: você acabou de criar um CardTitulo e precisa definir a cor de fundo e o sombreamento do card, ambos devem se adaptar ao tema atual. Para isso, é necessário criar um @TailorComponent para definir quais serão os temas daquele card, assim como o ThemeData faz com os temas materiais. Exemplo:
Dessa forma você terá um theme para cada componente com complexidade nível maior ou igual a de uma molécula, uma vez que átomos exportam todas as modificações.
Em relação à hierarquia o CardTituloTheme está dentro do contexto "Titulos". Por isso devemos criar um TailorComponent geral para o contexto de títulos e depois importar o CardTituloTheme no como um @themeExtension conforme abaixo:
Novamente voltamos à seguinte pergunta: em qual contexto o TitulosTheme está inserido? No contexto da aplicação completa! Portanto, devemos referenciá-lo em um Theme que seja para a aplicação completa, que no caso é o WidgetbookBaseTheme. Nele, estamos na camada máxima do ThemeTailor, abaixo somente do ThemeData.
Agora, para referenciar todo esse ThemeExtension, devemos pegar o WidgetbookBaseTheme e adicioná-lo como uma extensão do ThemeData de cada tipo light e dark. Abaixo segue um exemplo para o tema light:
Com isso, temos acesso às variáveis desses Themes em qualquer parte da nossa aplicação a partir do BuildContext, como no exemplo abaixo:
⏳ Padrão de projeto - Geração de componentes (Design Atomic)
Aqui vão ficar registradas as melhores formas de criar componentes, bem como aquelas que devemos evitar a todo custo. Em primeiro lugar, é importante lembrar que estamos trabalhando com design atomic.
Dica: Toda vez que for mencionado contexto, pense na feature correspondente. Por exemplo: o card de títulos está no contexto de títulos.
- Atomos
Os átomos estarão, praticamente, vinculados apenas ao contexto Shared, uma vez que eles representam os blocos de construção mais elementares do sistema de design, e, portanto, são componentes genéricos. Imagine que os átomos são as bases fundamentais do nosso aplicativo, nos quais definimos que, por exemplo, um GText só pode receber cores do tipo GColors, entre outras coisas.
Em relação à separação das regras dos átomos em arquivos, destacam-se os seguintes:
A. exemplo_usecase.dart o arquivo de usecase é criado para passar as variantes daquele componente. Esse arquivo é necessário para criar o componente no Widgetbook. Ele é separado em um arquivo diferente para que tenha a responsabilidade exclusiva de representar as formas de visualização daquele componente.
B. exemplo.dart Este é o arquivo que representa o próprio componente. Como se trata de um átomo, ele sempre externará seus parâmetros. Além disso, é importante destacar que um átomo é um bloco simples, como um texto genérico.
- Moléculas
As moléculas são basicamente representações de junções de átomos que estão próximos e fazem sentido juntar. Por exemplo, um título e subtítulo, um conjunto de valores ou um estilo de botão específico. Elas representam uma pequena parte de uma estrutura maior de um componente. Por exemplo, o botão "Enviar" é uma molécula de um organismo de Cadastro ou de um Card. O mesmo se aplica ao Título e Subtítulo. É importante destacar que as moléculas estão em um nível intermediário, ou seja, não são nem tão grandes nem tão pequenas. Portanto, não devem ser usadas diretamente na página, mas sim em um organismo ou template.
Além disso, abaixo vou listar as mesmas coisas que foram ditas sobre os átomos, mas agora vou adicionar o arquivo "exemple_theme.dart".
A. exemplo_usecase.dart o arquivo de usecase é criado para passar as variantes daquele componente. Esse arquivo é necessário para criar o componente no WidgetBook e é separado em um arquivo diferente para que tenha apenas a responsabilidade de representar as formas de visualização daquele componente.
B. exemplo.dart esse é o arquivo do componente em si, Como ele é uma molécula, sempre irá externalizar os parâmetros de dados, mas ele já deve ser estilizado com o theme ou widgetbookBaseTheme.
C. exemplo_theme.dart Este é o arquivo do tema daquela molécula em si. Como ele é uma molécula, ele não externaliza as estilizações. Dessa forma, ele deve ser construído já pensando no seu estilo. O arquivo *_theme.dart é utilizado para guardar essas informações quando se trata de cores ou algo que muda dependendo do ThemeMode.
- Organismos
Bem, os organismos são representações de junções de átomos e moléculas, sendo componentes grandes o suficiente para serem referenciados diretamente na tela, como uma AppBar personalizada ou componentes que estão ligados a algum template. Por exemplo, um CardTitulos que faz parte do Template de TitulosList. Eles são componentes que devem ter um modelo para seus dados internos e um adaptador para traduzir essa model com a model da regra de negócio. Além disso, podem ter um enum de variantes, dependendo do seu uso. Por exemplo, posso ter um BottomBar com dados que serão carregados, e assim terei uma variante de carregamento e outra com o dado completo. Portanto, teremos os seguintes arquivos:
A. exemplo_usecase.dart: O arquivo de usecase é criado para passar as variantes daquele componente. Esse arquivo é necessário para criar o componente no widgetbook. Ele é separado em um arquivo diferente para ter apenas a responsabilidade de representar as formas de visualização daquele componente.
B. exemplo.dart: Este é o arquivo do componente em si, e todos os dados externos devem ser inseridos via uma única Model ou Variant. As funções ainda devem ser externalizadas normalmente.
C. exemplo_theme.dart: Este é o arquivo do theme daquele organismo. Como ele é um organismo, ele não externaliza as estilizações. O motivo de ele ter esses estilos é que às vezes eu referenciei um átomo ou estou usando algum componente que preciso passar uma estilização de background ou coisa do tipo.
D. exemplo_model.dart: Aqui teremos que colocar todos os dados que devem estar entrando naquele componente para preencher os dados lá dentro.
E. exemplo_adapter.dart: Este arquivo é necessário quando temos uma ou mais models que simbolizam minha regra de negócio e queremos transformar essas informações na model anterior para a visualização do meu componente.
F. exemplo_variant.dart: É um arquivo de enum com as variações daquele organismo.
- Templates
Os templates são representações da nossa tela, como, por exemplo, uma lista de cards ou um formulário completo. Ele é a junção de organismos, que são componentes completos que normalmente têm várias variantes e interações. Ele deve externalizar as models dos organismos que está utilizando.
A. exemplo_usecase.dart: O arquivo de usecase é criado para passar as variantes daquele componente. Esse arquivo é necessário para criar o componente no widgetbook. Ele é separado em um arquivo diferente para ter apenas a responsabilidade de representar as formas de visualização daquele componente.
B. exemplo.dart: Este é o arquivo do componente em si, e todos os dados externos devem ser inseridos via Models ou Variant. As funções ainda devem ser externalizadas normalmente.
C. exemplo_variant.dart: É um arquivo de enum com as variações daquele template.
- Estruturas
As estruturas são representações de layout/organização da nossa tela, como uma Column ou coisa do tipo. Na verdade, ela só é usada para organizar aqueles componentes. Normalmente, ela não recebe dados em si por parâmetro e sim outros widgets.
A. exemplo_usecase.dart: O arquivo de usecase é criado para passar as variantes daquele componente. Esse arquivo é necessário para criar o componente no widgetbook. Ele é separado em um arquivo diferente para ter apenas a responsabilidade de representar as formas de visualização daquele componente.
B. exemplo.dart: Este é o arquivo do componente de layout em si.