Tradução de Design System: como fazer isso com Jetpack Compose

Pablo Baldez
Android Dev BR
Published in
6 min readApr 23, 2021

Esse é meu segundo artigo sobre o uso de Jetpack Compose para implementação de um design system. Recomendo que você leia também o primeiro, onde explico como criamos componentes customizados na Warren.

Agora vou explicar alguns pontos que consideramos importantes para implementar o Nebraska, design system da Warren, que leva esse nome por ser o estado de nascimento de Warren Buffet, investidor e filantropo americano que compartilha o nome com nosso app. Outra hora posso explicar um pouco mais sobre esses detalhes.

Agora o objetivo será explicar como integramos o Nebraska na plataforma android, que já funciona com seu design system nativo, o Material Design. Pra ser mais específico, vou detalhar aqui como “traduzimos” nosso esquema de cores para esquema de cores que o Material usa.

Porque tradução?

Uso esse termo porque, de forma geral, cada design system contém uma linguagem própria, com termos específicos que são usados em um contexto que fazem sentido pra ele. Por exemplo, olhando a documentação da função MaterialTheme, usada para configuração visual de toda a aplicação, percebemos três pilares base: cores, tipografia e shapes

A MaterialTheme defines the styling principles from the Material design specification.Material components such as Button and Checkbox use values provided here when retrievingdefault values. It defines: 
- colors as specified in the
Material Color theme creation spec,
- typography defined in the
Material Type Scale spec
- shapes defined in the
Shape scheme.

Acontece que isso não é uma regra geral para todos os design systems, como por exemplo o Nebraska. Ele deve atender os clients web e iOS também, por isso seguir as especificações do Material cegamente não nos serviria. Nossos pilares são outros, pois diferente do Material Design, nos baseamos em cores, tipografia e escalas de tamanho.

Parece pouca diferença, certo? Mas veja como a especificação de cores muda de um para outro, começando pela especificação de cores do Material

Esquema de cores do Material Design

Agora perceba a diferença da nossa especificação de cores nos modos light e dark (deixei sem zoom só pra mostrar que a diferença é grande hehe)

Esquema de cores do Nebraska para tema light
Esquema de cores do Nebraska para tema dark

Aqui com zoom em um grupo específico pra ficar mais fácil de entender. Esse é o grupo de cores que usamos nos textos.

Grupo de cores que usamos em labels

Como se pode ver são mundos completamente diferentes, afinal são propostos a resolver problemas diferentes. Enquanto o Material Design tem um grupo enxuto de cores, nós organizamos as nossas em dois grandes grupos: Core e Context.

  • No Core, temos as cores gerais do projeto que podem ser utilizadas independente do contexto que estamos inseridos, sendo tokens universais. Nesse grupo, estamos falando das cores de fontes, backgrounds, forms e ações secundárias.
  • Em Context, temos toda a tematização do Design System, sendo as cores específicas de um local da aplicação baseada em seu contexto. Aqui, definimos na página qual o contexto e com isso, retornamos suas cores primárias, de hover, links, etc.

Veja um exemplo de duas telas do app que apresentam features diferentes. Na primeira, utilizamos o context Trade (azul), já na segunda usamos o context Cash (verde). Ambos compartilham as mesmas cores secundárias, cores básicas de textos, etc. Entretanto, as cores principais e de mais destaque de cada tela são determinadas por seus respectivos contexts

Telas usando os grupos Trade e Cash, respectivamente

Não vou me aprofundar nos casos de uso de cada grupo pois isso consumiria seu tempo, além de não ser esse o objetivo do post. O ponto fundamental é entender que design systems diferentes utilizam linguagens diferentes.

Geralmente linguagens podem ser traduzidas ou adaptadas para outra (seja linguagem humana ou de programação). Com design systems isso não é diferente. Entendemos que nosso papel enquanto desenvolvedores android é realizar essa tradução. Mas como?

Modelando a engenhoca

Nosso ponto base de partida foi determinar que iríamos, por trás dos panos, usar o Material Design. Decidimos isso porque realizando a configuração das cores do MaterialTheme, acabaríamos configurando também os componentes nativos como Button, Checkbox, Text, etc…

Portanto, a “tradução” aqui consistiria em definir quais cores do nosso esquema seriam usadas como cores do MaterialTheme. De modo geral, pretendemos fazer isso

A primeira coisa a se fazer seria modelar as classes do nosso esquema de cores de forma a representar ele em código kotlin.

Nossa organização se baseia em grupos de cores, onde cada grupo consiste em uma coleção de cores para os temas dark e light que o usuário pode escolher nas configurações do device (nos prints só mostrei as cores light).

Portanto definimos uma abstração bem básica do que é um grupo

Depois disso começamos a especificar uma classe para cada grupo. Veja na imagem e trechos de código abaixo um exemplo da definição de cores de um grupo, e como implementamos isso em um data class.

Cores do modo light do grupo Status
Cores do modo dark do grupo Status

E aqui sua representação em kotlin

Perceba nesse exemplo como tivemos a preocupação de manter a fidelidade da classe com o grupo, fazendo com que cada propriedade tenha o nome de uma cor do grupo.

O companion object da classe é responsável por implementar a interface Group e determinar quais serão as instâncias light e dark.

Também nos preocupamos em fazer com que essas classes não tenham referências de “coisas” Android, como a classe Color ou a annotation @Composable. Apesar de ser um detalhe, isso desacopla qualquer dependência que a modelagem do Nebraska possa ter do Material Design.

Bom, assim já da pra entender como ficaram as classes dos outros grupos, certo? Todos seguiram exatamente o mesmo raciocínio, então vou poupar você e não vou colocar o código do resto (e poupar meu tempo tb hehe).

Como existem vários grupos Context e cada tela deve aplicar apenas um deles, resolvemos implementar vários objects que representem esses grupos.

Por fim, criamos a classe final, ColorSchema, que consiste em reunir todos os groups em um só lugar. Nela usamos o contexto Neutral como o default, mas logo mais a frente mostrarei como substituir isso.

O Gran Finale

Agora que já temos o Nebraska devidamente modelado, finalmente podemos mostrar como “traduzimos” tudo isso para o mundo do Material Design.

Vamos usar a classe ColorSchema, que contém todos os nossos grupos de cores, e faremos o mapeamento para as cores do Material Design.

Dessa forma, a tradução do Nebraska para o Material foi implementada. Alguns pontos dessa implementação que valem ser ressaltados:

  • Utilizamos a função isSystemInDarkTheme, disponível pela lib do Compose e usada para identificar se o modo dark está habilitado nas configurações do device;
  • Permitimos que o grupo de cores de contexto seja definido por parâmetro por quem usa essa função;
  • Criamos uma propriedade via extension chamada materialColors que faz o mapeamento das nossas cores para as cores do Material Design.

Assim finalizamos a parte mais importante, que consiste em converter nosso esquema de cores para as cores que o Material Design usa. Mas ainda não acabou!

Usamos a API de Locals para prover o ColorSchema (aquele que tem todos os grupos de cores). Assim fica fácil acessar qualquer cor do Nebraska que não seja contemplada pelo Material Design.

Agora, podemos utilizar nosso design system em qualquer tela, de forma fácil.

E voilà!

Com essas modelagens e definições de temas, traduzimos o Nebraska para o Material de forma que os componentes nativos fiquem estilizados, além de oferecer também o acesso as cores do nosso design system de forma fácil para os desenvolvedores.

Talvez o conteúdo desse artigo possa ter ficado extenso e um pouco complexo, mas o resultado final no projeto ficou simples e de fácil manutenção.

Deixe nos comentários se ficou com alguma dúvida, se gostou da solução ou se teria uma forma mais simples de resolver essa bronca!

Abraço e até a próxima!

--

--

Pablo Baldez
Android Dev BR

Desenvolvedor android a muito tempo. Interessado por aprender sobre tudo que posso desde sempre.