State Hoisting: elevação de estado e fluxo unidirecional no Compose

Autor: IT Sectr Publicado: 2026-06-28 Tempo de leitura: 8 min

State Hoisting é um padrão no Jetpack Compose onde o estado é movido para fora de uma função Composable filha para a função pai, e a filha recebe dados através de parâmetros e notifica sobre mudanças via callbacks. Esta é uma implementação do princípio de fluxo de dados unidirecional (UDF), onde o estado sobe e os eventos descem. De acordo com Google Android Developers, 2026, State Hoisting torna os componentes reutilizáveis, testáveis e previsíveis.

Principais pontos

  • State Hoisting move o estado do componente filho para o pai
  • UDF (Fluxo de Dados Unidirecional) — estado flui para baixo, eventos para cima
  • Parâmetros do componente filho: valor (T) + lambda (T) -> Unit
  • Reutilização — estado elevado permite usar a mesma função com diferentes fontes de dados
  • Testes — State Hoisting simplifica testes unitários isolando a lógica da UI

O que é State Hoisting no Jetpack Compose

State Hoisting é um padrão onde uma função Composable não possui o estado, mas o recebe de fora. Em vez de var dentro da função, dois parâmetros são usados: um valor para exibição e um callback lambda para lidar com mudanças. Tecnicamente, isso significa que o componente filho se torna stateless (sem estado próprio), enquanto o pai é stateful (possui o estado).

Exemplo: o componente TextField do Material3 não armazena o texto inserido internamente. Ele aceita value: String e onValueChange: (String) -> Unit. O pai que chama TextField declara var value by remember { mutableStateOf("") } e passa value e onValueChange. Isso é State Hoisting clássico: TextField é um componente burro (apenas exibe e reporta a entrada), o pai é inteligente (possui o estado).

Stateless vs Stateful: Um componente stateless é mais fácil de testar — não depende de estado interno, seu comportamento é totalmente determinado pelos parâmetros de entrada. Um componente stateful é conveniente para prototipagem rápida, mas mais difícil de reutilizar: está fortemente acoplado a uma única fonte de dados. State Hoisting lhe dá a escolha: qualquer componente pode ser tornado stateless movendo o estado para cima.

Fluxo de Dados Unidirecional (UDF) e State Hoisting

UDF (Fluxo de Dados Unidirecional) é um princípio arquitetônico onde os dados se movem em uma direção: da fonte da verdade (ViewModel ou Composable pai) para a UI, e os eventos fluem na direção oposta. State Hoisting é a implementação do UDF no nível de componentes individuais. Em vez de cada componente decidir quando e como mudar seu próprio estado, ele notifica o pai sobre um evento, e o pai decide como mudar o estado.

Vantagens do UDF: previsibilidade — o estado muda em apenas um lugar, eliminando condições de corrida; rastreabilidade — a pilha de chamadas permite reconstruir a cadeia de mudanças; testes — a lógica stateful pode ser extraída para uma classe separada e testada sem UI. Em projetos grandes, UDF combinado com State Hoisting é o padrão de facto.

Fonte Única da Verdade (Single Source of Truth) é outro princípio que acompanha o UDF. Cada parte do estado tem exatamente uma fonte. Se dois componentes usam o mesmo estado, a fonte deve ser compartilhada (no nível da ViewModel ou pai comum). State Hoisting garante que a fonte esteja acima na hierarquia e não ocorra duplicação de estado.

DireçãoO que é passadoComo é implementado
Para baixo (pai → filho)Valor para exibirParâmetro value: T
Para cima (filho → pai)Evento de mudançaParâmetro onValueChange: (T) -> Unit

Regras do State Hoisting: quando e como elevar o estado

A regra principal: o estado deve ser elevado ao nível mínimo possível suficiente para todos os componentes que precisam dele. Se o estado é usado apenas dentro de um componente — mantenha-o local. Se dois componentes adjacentes precisam do mesmo estado — eleve ao pai comum. Se o estado é necessário em toda a tela — eleve à ViewModel.

A regra de elevação mínima evita complexidade desnecessária. Não faz sentido elevar o estado de um campo de texto para uma ViewModel se ele é usado apenas dentro de uma tela e não é persistido quando a Activity é recriada. Use rememberSaveable no nível do pai da tela, não na ViewModel, para estado de UI que deve sobreviver a uma rotação de tela, mas não é necessário pela lógica de negócio.

Quando elevar para ViewModel: se o estado deve sobreviver à recriação da Activity, se é necessário por várias telas, se mudar o estado dispara lógica de negócio (requisições de rede, banco de dados). State Hoisting no nível da ViewModel é um padrão padrão na arquitetura MVVM, onde a camada de UI é stateless e a ViewModel é stateful.

kotlin
    // ❌ Ruim: componente possui seu próprio estado
@Composable
fun BadTextField(label: String) {
    var text by remember { mutableStateOf("") }
    TextField(value = text, onValueChange = { text = it }, label = { Text(label) })
}

    // ✅ Bom: State Hoisting — estado no pai
@Composable
fun GoodTextField(value: String, onValueChange: (String) -> Unit, label: String) {
    TextField(value = value, onValueChange = onValueChange, label = { Text(label) })
}

    // Uso: o pai possui o estado
@Composable
fun Form() {
    var name by rememberSaveable { mutableStateOf("") }
    GoodTextField(value = name, onValueChange = { name = it }, label = "Name")
}

Exemplos de State Hoisting em componentes reais

Considere uma tela de login com dois campos (email, senha) e um botão. Os três componentes recebem estado através de State Hoisting: email e senha são gerenciados pelo pai, o botão recebe seu status enabled como um valor.

kotlin
    // State Hoisting no nível da tela
@Composable
fun LoginScreen(viewModel: LoginViewModel) {
    val uiState by viewModel.uiState.collectAsState()

    Column(modifier = Modifier.padding(16.dp)) {
        // Campo Email — State Hoisting via lambda
        EmailField(
            email = uiState.email,
            onEmailChange = { viewModel.onEmailChanged(it) }
        )

        // Campo Senha — similarmente
        PasswordField(
            password = uiState.password,
            onPasswordChange = { viewModel.onPasswordChanged(it) }
        )

        // Botão — recebe apenas enabled (somente leitura)
        LoginButton(enabled = uiState.isFormValid, onClick = viewModel::login)
    }
}

// Componente Stateless: recebe email + callback
@Composable
fun EmailField(email: String, onEmailChange: (String) -> Unit) {
    OutlinedTextField(
        value = email,
        onValueChange = onEmailChange,
        label = { Text("Email") },
        singleLine = true
    )
}

// Componente de botão Stateless
@Composable
fun LoginButton(enabled: Boolean, onClick: () -> Unit) {
    Button(onClick = onClick, enabled = enabled) {
        Text("Entrar")
    }
}

EmailField e PasswordField são completamente stateless. Podem ser reutilizados em qualquer tela conectando-os a qualquer fonte de dados. LoginButton recebe enabled como somente leitura — esta é outra forma de State Hoisting onde o estado não é elevado (o botão não pode se habilitar sozinho) mas é passado pronto. Esta abordagem proporciona máxima flexibilidade com mínimo acoplamento entre componentes.

State Hoisting vs estado local: critérios de seleção

Nem todo estado precisa ser elevado. Estado local (State dentro de um Composable) é justificado quando: os dados são necessários apenas dentro de um componente, não afetam elementos irmãos e não devem sobreviver à recomposição de uma seção específica. Por exemplo, estado de animação, foco de campo de entrada, posição atual de rolagem — é razoável mantê-los localmente.

Quando State Hoisting é necessário: o estado é usado por vários componentes filhos; uma mudança em um filho deve ser refletida em outro; a lógica de mudanças de estado precisa ser testada separadamente da UI; o estado deve sobreviver à recriação da Activity. Nestes casos, o estado local cria duplicação e inconsistência de dados.

Abordagem híbrida: mantenha o estado mínimo localmente, eleve o resto. A regra do Compose: “eleve o estado tão alto quanto necessário e tão baixo quanto possível.” Na prática, isso significa começar com remember local e só elevar o nível quando o acesso de outro componente for necessário. Não aplique State Hoisting preventivamente — isso complica o código sem necessidade.

Perguntas frequentes

Como State Hoisting difere de ViewModel?

State Hoisting é um padrão de nível de componente de UI. ViewModel é uma camada arquitetônica para lógica de negócio. State Hoisting pode elevar o estado ao nível do Composable pai, ao nível da tela ou à ViewModel. ViewModel é o ponto mais alto de elevação para o estado que deve sobreviver à recriação da Activity.

Como testar um componente com State Hoisting?

Um componente stateless é testado simplesmente passando valores. Chame o Composable com os parâmetros necessários e verifique a exibição usando ComposeTestRule. As mudanças de estado são testadas no nível do pai ou da ViewModel — separadas da UI. Isso simplifica significativamente os testes: não há necessidade de simular recomposição dentro do componente.

Pode-se elevar o Estado como somente leitura?

Sim, é uma prática comum. Se um componente só precisa exibir dados sem a capacidade de modificá-los — passe State<T> (somente leitura). O componente será inscrito nas mudanças, mas não poderá iniciá-las. Isso fortalece o encapsulamento e protege os dados de mutações indesejadas.

E se o estado precisar ser elevado 3+ níveis de profundidade?

Para passagem profunda use CompositionLocal ou passe através de parâmetros do Composable pai. Se o estado for necessário em toda a tela — extraia para uma ViewModel e use collectAsState(). Passar através de 5+ níveis é sinal de arquitetura incorreta; reconsidere a hierarquia de componentes.

State Hoisting afeta o desempenho?

State Hoisting pode aumentar ligeiramente o número de recomposições, já que uma mudança no pai pode recompor todos os filhos. Use derivedStateOf para filtrar mudanças e keys no LazyColumn para atualizações direcionadas. Na maioria dos cenários, a sobrecarga do State Hoisting é insignificante comparada ao benefício de manutenibilidade.

Resumo

  • State Hoisting move o estado para o componente pai, tornando o filho stateless
  • UDF garante fluxo de dados unidirecional: estado para baixo, eventos para cima
  • Reutilização — componentes stateless podem ser conectados a qualquer fonte de dados
  • Testes — testes de UI apenas verificam exibição, a lógica é testada separadamente
  • Elevação mínima — eleve apenas o necessário
  • ViewModel — o ponto mais alto de elevação para estado com lógica de negócio
  • Recomendação: comece com remember local, eleve apenas quando necessário

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também