Boas práticas com Modifiers no Jetpack Compose
Tags: kotlin, android, jetpackcompose
Published: 2023-06-26 11:00:00

Ao escrever composables é natural o uso de [modificadores](https://developer.android.com/jetpack/compose/modifiers) - Modifiers para personalizar ou adicionar comportamentos para os nossos componentes visuais. 

Dado que o uso de Modifiers é intuitivo, é muito comum escrevermos códigos sem considerar algumas boas práticas que fazem muita diferença em Apps, seja para simplificar o código ou até mesmo em questões de performance... 

E considerando esses detalhes, neste artigo eu vou te mostrar as técnicas que você pode utilizar para otimizar o seu código e performance com o Jetpack Compose a partir de Modifiers, bora lá? 

## Modifier padrão

A primeira boa prática está mais para uma convenção! O famoso Modifier padrão:

```kotlin
Column(Modifier.fillMaxSize()) {
    Button(
        onClick = { /*TODO*/ },
        Modifier.fillMaxWidth()
    ) {
        Text(
            text = "button text test",
            Modifier
                .padding(8.dp)
                .background(
                    Color.Gray.copy(alpha = 0.5f),
                    RoundedCornerShape(20.dp)
                )
        )
    }
}
```

Não sei se você já notou, mas todo composable disponibilizado pela lib do Jetpack Compose ou outras libs como os do Material Design, **possuem um Modifier padrão como primeiro parâmetro opcional**.

O motivo dessa prática, é permitir a personalização de qualquer composable a partir de Modifiers, seja na modificação visual, eventos etc. Em outras palavras, quando criamos um composable nosso, é extremamente recomendado que exista um Modifier padrão para flexbilizar a personalização! 

## Adicionando um Modifier padrão

A adição do Modifier padrão exige apenas que seja o primeiro parâmetro opcional com o valor `Modifier` e que modifique o primeiro Composable:

```kotlin
@Composable
fun MyButton(
    text: String,
    modifier: Modifier = Modifier
) {
    Box(
        modifier
            .padding(8.dp)
    ) {
        Text(
            text = text,
            Modifier
                .align(Alignment.Center)
        )
    }
}
```

Com essa abordagem, podemos usar o mesmo botão com aparências diferentes:

```kotlin
Column {
    MyButton("meu primeiro botão")
    MyButton(
        "meu segundo botão",
        Modifier
            .fillMaxWidth()
            .padding(8.dp)
            .background(Color.Gray, RoundedCornerShape(20.dp))
    )
}
```

![App em execução apresentando 2 composables de MyButton em coluna. O primeiro composable apresenta apenas o texto 'meu primeiro botão' com fundo branco, o segundo apresenta o texto 'meu segundo botão' preenchendo a largura disponível e com espaçamento, também, o fundo é cinza com bordas arredondadas](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0zb0sakb4ehtayvdvb6j.png)

Essa é de longe uma das características que mais gosto do Jetpack Compose! 😎

## Reutilizando o Modifier

A próxima boa prática está na reutilização de Modifiers, isso mesmo! Temos a capacidade de extrair Modifiers e reutilizá-los:

```kotlin
Column {
    val myButtonModifier = Modifier
        .fillMaxWidth()
        .padding(8.dp)
        .background(Color.Gray, RoundedCornerShape(20.dp))
    MyButton("meu primeiro botão")
    MyButton(
        "meu segundo botão",
        myButtonModifier
    )
    Box(
        modifier = myButtonModifier
            .height(200.dp)
    )
}
```

![App em execução apresentando 2 composables MyButton e 1 Box em coluna. O primeiro composable apresenta apenas o texto 'meu primeiro botão' com fundo branco, o segundo apresenta o texto 'meu segundo botão' preenchendo a largura disponível e com espaçamento, também, o fundo é cinza com bordas arredondadas. O Terceiro apresenta a mesma aparência do segundo composable, com a diferença de uma altura maior](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gn76gfgclb6eg9umqcc3.png)

Nesta amostra, reutilizamamos a mesma aparência do segundo botão no terceiro `Box`! Inclusive, note que ainda mantemos a flexibilidade para ajustar o `Box` conforme a sua necessidade, como por exemplo, aplicar uma altura diferente.

## Vantagens de reutilização de Modifiers

Além de reduzir a quantidade de código, a reutilização de `Modifier` ajuda na otimização da recomposição, pois os Modifiers também são executados novamente nos composables afetados.

Os casos de maiores impactos são em implementações que tendem a realizar a recomposição constantemente, como a utilização de animações ou implementações lazies (`LazyColumn`, `LazyRow` etc).

## Extração de Modifier fora do escopo de composables

Para otimizar essas situações, devemos considerar a extração de modifiers fora do escopo dos composables. Em um primeiro momento pode parecer abstrato, então vamos considerar um exemplo:

```kotlin
val myButtonModifier = Modifier
    .padding(8.dp)
    .fillMaxWidth()
    .background(Color.Gray, RoundedCornerShape(20.dp))

@Composable
fun MyLazyColumn() {
    LazyColumn {
        items(1000) {
            MyButton(
                "meu segundo botão",
                myButtonModifier
            )
        }
    }
}
```

Sim! Apenas essa pequena extração, pode otimizar e muito implementações com recomposições frequentes! Além disso, é possível reutilizar o Modifier em outros composables, pois ele é globalmente acessível.


## Extração de Modifier dentro do escopo de composables

Embora a extração de Modifier fora de escopo tenha as suas vantagens, existirão casos em que não é possível, como por exemplo, os Modifiers de escopo de composables específicos.
 

Os composables mais notáveis para essa situação, são os de layout como `Box`, `Column` ou `Row`. Ambos oferecem escopos específicos que dão acesso a Modifiers exclusivos, como por exemplo o `align()` que permite alinhar filhos:

```kotlin
@Composable
fun MyColumn() {
    Column {
        val myButtonModifier = Modifier
            .padding(8.dp)
            .width(200.dp)
            .background(Color.Gray, RoundedCornerShape(20.dp))
            .align(CenterHorizontally)
        MyButton("meu primeiro botão")
        MyButton(
            "meu segundo botão",
            myButtonModifier
        )
        Box(
            modifier = myButtonModifier
                .height(200.dp)
        )
    }
}
```

![App em execução apresentando 2 composables MyButton e 1 Box em coluna. O primeiro composable apresenta apenas o texto 'meu primeiro botão' com fundo branco, o segundo apresenta o texto 'meu segundo botão' centralizado, o fundo é cinza com bordas arredondadas. O Terceiro apresenta a mesma aparência do segundo composable, com a diferença de uma altura maior](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0o1de34ybf7viavvkrv8.png)

Uma alternativa seria extrair um novo composable apenas adicionando os Modifiers daquele escopo:

```kotlin
val myButtonModifier = Modifier
    .padding(8.dp)
    .width(200.dp)
    .background(Color.Gray, RoundedCornerShape(20.dp))

@Composable
fun MyColumn() {
    Column {
        val myButtonColumnModifier =
            myButtonModifier
                .align(CenterHorizontally)
        MyButton("meu primeiro botão")
        MyButton(
            "meu segundo botão",
            myButtonColumnModifier
        )
        Box(
            modifier = myButtonColumnModifier
                .height(200.dp)
        )
    }
}
```

O que você achou dessas boas práticas com o Modifiers? Tem alguma técnica diferente que utiliza e acha interessante compartilhe, aproveite e deixe aqui nos comentários. 😉