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 esse estado e a IU o exibe.

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 interface,
o ViewModel
precisa ser o responsável por processá-los e atualizar o estado da interface.
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. A
O ViewModel
também processa o evento de login expondo 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
Jetnews is a sample news reading app, built with Jetpack Compose. The goal of the sample is to showcase the current UI capabilities of Compose.
To try out this sample app, use the latest stable version of Android Studio. You can clone this repository These samples showcase different architectural approaches to developing Android apps. In its different branches you'll find the same app (a TODO app) implemented with small differences.
In this branch you'll find:
User Interface built with Jetpack Jetchat is a sample chat app built with Jetpack Compose.
To try out this sample app, use the latest stable version of Android Studio. You can clone this repository or import the project from Android Studio following the steps here.
This sample Learn how this app was designed and built in the design case study, architecture learning journey and modularization learning journey.
This is the repository for the Now in Android app. It is a work in progress 🚧.
Now in Android is a fully functionalJetnews sample
Architecture
Jetchat sample
Now in Android App