DEV Community

Marcos Anjos
Marcos Anjos

Posted on

Jetpack Compose fez sentido pra mim quando eu parei de comparar tudo com XML

Image JetpackCompose logo

Quando comecei a mexer com Jetpack Compose, tentei entender tudo com a cabeça do Android antigo. Eu ainda pensava em XML, Activity, Fragment e atualização manual de interface. Por isso, no começo, parecia que estava faltando alguma coisa: cadê o XML, os ids e o findViewById? O que me destravou foi perceber que Compose não era só um jeito novo de fazer a mesma coisa, mas uma forma declarativa de construir UI. E, vindo do React, foi aí que tudo começou a fazer sentido.

A primeira virada de chave

A principal virada de chave para mim foi perceber que o Compose não era “Android sem XML”. No React, a gente já está acostumado com a ideia de que a interface responde ao estado. No Compose, a lógica é parecida: você descreve a UI com funções @Composable, usa estado, e o framework atualiza o que precisa quando esse estado muda. Não é o mesmo modelo do React, mas a ideia de “descrever a interface em vez de montar tudo manualmente” está ali. Quando eu passei a enxergar o Compose assim, ele começou a fazer muito mais sentido.

Row, Column e Box me fizeram parar de brigar com layout

A parte de layout foi uma das primeiras coisas que me deixou confortável. Row e Column são quase autoexplicativos. Um organiza na horizontal, o outro na vertical. E o Box resolve aqueles casos em que você quer empilhar coisas ou alinhar um elemento dentro de uma área. Não é CSS. Mas, se você já trabalhou com flexbox, a sensação não é tão distante.

// código React
export function ProfileHeader() {
  return (
    <div
      style={{
        display: 'flex',
        flexDirection: 'row',
        gap: 12,
        alignItems: 'center',
      }}
    >
      <img
        src="/avatar.png"
        alt="Avatar"
        style={{
          width: 48,
          height: 48,
          borderRadius: 999,
        }}
      />

      <div
        style={{
          display: 'flex',
          flexDirection: 'column',
        }}
      >
        <strong>Marcos</strong>
        <span>Android + Backend</span>
      </div>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode
// código Compose
@Composable
fun ProfileHeader() {
    Row(
        modifier = Modifier.fillMaxWidth(),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        Image(
            painter = painterResource(R.drawable.avatar),
            contentDescription = "Avatar",
            modifier = Modifier
                .size(48.dp)
                .clip(CircleShape)
        )

        Column {
            Text(
                text = "Marcos",
                style = MaterialTheme.typography.titleMedium
            )
            Text(
                text = "Android + Backend",
                style = MaterialTheme.typography.bodyMedium
            )
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

O que eu gostei aqui é que o layout fica muito explícito. Você bate o olho e entende a estrutura da tela sem precisar ficar pulando entre XML, style, tema e código.

Modifier foi a peça que mais me confundiu no começo

Se teve uma coisa que me pegou de verdade foi Modifier. No início eu pensava nele quase como um “style” do Compose. Só que não é só isso. Com Modifier, você mexe em tamanho, espaçamento, comportamento, clique, borda, background, clipping, alinhamento… e o detalhe mais importante: a ordem importa.

Isso parece pequeno até você perceber que:

  • padding antes ou depois de clickable muda a área clicável
  • clip antes ou depois de background muda o resultado visual
  • border, padding e background não são intercambiáveis

Foi uma das partes em que eu mais apanhei, porque no começo eu só encadeava as coisas até “ficar com cara certa”. Depois entendi que ali existe uma lógica mesmo.

Um exemplo simples

@Composable
fun SelectableCard(
    selected: Boolean,
    onClick: () -> Unit,
    content: @Composable () -> Unit
) {
    val borderColor = if (selected) Color(0xFF4F46E5) else Color(0xFFE5E7EB)

    Box(
        modifier = Modifier
            .clip(RoundedCornerShape(12.dp))
            .border(1.dp, borderColor, RoundedCornerShape(12.dp))
            .clickable(onClick = onClick)
            .padding(16.dp)
    ) {
        content()
    }
}
Enter fullscreen mode Exit fullscreen mode

Depois que eu entendi que Modifier não era enfeite, mas parte da lógica visual e interativa da UI, muita coisa ficou mais previsível.

O lado bom: if, when e loops são só Kotlin

Uma coisa que eu gostei rápido no Compose foi não precisar entrar naquele modo mental de “template + expressão” toda hora. No React, embora tudo esteja dentro do JavaScript, ainda existe aquela mistura constante entre JSX e lógica de renderização. No Compose, a interface já é código Kotlin desde o começo. Então você usa if, when, for e outras estruturas da linguagem de forma direta, sem sentir que está alternando entre marcação e lógica o tempo todo.

Input controlado continua sendo input controlado

Essa parte foi das mais tranquilas pra mim porque lembra muito o React. Se você já trabalhou com input controlado, não tem muito mistério. O valor vem do estado, o onValueChange atualiza esse estado, e a UI reage.

@Composable
fun NameForm() {
    var name by remember { mutableStateOf("") }

    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        OutlinedTextField(
            value = name,
            onValueChange = { name = it },
            label = { Text("Nome") }
        )

        if (name.isNotBlank()) {
            Text("Hello, $name")
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Pra mim, o que muda de verdade aqui não é o conceito de estado, e sim o vocabulário que o Compose usa para lidar com isso. Se no React a gente pensa em useState e no ciclo de renderização do componente, no Compose essa conversa passa muito por remember, mutableStateOf e rememberSaveable. O remember eu comecei a enxergar como aquele estado que continua existindo entre recomposições. Já o rememberSaveable fica mais próximo daqueles casos em que você quer preservar melhor a informação da tela, por exemplo em uma rotação, sem simplesmente perder tudo que o usuário já fez.

LazyColumn foi onde o Compose começou a ficar familiar

Quando cheguei em lista, tudo começou a parecer menos “mundo novo”. No Compose, LazyColumn é aquele componente que você naturalmente usa pra listas maiores, renderizando só o necessário. E aí entra uma coisa que pra quem vem do React já deveria soar conhecida a identidade do item importa. Se existe um id, usa esse id.

data class Hero(
    val id: String,
    val name: String
)

@Composable
fun HeroList(
    heroes: List<Hero>,
    selectedId: String?,
    onSelect: (String) -> Unit
) {
    LazyColumn(
        contentPadding = PaddingValues(8.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        items(
            items = heroes,
            key = { it.id }
        ) { hero ->
            SelectableCard(
                selected = hero.id == selectedId,
                onClick = { onSelect(hero.id) }
            ) {
                Text(hero.name)
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

O padrão stateful/stateless me ajudou bastante

Outra coisa que eu trouxe do React foi separar melhor o que segura estado do que só renderiza, me ajudou muito pensar assim:

  • a tela principal segura o estado
  • os componentes menores recebem dados e callbacks
@Composable
fun ChooseHeroScreen(heroes: List<Hero>) {
    var selectedId by rememberSaveable { mutableStateOf<String?>(null) }

    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("Qual é seu herói favorito?") }
            )
        }
    ) { innerPadding ->
        HeroList(
            heroes = heroes,
            selectedId = selectedId,
            onSelect = { selectedId = it },
            modifier = Modifier.padding(innerPadding)
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

Isso conversa muito com state hoisting, que é outro conceito que no começo parece nome bonito pra algo simples deixar o estado subir para um nível mais apropriado.

O que mais me atrapalhou vindo do React

Algumas coisas me fizeram perder mais tempo do que eu gostaria:

Achar que Modifier era só estilo

  • Não é. Ele mexe com layout, interação e até semântica.

Ignorar a ordem das coisas

  • No Compose, a ordem muda bastante o resultado.

Colocar estado cedo demais no componente errado

  • Quando eu deixava componente pequeno segurando regra demais, rapidamente ficava chato de reaproveitar.

Subestimar rememberSaveable

  • Tem tela em que perder estado por besteira é irritante.

Tratar Compose como XML com sintaxe diferente

  • Esse foi provavelmente o maior erro de todos.

No fim das contas

Hoje, se eu tivesse que explicar Jetpack Compose de um jeito simples, eu diria que ele me pareceu uma mistura de três coisas:

  • a mentalidade declarativa que eu já conhecia de React
  • a estrutura forte de uma UI nativa Android
  • a praticidade de escrever tudo em Kotlin, sem ficar alternando demais de contexto

O que mais me ajudou a sair da trava inicial foi parar de tentar decorar API e focar em entender alguns blocos básicos:

  • Row, Column e Box
  • Modifier
  • remember e rememberSaveable
  • LazyColumn
  • state hoisting

Depois disso, o resto começou a parecer menos intimidador.

Compose ainda tem muita coisa que eu estou aprendendo. Mas, honestamente, depois que esse modelo mental encaixa, a experiência fica bem melhor do que eu imaginava quando comecei.

Top comments (0)