Don't Panic: Jetpack Compose E Tudo Mais

Lucas Marciano
7 min readJul 18, 2022

--

O Jetpack Compose é nova biblioteca do android, que está no guarda-chuva do nosso amado Jetpack, é isso mesmo jovens e jovas, preparem os cintos que foguete não da ré, mas explode.

Uma imagem animada mostrando uma serie de equipamentos eletronicos ligados ao livro O Guia do Mochileiro das Galáxias
Não entre em panico, pegue sua toalha e vem comigo

Um pouquinho de historia

Quem não odeia o XML, pois é, eu sei, também não curto muito o XML, apesar dele estar no mesmo pacote de "coisas que me ajudaram a crescer e ser um desenvolvedor melhor mais ainda sim eu odeio", todo desenvolvedor tem um pacote assim. Mas vamos lá. O Android Compose se vale de um paradigma que chamamos de programação declarativa, onde, utilizamos de pequenos blocos chamadas de componentes para montar a nossa tela, e essa tela é formada a partir de uma informação, e essa informação vai alterar o componente e seu estado. Apesar de ser um resumo um tanto simplório, esse conceito resume bem o que o Android Compose.

Vou deixar nas referencias uma pagina explicando melhor a diferença entre a programação declarativa e a imperativa que é a que a gente usa no desenvolvimento Android, em geral.

Vamos de Estados

Ao receber o dado o estado mudou

No ponto anteriror comentamos que o dado altera o estado do componente, mas o que é um estado? O estado do componente é como se fosse uma foto do momento que o componente recebeu o dado e se montou, nesse momento temos um estado.

Quem trabalha com desenvolvimento a mais tempos pode ligar esse conceito com o conceito de UIState

Os estados dos componentes precisam ser alterados sempre que os dados mudam, fazendo com que a interface seja alterada de alguma forma, desde mudanças como amimações até apenas um texto que mudou. Essa alteração ocorre dentro de um outro conceito que devemos tomar muito cuidado chamado de recomposição, citando o Ben Trengrove:

"Recomposition is the process of calling your composable functions again when inputs change. This happens when the function’s inputs change. When Compose recomposes based on new inputs, it only calls the functions or lambdas that might have changed, and skips the rest. By skipping all functions or lambdas that don’t have changed parameters, Compose can recompose efficiently."

O conceito de recomposição e estado estão intimamente ligados pelo Loop de atualização da UI (UI Update Loop). Veja a imagem a seguir para entender melhor, considere o bloco Event como sendo uma entrada ou mudança nos dados.

UI Update Loop

O que a imagem mostra é como a recomposição atua. Segue o pensamento:

  • Ocorre um evendo (Evente), por exemplo, mudamos os dados ou enviamos novos dados para o componente;
  • O estado (State) inicial é atualizado;
  • A interface é alterado de acordo com o novo estado.

Esse é a recomposição que ocorre dentro de cada componente. Mas por que precisamos ter muito cuidado com ela? Bom, basicamente a recomposição reconstroi todo o componente, isso, caso não tenhamos cuidado por fazer sua aplicação consumir muita memoria, vamos dar uma exemplo. Imagine que você tem o seguinte componente:

O nosso componente MyComponent ele recebe um objeto data, imagine que o primeiro estado é o objeto datainfo (linha 7), agora imagine que temos um viewmodel onde ele seta um novo valor no datainfo, quando esse valor mudar o que vai acontecer é a recomposição, ou seja, todo o componente vai ser "destruído" e recriado, isso é a recomposição. Em casos de componente simples, isso não tem tanto problema, mas agora imagine n componentes sendo recriados ou um componente um pouco mais complexo (que deve ser evitado). Isso vai ter um custo para sua aplicação, existe meios para evitar esse comportamento, vamos falar disso em outro momento.

Conservando os estados

Remember this

Pensou que eu ia deixar esse ponto de conservar o estado para outro momento? Sim eu ia, mas mudei de ideia. Vamos lá!

Só relembrando, sabemos que o Compose é declarativo, portanto quando precisamos mudar um componente de alguma forma é necessário alterar os dados inseridos e consequentemente o estado, chamando a recomposição.

Perfeito, temos um ponto importante, o dado deve ser alterado ou seja, se temos um componente que recebe uma string, precisamos muda-la a fim de mudar o componente, um componente precisa ser informado explicitamente sobre seu novo estado.

Mas existe alguma maneira de evitar a recomposição? Sim, não entre em pânico.

No Compose existe uma estrutura chamada remember, ela é responsável por armazenar em memoria o objeto que o componente precisa para ser montado. O remember armazena o valor inicial, assim que o componente é criado e utilizado em sua recomposição, além disso ele pode armazenar valores mutáveis e imutáveis. No caso de objetos mutáveis usamos o mutableStateOf, mas existem diversos tipos de mutables que podem ser integrados ao remember. Quando utilizamos o remember com o mutableStateOf, temos algo assim:

var name by remember { mutableStateOf("") }

Sendo que há três maneiras de declarar um objeto MutableState em um elemento que pode ser composto:

val mutableState = remember { mutableStateOf(default) } 
var value by remember { mutableStateOf(default) }
val (value, setValue) = remember { mutableStateOf(default) }

Quando usa a sintasy by é preciso usar os imports:

import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue

Com essa implementação criamos um observador para a variável name, e como estamos trantando de states, o name agora tem um value, e caso esse value seja alterado, a recomposição vai ser ativada, assim, o componente não precisa receber nenhuma ação externa para mudar.

O principal beneficio dessa abordagem é que o seu componente é livre para se alterar, evitando que haja recomposição da tela toda, e apenas daquele componente pequeno.

Ainda sim existem várias formas de se utilizar o remember, um outro exemplo/uso importante é de elevar o estado para um componente pai e deixar os componentes micros sem esse controle de estado. Beneficio disso é que seu componente vai ser mais simples, e fácil de testar, reutilizar, uma unica fonte de verdade e, muitas vezes, com baixa complexidade. Vamos ao exemplo.

Veja pre codigo, que na HelloScreen temos o remember, o controle de estado, e temos o componente HelloContent que apenas recebe um nome e uma função que rebece uma string. O que aconte é que quando o texto é alterado no componente OutlinedTextField a função onValueChange é disparada, ela retorna uma string que preenche o onNameChange e consequentemente chama o compoente pai, recriando ele, e seus filhos também. A imagem a seguir descreve bem como funciona o fluxo:

A Screen envia o estado para o content, o Content recebe esse State e via Event ele muda os dados, o Screen por sua vez, percebendo que houve mudança no State dispara a recomposição alterando a tela para o usuário. Recomeçando o fluxo. O padrão em que o estado desce e os eventos sobem é chamado de fluxo de dados unidirecional.

Efeitos colaterais

Efeito colateral de confiar no Ford

Está pronto? Pega sua toalha que vamos ver um conceito mais denso dentro do Compose, mas infinitamente poderoso e que vai dar a sua interface mais rapidex e fluidez a sua aplicação.

Inicialmente queria deixar um ponto de que as função de composição não precisam ter efeitos colaterais, mas as vezes vamos precisar manipular o estado do componente em um escopo controlado. Por exemplo, alguma chamada da API gera um resultado e isso precisa ter uma resposta visual no seu componente, para isso usamos a lib do Effects para ter esse nível de controle.

Segue metodos que estão na lib Effects:

  • LaunchedEffect: executar funções de suspensão no escopo de uma função que pode ser composta;
  • rememberCoroutineScope: extrair um escopo compatível com a composição para iniciar uma corrotina fora de uma função que pode ser composta;
  • rememberUpdatedState: referenciar um valor em um efeito que não pode ser reiniciado se o valor mudar;
  • DisposableEffect: efeitos que exigem limpeza;
  • SideEffect: publicar estado do Compose em código que não é do Compose;
  • productState: converter um estado que não é do Compose em um estado do Compose;
  • derivedStateOf: converter um ou vários objetos de estado em outro estado;
  • snapshotFlow: converter o estado do Compose em fluxos.

Vejam que são muitos itens para abordarmos em um unico material, então vou deixar uma explicação mais detalhada para outro post. Mas quero deixar no seu radar as possiveis formas de trabalhar com efeitos dentro do seu compoente.

Obrigado por todos os likes!! :D

Referências

--

--