No Compose, a IU é imutável. Não há como atualizá-la depois que ela for desenhada. O que pode ser controlado é o estado da IU. Cada vez que o estado da IU muda, o Compose recria as partes da árvore da IU que mudaram. As funções que podem ser compostas conseguem aceitar estados e expor eventos. Por exemplo, um TextField
aceita um valor e expõe um onValueChange
de callback que solicita que o gerenciador de callback mude o valor.
var name by remember { mutableStateOf("") } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } )
Como as funções que podem ser compostas aceitam estados e expõem eventos, o padrão de fluxo de dados unidirecional é adequado para o Jetpack Compose. Este guia se concentra em como implementar o padrão de fluxo de dados unidirecional no Compose, como implementar detentores de estados e eventos e como trabalhar com ViewModels no Compose.
Fluxo de dados unidirecional
Um fluxo de dados unidirecional (UDF, na sigla em inglês) é um padrão de design em que os estados fluem para baixo e os eventos para cima. Ao seguir o fluxo de dados unidirecional, você pode desagrupar as funções que podem ser compostas que exibem o estado na IU das partes do app que armazenam e mudam esse estado.
O loop de atualização da IU para um app usando o fluxo de dados unidirecional é semelhante a este:
- Evento: parte da interface gera um evento e o transmite para cima, como um clique de botão transmitido ao ViewModel para ser processado, ou um evento transmitido de outras camadas do app, como a indicação de que a sessão do usuário expirou.
- Estado de atualização: um manipulador de eventos pode mudar o estado.
- Estado de exibição: o detentor do estado transmite o estado, e a interface o mostra.

Seguir esse padrão ao usar o Jetpack Compose oferece várias vantagens:
- Capacidade de teste: a separação do estado da IU que o exibe facilita a realização de testes em ambos de forma isolada.
- Encapsulamento de estado: como o estado pode ser atualizado em um só lugar e há apenas uma fonte de verdade para o estado de uma função de composição, é menos provável que você crie bugs causados por estados inconsistentes.
- Consistência da IU: todas as atualizações de estado são refletidas imediatamente na IU pelo uso de detentores de estado observáveis, como
StateFlow
ouLiveData
.
Fluxo de dados unidirecional no Jetpack Compose
Funções que podem ser compostas operam com base em estados e eventos. Por exemplo, um TextField
só é atualizado quando o parâmetro value
é atualizado e expõe um callback onValueChange
, evento que solicita que o valor mude para um novo. O Compose define o objeto State
como um detentor de valor, e as mudanças de valor do estado acionam uma recomposição. É possível manter o estado em um remember { mutableStateOf(value) }
ou um rememberSaveable { mutableStateOf(value)
, dependendo do tempo pelo qual o valor precisa ser lembrado.
O tipo de valor do TextField
de composição é String
. Portanto, ele pode ser originado de qualquer lugar: seja de um valor fixo no código, um ViewModel ou transmitido da função de composição mãe. Não é necessário mantê-lo em um objeto State
, mas é necessário atualizar o valor quando onValueChange
é chamado.
Definir parâmetros que podem ser compostos
Ao definir os parâmetros de estado de uma função que pode ser composta, é necessário considerar as seguintes questões:
- Qual é a capacidade de reutilização ou flexibilidade da função?
- Como os parâmetros de estado afetam o desempenho dessa função?
Para incentivar o desagrupamento e a reutilização, cada função que pode ser composta precisa conter a menor quantidade possível de informações. Por exemplo, ao criar uma função para conter o cabeçalho de uma matéria jornalística, prefira transmitir apenas as informações que precisam ser mostradas, e não a matéria toda:
@Composable fun Header(title: String, subtitle: String) { // Recomposes when title or subtitle have changed. } @Composable fun Header(news: News) { // Recomposes when a new instance of News is passed in. }
Algumas vezes, o uso de parâmetros individuais também melhora a performance. Por exemplo, caso News
contenha mais informações do que apenas title
e subtitle
, sempre que uma nova instância de News
for transmitida para Header(news)
, a composição será recompilada, ainda que title
e subtitle
não tenham mudado.
Considere cuidadosamente o número de parâmetros transmitidos. Ter uma função com muitos parâmetros diminui a ergonomia dela. Portanto, nesse caso, agrupar as funções em uma classe é a melhor opção.
Eventos no Compose
Cada entrada do app precisa ser representada como um evento: toques, mudanças de texto e até mesmo timers ou outras atualizações. À medida que esses eventos mudam o estado da IU, o ViewModel
precisa processá-los e atualizar o estado da IU.
A camada de IU nunca muda o estado fora de um gerenciador de eventos, porque isso pode introduzir inconsistências e bugs no aplicativo.
Prefira transmitir valores imutáveis para lambdas de estado e manipulador de evento. Essa abordagem tem os seguintes benefícios:
- Melhora a reutilização.
- Garante que a IU não mudará o valor do estado diretamente.
- Evita problemas de simultaneidade, porque garante que o estado não será modificado a partir de outra linha de execução.
- Geralmente, reduz a complexidade do código.
Por exemplo, uma função de composição que aceita uma String
e um lambda como parâmetros pode ser chamada a partir de muitos contextos e é altamente reutilizável. Suponha que a barra superior do app sempre exiba texto e tenha um botão "Voltar". Você pode definir uma função MyAppTopAppBar
mais genérica que pode ser composta e que receba o texto e o botão "Voltar" como parâmetros:
@Composable fun MyAppTopAppBar(topAppBarText: String, onBackPressed: () -> Unit) { TopAppBar( title = { Text( text = topAppBarText, textAlign = TextAlign.Center, modifier = Modifier .fillMaxSize() .wrapContentSize(Alignment.Center) ) }, navigationIcon = { IconButton(onClick = onBackPressed) { Icon( Icons.AutoMirrored.Filled.ArrowBack, contentDescription = localizedString ) } }, // ... ) }
ViewModels, estados e eventos: um exemplo
Ao usar ViewModel
e mutableStateOf
, você também vai poder introduzir o fluxo de dados unidirecional no app se uma das seguintes condições for verdadeira:
- O estado da IU é exposto por holders de estado observáveis, como
StateFlow
ouLiveData
. - A classe
ViewModel
gerencia eventos provenientes da IU ou de outras camadas do app e atualiza o holder de estado com base nos eventos.
Por exemplo, ao implementar uma tela de login, tocar em um botão Login fará com que o app exiba um ícone de progresso de carregamento e uma chamada de rede. Se o login for realizado corretamente, o app vai navegar para uma tela diferente. No caso de um erro, o app vai mostrar uma Snackbar. Veja como modelar o estado da tela e o evento:
A tela tem quatro estados:
- Desconectado: quando o usuário ainda não fez login.
- Em andamento: quando o app está tentando fazer login do usuário, realizando uma chamada de rede.
- Erro: quando ocorreu um erro durante o login.
- Conectado: quando o usuário está conectado.
Você pode modelar esses estados como uma classe selada. O ViewModel
expõe o estado como um State
, define o estado inicial e atualiza o estado conforme necessário. O ViewModel
também processa o evento de login ao expor um método onSignIn()
.
class MyViewModel : ViewModel() { private val _uiState = mutableStateOf<UiState>(UiState.SignedOut) val uiState: State<UiState> get() = _uiState // ... }
Além da API mutableStateOf
, o Compose fornece extensões para LiveData
, Flow
e Observable
para serem registradas como um listener e representar o valor como um estado.
class MyViewModel : ViewModel() { private val _uiState = MutableLiveData<UiState>(UiState.SignedOut) val uiState: LiveData<UiState> get() = _uiState // ... } @Composable fun MyComposable(viewModel: MyViewModel) { val uiState = viewModel.uiState.observeAsState() // ... }
Saiba mais
Para saber mais sobre a arquitetura no Jetpack Compose, consulte estes recursos:
Amostras
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Estado e Jetpack Compose
- Salvar o estado da interface no Compose
- Processar a entrada do usuário