Como implementar uma tela de carregamento no Jetpack Compose
Alex Felipe
Posted on June 23, 2023
Ao desenvolver um App Android, é muito comum buscarmos informações para apresentar na tela, seja a busca de um banco de dados interno ou por uma API.
Independente da fonte de dados que usamos, uma coisa é certa, a disponibilidade dos dados nem sempre será garantida! Ou seja, enquanto o dado não está pronto, o que podemos mostrar para o nosso usuário?
Uma das abordagens comuns é apresentar uma tela ou um componente de carregamento. Existem várias maneiras de representar o carregamento, pode ser apenas um texto com a mensagem 'carregando', ou então, indicadores de progresso do Material Design.
TL;DR
Para você que quer apenas o código completo para testar e chegar neste resultado:
A tela é representada por esse composable:
@Composable
fun ProductsListScreen(uiState: ProductsListUiState) {
val products = uiState.products
val isLoading = uiState.isLoading
if (isLoading) {
Box(modifier = Modifier.fillMaxSize()) {
CircularProgressIndicator(Modifier.align(Center))
}
} else {
LazyColumn(Modifier.fillMaxSize()) {
items(products) { p ->
Column(
Modifier
.clip(RoundedCornerShape(10.dp))
.padding(8.dp)
.fillMaxWidth()
.border(
1.dp,
Color.Gray.copy(alpha = 0.5f),
RoundedCornerShape(10.dp)
)
.padding(8.dp)
) {
Text(text = p.name, fontWeight = FontWeight.Bold, fontSize = 24.sp)
Text(text = p.description)
Text(
text = p.price.toBrazilianCurrency(),
fontWeight = FontWeight.Bold,
style = TextStyle.Default.copy(color = Color(0xFF4CAF50)),
fontSize = 18.sp
)
}
}
}
}
}
o modelo de produto, lista de produtos de amostra e o UiState:
class ProductsListUiState(
val products: List<Product> = emptyList(),
val isLoading: Boolean = true
)
val sampleProducts = List(10) {
Product(
name = LoremIpsum(Random.nextInt(1, 5)).values.first(),
description = LoremIpsum(Random.nextInt(1, 10)).values.first(),
price = BigDecimal(Random.nextInt(1, 100) * it)
)
}
class Product(
val name: String,
val description: "String,"
val price: BigDecimal
)
O formatador de moeda brasileira:
private fun BigDecimal.toBrazilianCurrency(): String =
NumberFormat.getCurrencyInstance(
Locale("pt", "br")
).format(this)
o código para o gerenciamento de estado com o UiState:
var uiState by remember {
mutableStateOf(ProductsListUiState())
}
LaunchedEffect(null) {
delay(3000)
uiState = ProductsListUiState(
products = sampleProducts,
isLoading = false
)
}
ProductsListScreen(uiState)
Caso o seu interesse é entender como esse código funciona e as motivações para implementá-lo dessa forma, continue a leitura do artigo 😄
Como representar um carregamento na tela?
No Jetpack Compose, podemos apresentar um indicador de progresso circular da seguinte maneira:
Box(modifier = Modifier.fillMaxSize()) {
CircularProgressIndicator(
Modifier.align(Alignment.Center)
)
}
Observe que temos um indicador indeterminado, ou seja, ele fica girante pra sempre! Novamente, essa é apenas uma possibilidade de representação e poderíamos usar qualquer outro componente... O grande detalhe é:
"Como podemos escrever uma lógica para apresentar ou esconder o carregamento no momento esperado?" 🤔
Gerenciamento de estado
Para isso, precisamos utilizar gerenciamento de estado no Jetpack Compose. Há mais de uma implementação de gerenciamento de estado, seja direto nos composables ou a partir de ViewModel que é o mais recomendado.
Dado que o objetivo deste artigo é focar em mostrar apenas como fazer a representação de carregamento, farei a implementação direta no composable. Antes de mexer no código, vamos entender o que precisamos fazer:
- tela principal que precisa buscar os produtos
- ao acessar a tela, apresenta uma representação de carregamento
- após carregar produtos, mostra os produtos em lista
Agora que sabemos o que precisamos implementar, vamos seguir com o código.
Definindo o modelo e criando amostras de produtos
Primeiro, vamos começar com o modelo para o produto e a criação de algumas amostras:
class Product(
val name: String,
val description: String,
val price: BigDecimal
)
val sampleProducts = List(10) {
Product(
name = LoremIpsum(Random.nextInt(1, 5)).values.first(),
description = LoremIpsum(Random.nextInt(1, 10)).values.first(),
price = BigDecimal(Random.nextInt(1, 100) * it)
)
}
Com esse código, geramos 10 produtos com valores aleatórios para nome, descrição e preço.
Implementação da tela da lista de produtos
Para apresentar os produtos, vamos criar a tela de lista de produtos:
@Composable
fun ProductsListScreen(products: List<Product>) {
LazyColumn(Modifier.fillMaxSize()) {
items(products) { p ->
Column(
Modifier
.clip(RoundedCornerShape(10.dp))
.padding(8.dp)
.fillMaxWidth()
.border(
1.dp,
Color.Gray.copy(alpha = 0.5f),
RoundedCornerShape(10.dp)
)
.padding(8.dp)
) {
Text(text = p.name, fontWeight = FontWeight.Bold, fontSize = 24.sp)
Text(text = p.description)
Text(
text = p.price.toBrazilianCurrency(),
fontWeight = FontWeight.Bold,
style = TextStyle.Default.copy(color = Color(0xFF4CAF50)),
fontSize = 18.sp
)
}
}
}
}
E para melhorar o visual do preço, vamos também adicionar o formatador de moeda brasileira:
private fun BigDecimal.toBrazilianCurrency(): String =
NumberFormat.getCurrencyInstance(
Locale("pt", "br")
).format(this)
Com a tela implementada, podemos focar no gerenciamento de estado da mesma.
Representando o estado da interface de usuário - UI State
Para representar os dados da tela, utilizamos o padrão de estado de interface do usuário ou UI State. A ideia de um UI State é conter os dados que devem ser apresentados na tela, ou então, um estado do dado, como por exemplo, um estado que indique se o dado foi carregado, se houve falha ou se está carregando!
Essa representação pode ser feita a partir de uma classe qualquer:
class ProductsListUiState(
val products: List<Product> = emptyList(),
val isLoading: Boolean = true
)
E então, a nossa tela pode receber ProductsListUiState
via parâmetro e apresentar o conteúdo com base nos dados do UI State:
@Composable
fun ProductsListScreen(uiState: ProductsListUiState) {
val products = uiState.products
val isLoading = uiState.isLoading
if (isLoading) {
Box(modifier = Modifier.fillMaxSize()) {
CircularProgressIndicator(Modifier.align(Center))
}
} else {
LazyColumn(Modifier.fillMaxSize()) { ... }
}
}
Nesta configuração, enquanto o isLoading
estiver como true
, a lista não é apresentada!
Testando o App simulando a busca de produtos
Agora que temos a representação, precisamos modificar a lógica de chamada para que faça a busca das informações e atualize o UI State:
var uiState by remember {
mutableStateOf(ProductsListUiState())
}
LaunchedEffect(null) {
delay(3000)
uiState = ProductsListUiState(
products = sampleProducts,
isLoading = false
)
}
ProductsListScreen(uiState)
Veja que agora o App apresenta o indicador de progresso circular, e então, após 3 segundos, mostra a lista de produtos! Não entendeu o código? Vamos para uma breve explicação.
- Definimos o UI State a partir de um
remember
para que a inicialização não seja afetada pela recomposição - o
LaunchedEffect(null)
é um composable de Side Effect que não emite componente visual. - Componentes de Side Effect dão suporte para códigos que não são afetados pela recomposição. Ao enviar
null
como argumento, indicamos que ele vai executar apenas uma vez! - O
LaunchedEffect()
também permite executar coroutines, e é por isso que ele não trava a execução mesmo aplicando umdelay
de segundos via coroutine. - Ao modificar o
uiState
acontece a recomposição e é enviado um novouiState
com os produtos novos e a sinalização de que não está mais carregando para a tela de lista de produtos.
Para saber mais
É importante ressaltar que essa é uma implementação simples e objetiva, ou seja, existem abordagens mais sofisticadas e recomendadas para implementar o gerenciamento de estado com UI State. Um dos exemplos mais usados, é a partir de ViewModels, embora seja mais complexo ele resolve uma série de detalhes a nível de arquitetura de App.
Se você tem interesse no assunto e quer aprender a partir de um conteúdo mais estruturado, posso te recomendar a formação de Jetpack Compose: gerenciamento de estado da Alura. Caso você ainda não seja assinante, eu posso te ajudar com um cupom de desconto da assinatura da Alura.
O que você achou desta implementação? Já conhecia essas técnicas de gerenciamento de estado para apresentar um conteúdo dinâmico nos composables? Aproveite para compartilhar outras técnicas que utiliza pra isso 😉
Posted on June 23, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
January 26, 2024