Boas práticas com Modifiers no Jetpack Compose

alexfelipe

Alex Felipe

Posted on June 26, 2023

Boas práticas com Modifiers no Jetpack Compose

Ao escrever composables é natural o uso de modificadores - 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:

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)
                )
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

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

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

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

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

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:

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)
    )
}
Enter fullscreen mode Exit fullscreen mode

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

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:

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
            )
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

@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)
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

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

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

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)
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

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. 😉

💖 💪 🙅 🚩
alexfelipe
Alex Felipe

Posted on June 26, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related