Conceitos e implementação do Jetpack Compose
O papel da IU é mostrar os dados do app na tela e atuar como o ponto principal de interação do usuário. Sempre que os dados mudam, seja devido à interação do usuário (como o pressionamento de um botão) ou a entradas externas (como uma resposta de rede), a interface precisa ser atualizada para refletir essas mudanças. A IU é uma representação visual do estado do app recuperado da camada de dados.
No entanto, os dados do app recebidos da camada de dados costumam estar em um formato diferente das informações que são que precisam ser mostradas. Por exemplo, talvez você só precise de parte dos dados da IU ou tenha que combinar duas fontes de dados diferentes para apresentar informações relevantes ao usuário. Independentemente da lógica aplicada, você deve transmitir à IU todas as informações que ela precisa renderizar totalmente. A camada de IU é o pipeline que converte as mudanças de dados do app em um formato que a IU pode usar para que elas sejam mostradas.
Expor o estado da IU
Após definir o estado da IU e determinar como você vai gerenciar a produção
desse estado, a próxima etapa é apresentar o estado produzido à IU. Como
você está usando o UDF para gerenciar a produção do estado, pode considerar o
estado produzido como um fluxo. Em outras palavras, várias versões dele
serão produzidas ao longo do tempo. Assim, você precisa expor o estado da IU em um
detentor de dados observáveis, como LiveData ou StateFlow. O motivo é que
a IU pode reagir a qualquer mudança feita no estado sem precisar
extrair dados de forma manual diretamente da ViewModel. Esses tipos também têm o
benefício de sempre ter a versão mais recente do estado da IU armazenada em cache, o que é
útil para uma restauração rápida dele após mudanças de configuração.
class NewsViewModel(...) : ViewModel() {
val uiState: StateFlow<NewsUiState> = …
}
Uma maneira comum de criar um fluxo da classe UiState é expor um fluxo mutável de apoio
como um fluxo imutável da ViewModel. Por exemplo, expor
MutableStateFlow<UiState> como StateFlow<UiState>.
class NewsViewModel(...) : ViewModel() {
private val _uiState = MutableStateFlow(NewsUiState())
val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()
...
}
A ViewModel pode expor métodos que modificam internamente o estado,
publicando atualizações para que a IU consuma. Considere, por exemplo, o caso em que uma
ação assíncrona precisa ser realizada. Uma corrotina pode ser iniciada usando a propriedade
viewModelScope, e
o estado mutável pode ser atualizado após a conclusão.
class NewsViewModel(
private val repository: NewsRepository,
...
) : ViewModel() {
private val _uiState = MutableStateFlow(NewsUiState())
val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()
private var fetchJob: Job? = null
fun fetchArticles(category: String) {
fetchJob?.cancel()
fetchJob = viewModelScope.launch {
try {
val newsItems = repository.newsItemsForCategory(category)
_uiState.update {
it.copy(newsItems = newsItems)
}
} catch (ioe: IOException) {
// Handle the error and notify the UI when appropriate.
_uiState.update {
val messages = getMessagesFromThrowable(ioe)
it.copy(userMessages = messages)
}
}
}
}
}
Consumir o estado da IU
Ao consumir detentores de dados observáveis na IU, considere o
ciclo de vida dela. Isso é importante porque a interface
não pode observar o estado dela quando a visualização não está sendo mostrada para o
usuário. Para saber mais sobre esse assunto, consulte esta postagem
do blog (em inglês).
Ao usar o LiveData, a interface LifecycleOwner cuida das questões do
ciclo de vida de forma implícita. Ao usar fluxos, é recomendável gerenciar isso com o escopo adequado de corrotinas
e a API repeatOnLifecycle:
class NewsActivity : AppCompatActivity() {
private val viewModel: NewsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
...
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect {
// Update UI elements
}
}
}
}
}
Mostrar operações em andamento
Uma maneira simples de representar os estados de carregamento em uma classe UiState é com um
campo booleano:
data class NewsUiState(
val isFetchingArticles: Boolean = false,
...
)
O valor dessa sinalização representa a presença ou ausência de uma barra de progresso na interface.
class NewsActivity : AppCompatActivity() {
private val viewModel: NewsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
...
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// Bind the visibility of the progressBar to the state
// of isFetchingArticles.
viewModel.uiState
.map { it.isFetchingArticles }
.distinctUntilChanged()
.collect { progressBar.isVisible = it }
}
}
}
}
Animações
Para fornecer transições de navegação de nível superior fluidas e suaves,
recomendamos que você aguarde a segunda tela carregar os dados antes de iniciar a animação.
O framework de visualização do Android fornece hooks para atrasar transições entre destinos
de fragmento com as APIs
postponeEnterTransition()
e
startPostponedEnterTransition(). Essas APIs fornecem uma maneira de garantir que os elementos da IU na segunda
tela, normalmente uma imagem buscada na rede, estejam prontos para serem mostrados
antes da animação da transição para essa tela.
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado
- Produção do estado da interface
- Detentores de estado e estado da interface {:#mad-arch}
- Guia para a arquitetura do app