Olá, pessoal! Eu sou Alex Felipe, instrutor da Alura, e vou apresentar para vocês o curso Jetpack Compose: Lazy layouts e estados. A partir deste curso, eu vou assumir que vocês têm alguns pré-requisitos em relação ao Jetpack Compose:
Caso não conheçam esses tópicos, acessem o curso Jetpack Compose: criando um app android, aqui da Alura. A partir dele, vocês conseguirão verificar e aprender todo o conteúdo necessário a partir deste segundo curso, que é uma continuação do primeiro.
Então daremos continuidade ao projeto do primeiro curso com lazy layout e estados. Se entrarmos no Android Studio e abrirmos nosso aplicativo, notem que ele tem algumas funcionalidades que o primeiro projeto não tinha. Por exemplo, na parte superior da tela inicial há um campo de texto, através do qual adicionamos uma nova funcionalidade ao nosso app, que é o Filtro de produtos.
Por exemplo, se clicarmos nesse campo, o teclado do celular sobe na parte inferior da tela. Com ele, conseguimos digitar um conteúdo que queremos. Digitando tanto no teclado físico ou virtual, observamos a ação de filtragem dos resultados. Esse filtro irá pesquisar pelos resultados que queremos.
Sendo assim, se digitarmos a letra "a" nesse campo, ele busca todo conteúdo que tem "a" no nome ou descrição. Se escrevermos "hamburguer", ele irá procurar tudo que está relacionado a hamburguer.
No caso, teremos só um resultado porque só um produto tem esse nome. Enquanto isso, nas descrições estão preenchidas apenas com o Lorem Ipsum, que é o texto que utilizamos apenas para preencher conteúdo. Dessa forma, se escrevermos "ipsu" no campo de texto, aparecem outros produtos com essa descrição.
Então essa será a funcionalidade que adicionaremos ao nosso app e, para isso, utilizaremos novas técnicas. Um exemplo é a técnica de carregamento de imagens a partir de URL. Exploraremos bastante nas fotos dos nossos produtos, e aprenderemos como fazer isso a partir da Biblioteca Coil.
Também, como vimos, esse campo de texto é um Composable novo e aprenderemos a implementá-lo. Para adicionarmos essa funcionalidade e, quando buscarmos, a tela atualizar e mostrar apenas os resultados compatíveis, precisamos de uma introdução a estados dentro do Jetpack Compose.
Faremos o gerenciamento de estados e assim por diante e aprenderemos como lidar com isso e seus problemas. Outra técnica muito importante é o Lazy layout, que eu comentei anteriormente. Ele serve justamente para implementarmos essas soluções, porque temos uma coleção de composables.
Seja a nossa lista de produtos ou nossas seções, perceberemos que esse tipo de solução é bastante bem-vinda para garantir esse tipo de layout. Portanto entenderemos em detalhes como isso funciona e seus benefícios.
Aprenderemos também outras técnicas de boas-práticas, como o state hoisting. Entenderemos o que é o slot based, ou seja, a API de Slot, para personalizarmos os componentes. Descobriremos que existem slots para adicionarmos ícones, como a lupa, ou dicas do que escrever no campo, como o "o que você procura". Também entenderemos como isso funciona no Compose.
Notem que são várias informações e técnicas para esse projeto. Espero que tenham gostado e aguado vocês na primeira aula.
Vamos começar?
Antes de modificarmos nosso código, nesse momento eu apresentarei algumas modificações que ocorreram no projeto do curso anterior:
Começando pela versão do Android Studio. Para saber qual a versão que estamos usando, na barra superior do Android Studio e navegamos por "Help > About". Com isso, uma janela é aberta no centro da tela contendo as informações da IDE. A partir desse curso usaremos a versão Android Studio Dolphin | 2021.3.1 RC 1, que está acima da versão do curso anterior.
Um pequeno detalhe que notamos é que essa não é a versão de release, porque neste momento ainda não há a versão de lançamento. Contudo, não se trata de uma versão beta ou Canary, e sim uma "RC", ou seja, uma Release Candidate (Candidata a Lançamento).
Na prática, isso significa que essa é a versão mais próxima de lançamento e talvez, durante a gravação, tenha esse lançamento, por isso já estou utilizando-a. Por isso recomendo que utilizem a versão Dolphin, seja alguma versão RC ou o próprio lançamento, para acompanhar o conteúdo.
Outro ponto que notamos é relacionado à visualização inicial do nosso projeto. Ao invés de usarmos as cores padrão que vêm na criação do projeto, agora foi definido um azul índigo para o header, que veremos as especificações com mais detalhes depois. Também observamos que as imagens estão apenas com o placeholder ao invés das fotos do curso anterior, e vamos entender o porquê.
Agora que passamos pela parte visual, vamos analisar as mudanças no código. Para isso, eu separei um commit no GitHub para observarem tudo que foi atualizado em relação ao curso anterior.
Então se baixarem o projeto inicial, acessando a próxima atividade, ele já estará atualizado com todas as mudanças que eu vou mostrar nesse commit. Então vamos analisar essas mudanças para sabermos ao que devemos nos atentar.
No commit do GitHub temos todas as mudanças. Inclusive na lateral esquerda dos arquivos temos um Explorer indicando quais arquivos que sofreram alteração.
Usaremos esse Explorer para acessarmos as mudanças que queremos verificar, então ao clicarmos em build.gradle
, na pasta "app", no lado direito do Explorador aparecem as indicações de mudanças feitas no código. Então aproveitem bastante esse Explorador para identificarem o que mudou.
O que rapidamente notamos no arquivo build.gradle
é a mudança de versão do compilador do Compose para o kotlinCompilerExtensioVersion = '1.3.0'
. Antes usávamos uma variável criada para reutilizar a versão em todos os módulos do Compose, e entenderemos o motivo dessa troca. Além dele, ainda no build.gradle
, duas bibliotecas foram atualizadas:
//Código suprimido
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
implementation 'androidx.activity:activity-compose:1.5.1'
//Código suprimido
Essas bibliotecas são atualizadas constantemente, e por isso adicionei essas, que estavam disponíveis, quando alterei o projeto. Provavelmente haverá outras atualizações quando forem fazer o curso e vocês podem atualizar.
Rolando para baixo o lado esquerdo, onde aparecem as alterações do código, encontramos as alterações feitas na MainActivity.kt
. Nela temos a nossa HomeScreen()
, a primeira tela que aparece quando abrimos o aplicativo no emulador, mostrando as categorias de produtos. Agora ela recebe o sampleSections
nos parênteses.
//Código suprimido
HomeScreen(
sampleSections
)
//Código suprimido
Entenderemos melhor o que é o sampleSections
, mas adianto que é para definição das seções de produto que aparecem na tela. Seguindo nossa análise, o modelo de produto foi modificado. No modelo Product.kt
, reparamos que agora o modelo não recebe mais um @DrawableRes
, e sim uma String que pode ser nula. Entenderemos essa modificação depois, por enquanto entendam apenas que tivemos essa modificação bastante impactante.
Rolando a tela para o arquivo do SampleData.kt
, observamos que há mais informações de amostra do que antes, tanto de doces, quanto de bebidas. Além disso, nós mantivemos a lista sampleProducts
, com o hambúrguer, pizza e batata frita, mas agora adicionamos as amostra de doces e bebidas.
Na prática isso significa que todos os produtos que vemos na categoria "Promoções", na parte superior do aplicativo, tem tanto os produtos genéricos, como bebidas e doces. Essa adição é observada na linha de código sampleDrinks.toTypedArray(), *sampleCandies.toTypedArray()
. Com ela as listas criadas anteriormente são transformadas em array e são adicionados apenas os valores, através do *
.
No final no Product.kt
, criamos um mapa representando as seções. Esse mapa tem uma chave que é uma String representam o título da seção, como "Promoções", e o valor, que é a lista de produtos com as amostras que queremos. Nossa HomeScreen
, como veremos com mais atenção, também recebe esse mapa.
//Código suprimido
val sampleSections = mapOf(
"Promoções" to sampleProducts,
"Doces" to sampleCandies,
"Bebidas" to sampleDrinks
Seguindo nossa análise, notamos que o ProductItem.kt
passou por uma mudança na cor, que agora é uma lista do MaterialTheme.colors.primary
e o MaterialTheme.colors.secondary
, ou seja, utilizamos o próprio tema. Anteriormente usamos Purple500, Teal200
por ser nosso primeiro contato com as cores no Compose, mas agora, utilizando o tema, os ajustes serão conforme as configurações do tema.
Ainda no ProductItem.kt
, observamos um //TODO ajustar imagem do produto
, que será nossa primeira tarefa depois de revisarmos as mudanças de código. Atualmente as imagens estão apresentando o placeholder e trabalharemos para que sejam exibidas conforme a String que pode ser nula.
Prestando mais atenção ao nosso SampleDate.kt
, reparemos que as imagens são representadas por uma URL, portanto será a partir dessa URL que carregaremos nossas imagens. É uma abordagem comum porque, quando temos muitos objetos, não faz sentido esperarmos que as imagens estejam disponíveis no app.
Nesses contextos, as imagens serão disponibilizadas a partir de um conteúdo que pode ser carregado via Internet. Esse é o padrão que costumamos utilizar e aprenderemos isso na próxima atividade.
Seguindo com nossa análise, reparamos que fizemos mais alterações no nosso ProductItem.kt
. No @Preview
, substituímos a imagem que era um drawable por um ProductItem()
.
//Código suprimido
@Preview(showBackground = true)
@Composable
private fun ProductItemPreview() {
AluveryTheme {
Surface {
ProductItem(
Product(
name = LoremIpsum(50).values.first(),
price = BigDecimal("14.99")
)
)
}
}
}
Seguindo para a ProductsSection.kt
, tivemos uma modificação de tema. Agora adicionei o AluveryTheme
para possibilitar uma verificação de dark ou light mode (modo escuro ou claro). Descendo, observamos que eu removi o arquivo TestComponents.kt
do projeto, porque não iremos mais utilizá-lo.
Já no arquivo HomeScreen.kt
, agora teremos as seções, com o sample sections, que é um Map<String, List<Product>>
. Nossas seções também passaram a ser carregadas dinamicamente com uma interação for
.
for (section in sections) {
val title = section.key
val products = section.value
ProductsSection(
title = title,
products = products
)
}
Com o for
, extraímos o título e o valor de cada seção que obtivermos e montamos a seção de produtos enviando esses dados. Também adicionamos o AluveryTheme
nesse arquivo, possibilitando a verificação de modo claro ou escuro.
As cores também foram modificadas no arquivo Color.kt
:
val Indigo400 = Color(0xFF5C6BC0)
val Indigo500 = Color(0xFF3F51B5)
val Indigo400Light = Color(0xFF5C6BC0)
Essa modificação foi refletida no tema, e por isso estamos utilizando o tema no ProductItem
. Então, no Theme.kt
, temos alterações nas cores do tema claro e escuro. Vocês podem analisar os detalhes dessas trocas no commit.
private val DarkColorPalette = darkColors(
primary = Indigo400,
primaryVariant = Indigo400Light,
secondary = Indigo500,
onSecondary = Color.White
)
private val LightColorPalette = lightColors(
primary = Indigo400,
primaryVariant = Indigo400Light,
secondary = Indigo500,
onSecondary = Color.White
Abaixo do arquivo de tema, notamos que apagamos todas as imagens que adicionamos ao projeto. Depois, no arquivo build.gradle
, reparamos que a versão do Compose mudou para a versão de lançamento, porque no curso passado usávamos a versão beta, que era a mais recente disponível. Além disso, como mudamos a versão do Android Studio, mudamos a versão dos plugins e do Kotlin.
Quanto às versões, eu quero destacar que, no Compose, temos a versão 1.2.1, mas no app/build.gradle
, que é o arquivo do compilador, temos a versão 1.3.0. Podemos entender porque isso acontece analisando a página da documentação onde mostra as versões do Compose.
Notamos que existem esses módulos fundamentais para o Compose funcionar, mas o compilador tem uma versão diferente. Isso acontece porque a evolução desse compilador é independente dos outros módulos e, portanto, é atualizado de forma independente, algo que precisamos nos atentar.
Sendo assim, a versão disponível para o compilador nem sempre será a mesma para os outros módulos. Pode acontecer de precisarmos usar versões diferentes ou as que estejam disponíveis em release.
Esses são todos os detalhes e, por ser muita informação, eu recomendo que acessem o commit para observarem detalhadamente. Provavelmente notarão diferença em relação a versão mais recente do Android Studio.
Por exemplo, abrindo o Android Studio e pressionando "Ctrl + Shift + N", abrimos o campo de busca. Pesquisaremos e abrirmos o arquivo build.gradle(Aluvery)
. Notamos que a versão da IDE é a 'com.android.application' version '7.3.0-rc01'
, referente ao plugin da versão Dolphin. No commit ainda aparece a versão do Chipmunk.
Essa pode ser a diferença, mas tranquilizem-se que eu tentarei manter o GitHub atualizado para passar esse conteúdo para vocês. Era isso que precisávamos ver, em seguida passaremos para nossa próxima tarefa.
Agora que conhecemos todas as modificações que feitas no nosso projeto, seguiremos com a primeira tarefa: obter as imagens representadas por URL para exibi-las no composable de imagem. Aprenderemos agora como fazer isso. Em passos, a ordem é a seguinte:
Image()
consegue apresentar, ou seja, painter, bitmap ou image vector.Assim conseguimos exibir o conteúdo. Notem que enumerando os passos, parece algo bem simples, mas esse tipo de código tem uma certa complexidade para funcionar perfeitamente. Isso significa que, ao invés de seguirmos esses passos manualmente, usaremos a biblioteca Coil.
A Coil é uma biblioteca bastante na comunidade Android, para fazer esses passos por nós, porque essa é uma abordagem comum, mas um pouco trabalhosa de se fazer manualmente. Essa biblioteca, inclusive, tem um módulo destinado ao Jetpack Compose, mas ela também é utilizada em sistema de views, caso se interessem.
Como nosso trabalho é com o Compose, utilizaremos o módulo de Compose da Coil. Para isso, precisamos adicionar essa biblioteca ao nosso projeto e, ao termos acesso a ela, teremos acesso ao composable AsyncImage
, que indica que será uma imagem assíncrona.
Com isso, ao definirmos uma imagem, o AsyncImage
fará a requisição e, enquanto estiver baixando, ele não exibirá o conteúdo esperado. Finalizado o processo, ele mostra a imagem. Portanto, o assíncrono significa que a tarefa será executada em paralelo à execução principal e, quando for finalizada, o resultado é exibido.
Dessa forma, o que precisamos agora é adicionar a dependência e usar esse composable. Começamos copiando o "io.coil-kt:coil-compose:2.2.0"
da documentação e voltamos para o Android Studio.
Pressionando "Ctrl + Shift + N", pesquisaremos e acessaremos o build.gradle(:app)
. Dentro desse arquivo, em dependencies
, adicionamos a dependência, atentando-se para que seja a versão 2.2.0. Caso tenha algo diferente da aula, pode estar relacionado à versão diferente que estão usando.
dependencies {
implementation "io.coil-kt:coil-compose:2.2.0"
//Código suprimido
Por fim, clicaremos no "Sync Now", que está no canto superior direito da janela de código. Após a sincronização, teremos disponível todo o conteúdo do Coil.
Feito isso, primeiramente precisamos voltar no ProductItem.kt
e, na linha 55, modificar o Image()
por AsyncImage()
. Notaremos que o código não irá compilar, porque ele não recebe painter, image vector ou bitmap, e sim um model
, que pode receber null
ou Any?
, que são dois tipos de informação.
Voltando para a documentação da biblioteca, observamos que a AsyncImage()
pode receber tanto uma String, que é a URL que queremos, ou a ImageRequest
, que é uma referência que possibilita um acesso mais preciso ao modo como a requisição será feito. No exemplo da documentação, o ImageRequest
traz dados de qual será a URL, se terá efeito para carregar e afins.
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data("https://example.com/image.jpg")
.crossfade(true)
.build(),
placeholder = painterResource(R.drawable.placeholder),
contentDescription = stringResource(R.string.description),
contentScale = ContentScale.Crop,
modifier = Modifier.clip(CircleShape)
)
Portanto, se precisarem definir mais detalhes de exibição da imagem, usarão o ImageRequest
, mas no caso deste curso, precisamos apenas enviar o endereço, ou seja, passaremos a String com a URL. Então voltaremos para IDE para adicionar o endereço. Como temos acesso ao produto e o produto tem acesso à URL, basta codar model = product.image
na linha 58.
{
AsyncImage(
// TODO: ajustar imagem do produto
model = product.image,
contentDescription = null,
Modifier
.size(imageSize)
.offset(y = imageSize / 2)
.clip(shape = CircleShape)
.align(Alignment.BottomCenter),
contentScale = ContentScale.Crop
)
}
Quando mudamos o código, recebemos a notificação que precisamos atualizar o preview, então usando o atalho "Ctrl + Shift + F5", percebemos que nosso preview não mostra mais o círculo com o placeholder como antes. Agora não há uma imagem dentro do projeto para fazer uma exibição, e a requisição não é feita enquanto o aplicativo não é executado.
Não se preocupem, porque existem uma técnica para verificar o preview e, após observamos o funcionamento do Coil, aprenderemos essa técnica. Feito isso, ao tentarmos executar o app, eu já adianto que teremos um problema, mas vou mostrar ele acontecendo para conseguirem identificá-lo.
Então vamos abrir o "Logcat", clicando no sexto botão da barra inferior do Android Studio. Algo interessante é que, a partir da versão Dolphin, o Logcat tem uma visualização diferente, e não apresenta apenas um texto branco.
Dica: A Jeniffer Bittencourt (Jeni), do Scuba Team Mobile, gravou um Alura+ sobre as Atualizações do Logcat na versão Dolphin do Android Studio!
Vamos limpar o Logcat, clicando no ícone de lixeira que fica no canto superior esquerdo do Logcat, e pressionar "Shift + F10" para executar o app. Com isso nossa aplicação é executada e quebra em seguida.
No Logcat somos notificados de uma exception indicando que ainda não temos a permissão de Internet, porque precisamos baixar o conteúdo da Internet. Como precisamos dessa permissão, precisamos realizar uma configuração no nosso aplicativo.
Para isso, buscaremos e abriremos o arquivo AndroidManifest.xml
e, antes do <application
, teremos acesso à algumas tags. Nesse caso usaremos a <uses-permission
.
A partir dela, teremos a configuração de diversas permissões que podemos solicitar durante a instalação do nosso aplicativo, dentre elas, a permissão de Internet, codando <uses-permission android:name="android.permission.INTERNET" />
. Com essa permissão, solucionamos o problema e podemos testar novamente.
Ao executarmos o código novamente e o aplicativo abre, mas as imagens não são carregadas imediatamente. Entretanto, após algum tempo as fotos dos produtos começam a aparecer. Isso acontece porque o download depende da Internet, então quanto melhor a conexão, mais rápido as imagens aparecem.
É muito importante verificarem, inclusive no material que vamos oferecer a vocês, se as imagens ainda estão disponíveis, porque se não estiverem, o conteúdo não será exibido no aplicativo. Com isso, notamos que não temos nenhum feedback, seja no preview ou quando a imagem não é carregada, o que pode aparecer estranho.
Quando as imagens dos meus cartões demoraram para carregar, observamos que o espaço ficou vazio, o que não é uma abordagem interessante. A pessoa que usar nosso app precisa saber que um espaço conterá uma imagem, que a imagem está carregando ou algo do gênero. O Coil oferece essas técnicas para nós, então aprenderemos como fazer isso.
Voltando para o ProductItem.kt()
, e acessando o AsyncImage
, teremos acesso a algumas técnicas, entre elas, o placeholder
. Portanto, o AsyncImage
placeholder como a imagem que aparecerá no preview e no lugar da imagem que não for carregada.
Nesse método placeholder
, podemos usar o painterResource(id = R.drawable.placeholder)
. Por esse motivo a image do placeholder foi mantida no projeto, porque utilizamos esse conteúdo.
{
AsyncImage(
// TODO: ajustar imagem do produto
model = product.image,
contentDescription = null,
Modifier
.size(imageSize)
.offset(y = imageSize / 2)
.clip(shape = CircleShape)
.align(Alignment.BottomCenter),
contentScale = ContentScale.Crop,
placeholder = painterResource(id = R.drawable.placeholder),
)
}
Pressionando "Ctrl + Shift + F5", visualizamos essa mudança no preview, já que o placeholder agora está no lugar da imagem. Sendo assim, vamos executar aplicativo de novo para descobrir se ele funciona como esperado. Assim notamos que ele carregou inicialmente o placeholder, por padrão e depois as imagens.
Nessa segunda execução do aplicativo, minha Internet foi mais ágil, então não foi possível observar muito bem o placeholder, mas vocês podem voltar o vídeo e até diminuir a velocidade de reprodução para observar isso melhor. Sendo assim, enquanto ele não carregar a imagem, ele terá o placeholder disponível.
Dica: O placeholder pode ser a imagem que vocês quiserem, então caso queiram substituir a imagem cinza por outra imagem, como uma animação de carregamento, fiquem à vontade!
Era isso que precisávamos fazer, sendo assim, podemos apagar o //TODO
. Agora nossas imagens são carregadas de maneira dinâmica!
Um último ponto que quero ressaltar é o que Coil é uma ferramenta muito poderosa para carregar imagens dinâmicas, então recomendo que deem uma olhada na documentação do Coil. Se tiver algo mais que queiram fazer, podem adicionar ao projeto e configurar, mas quero deixar claro que o objetivo do curso não será trabalhar com as possibilidades do Coil.
Por isso recomendo a vocês analisarem a documentação para descobrir se existe algo mais que queiram adicionar. Era isso que queria passar para vocês neste vídeo!
O curso Jetpack Compose: utilizando Lazy Layout e estados possui 172 minutos de vídeos, em um total de 59 atividades. Gostou? Conheça nossos outros cursos de Android em Mobile, ou leia nossos artigos de Mobile.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.