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.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
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
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
- Gerenciar entradas do usuário