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. Os elementos combináveis podem aceitar estados e expor eventos. Por exemplo, um
TextField aceita um valor e expõe um callback onValueChange 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 o transmite, 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
StateFlowouLiveData.
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 combinável, considere 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 desacoplamento e a reutilização, cada elemento combinável precisa conter a menor quantidade de informações possível. 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 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.
- Verifique se a UI não muda 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 combinável MyAppTopAppBar mais genérica
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 UI é exposto usando detentores de estado observáveis, como
StateFlowouLiveData. - O
ViewModelgerencia eventos provenientes da IU ou de outras camadas do app e atualiza o detentor do 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 os seguintes 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 entrada do usuário