Adicionar o Compose a um app baseado em visualização

1. Antes de começar

Desde o início, o Jetpack Compose foi criado com interoperabilidade de visualização, o que significa que ele e o sistema de visualização podem compartilhar recursos e trabalhar lado a lado para mostrar a interface. Essa funcionalidade permite adicionar o Compose ao seu app já existente baseado em visualização. Isso significa que o Compose e as visualizações podem coexistir na base de código até que todo o app esteja totalmente no Compose.

Neste codelab, você vai mudar o item de lista baseado em visualização no app Juice Tracker para usar o Compose. Você pode converter o restante das visualizações do Juice Tracker por conta própria, se quiser.

Se você tem um app que usa uma interface baseada em visualização, talvez não queira reprogramar toda a interface de uma vez. Este codelab ajuda a converter uma única visualização de uma interface baseada em visualização em um elemento do Compose.

Pré-requisitos

  • Conhecer a interface baseada em visualização.
  • Saber criar um app usando uma interface baseada em visualização.
  • Ter experiência com a sintaxe do Kotlin, incluindo lambdas.
  • Saber criar um app no Jetpack Compose.

O que você vai aprender

  • Como adicionar o Compose a uma tela criada com visualizações do Android.
  • Como visualizar um elemento combinável adicionado ao app baseado em visualização.

O que você vai criar

  • Você vai converter um item de lista baseado em visualização para o Compose no app Juice Tracker.

2. Visão geral do app inicial

Este codelab usa o código de solução do app Juice Tracker mostrado em Criar um app Android com visualizações como o código inicial. O app inicial já salva dados usando a biblioteca de persistência Room. O usuário pode adicionar informações sobre sucos ao banco de dados do app, como nome, descrição, cor e nota.

36bd5542e97fee2e.png

Neste codelab, você vai converter o item de lista baseado em visualização para o Compose.

Item da lista com detalhes do suco

Fazer o download do código inicial para este codelab

Para começar, faça o download do código inicial:

Outra opção é clonar o repositório do GitHub:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker.git
$ cd basic-android-kotlin-compose-training-juice-tracker
$ git checkout views

Procure o código no repositório do GitHub do JuiceTracker (link em inglês).

3. Adicionar a biblioteca do Jetpack Compose

Não se esqueça, Compose e Views podem existir juntos em uma determinada tela. Você pode ter alguns elementos da interface no Compose e outros no sistema de visualização. Por exemplo, você pode ter apenas a lista no Compose e o restante da tela no sistema de visualização.

Conclua as etapas a seguir para adicionar a biblioteca do Compose ao app Juice Tracker.

  1. Abra o Juice Tracker no Android Studio.
  2. Abra o build.gradle.kts do app.
  3. No bloco buildFeatures, adicione uma flag compose = true.
buildFeatures {
    //...
    // Enable Jetpack Compose for this module
    compose = true
}

Essa flag permite que o Android Studio funcione com o Compose. Você não realizou essa etapa nos codelabs anteriores, porque o Android Studio gera esse código automaticamente quando você cria um novo projeto de modelo do Compose no Android Studio.

  1. Abaixo de buildFeatures, adicione o bloco composeOptions.
  2. No bloco, defina kotlinCompilerExtensionVersion como "1.5.1" para definir a versão do compilador Kotlin.
composeOptions {
    kotlinCompilerExtensionVersion = "1.5.1"
}
  1. Na seção dependencies, adicione dependências do Compose. Você precisa das dependências a seguir para adicionar o Compose a um app baseado em visualização. Essas dependências ajudam a integrar o Compose à atividade, adicionar a biblioteca de componentes de design do Compose, oferecer suporte aos temas do Jetpack e fornecer ferramentas para melhorar o suporte ao ambiente de desenvolvimento integrado.
dependencies {
    implementation(platform("androidx.compose:compose-bom:2023.06.01"))
    // other dependencies
    // Compose
    implementation("androidx.activity:activity-compose:1.7.2")
    implementation("androidx.compose.material3:material3")
    implementation("com.google.accompanist:accompanist-themeadapter-material3:0.28.0")

    debugImplementation("androidx.compose.ui:ui-tooling")
}

Adicionar ComposeView

Uma ComposeView é uma visualização do Android que pode hospedar conteúdo da interface do Jetpack Compose. Use setContent para fornecer a função combinável do conteúdo para a visualização.

  1. Abra o layout/list_item.xml e confira a prévia na guia Split.

Ao final deste codelab, você vai substituir essa visualização por um elemento combinável.

7a2df616fde1ec56.png

  1. No JuiceListAdapter.kt, remova ListItemBinding de todos os lugares. Na classe JuiceListViewHolder, substitua binding.root por composeView.
import androidx.compose.ui.platform.ComposeView

class JuiceListViewHolder(
    private val onEdit: (Juice) -> Unit,
    private val onDelete: (Juice) -> Unit
): RecyclerView.ViewHolder(composeView)
  1. Na pasta onCreateViewHolder(), atualize a função return() para que ela fique como este código:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): JuiceListViewHolder {
   return JuiceListViewHolder(
       ComposeView(parent.context),
       onEdit,
       onDelete
   )
}
  1. Na classe JuiceListViewHolder, exclua todas as variáveis private e remova todo o código da função bind(). A classe JuiceListViewHolder agora terá esta aparência:
class JuiceListViewHolder(
    private val onEdit: (Juice) -> Unit,
    private val onDelete: (Juice) -> Unit
) : RecyclerView.ViewHolder(composeView) {

   fun bind(juice: Juice) {

   }
}
  1. Agora, é possível excluir as importações com.example.juicetracker.databinding.ListItemBinding e android.view.LayoutInflater.
// Delete
import com.example.juicetracker.databinding.ListItemBinding
import android.view.LayoutInflater
  1. Exclua o arquivo layout/list_item.xml.
  2. Selecione OK na caixa de diálogo Delete.

2954ed44c5827571.png

4. Adicionar uma função combinável

Agora, crie um elemento combinável que emita o item de lista. A função combinável usa Juice e duas funções de callback para editar e excluir o item de lista.

  1. No JuiceListAdapter.kt, após a definição da classe JuiceListAdapter, crie uma função combinável chamada ListItem().
  2. Faça com que a função ListItem() aceite o objeto Juice e um callback lambda para exclusão.
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier

@Composable
fun ListItem(
    input: Juice,
    onDelete: (Juice) -> Unit,
    modifier: Modifier = Modifier
) {
}

Observe a prévia do item de lista que você quer criar. Ela mostra um ícone e detalhes do suco, além de um botão de exclusão. Você vai implementar esses componentes em breve.

4ec7f82371c6bc15.png

Criar o elemento combinável do ícone de suco

  1. No JuiceListAdapter.kt, depois do elemento combinável ListItem(), crie outra função combinável chamada JuiceIcon() que usa uma color e um Modifier.
@Composable
fun JuiceIcon(color: String, modifier: Modifier = Modifier) {

}
  1. Na função JuiceIcon(), adicione variáveis para a color e a descrição do conteúdo, conforme mostrado no seguinte código:
@Composable
fun JuiceIcon(color: String, modifier: Modifier = Modifier) {
   val colorLabelMap = JuiceColor.values().associateBy { stringResource(it.label) }
   val selectedColor = colorLabelMap[color]?.let { Color(it.color) }
   val juiceIconContentDescription = stringResource(R.string.juice_color, color)

}

Usando as variáveis colorLabelMap e selectedColor, você vai recuperar o recurso de cores associado à seleção do usuário.

  1. Adicione um layout Box para mostrar dois ícones ic_juice_color e ic_juice_clear, um sobre o outro. O ícone ic_juice_color tem uma tonalidade e está alinhado ao centro.
import androidx.compose.foundation.layout.Box

Box(
   modifier.semantics {
       contentDescription = juiceIconContentDescription
   }
) {
   Icon(
       painter = painterResource(R.drawable.ic_juice_color),
       contentDescription = null,
       tint = selectedColor ?: Color.Red,
       modifier = Modifier.align(Alignment.Center)
   )
   Icon(painter = painterResource(R.drawable.ic_juice_clear), contentDescription = null)
}

Como você já conhece a implementação de um elemento combinável, os detalhes sobre essa etapa não são fornecidos.

  1. Adicione uma função para visualizar JuiceIcon(). Transmita a cor como Yellow.
import androidx.compose.ui.tooling.preview.Preview

@Preview
@Composable
fun PreviewJuiceIcon() {
    JuiceIcon("Yellow")
}

c016198f82a5d199.png

Criar elementos combináveis de detalhes do suco

No JuiceListAdapter.kt, é necessário adicionar outra função combinável para mostrar os detalhes do suco. Você também precisa de um layout de coluna para mostrar dois elementos combináveis Text para o nome e a descrição, além de um indicador de nota. Para isso, siga estas etapas:

  1. Adicione uma função combinável com o nome JuiceDetails(), que usa um objeto Juice e um Modifier, além de um elemento combinável de texto para o nome e outro para a descrição do suco, conforme mostrado no código abaixo:
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.ui.text.font.FontWeight

@Composable
fun JuiceDetails(juice: Juice, modifier: Modifier = Modifier) {
   Column(modifier, verticalArrangement = Arrangement.Top) {
       Text(
           text = juice.name,
           style = MaterialTheme.typography.h5.copy(fontWeight = FontWeight.Bold),
       )
       Text(juice.description)
       RatingDisplay(rating = juice.rating, modifier = Modifier.padding(top = 8.dp))
   }
}
  1. Para resolver o erro de referência, crie uma função combinável chamada RatingDisplay().

536030e2ecb01a4e.png

No sistema de visualização, você tem uma RatingBar para mostrar a barra de nota a seguir. Como o Compose não tem um elemento combinável da barra de nota, é necessário implementar esse elemento do zero.

  1. Defina a função RatingDisplay() para mostrar as estrelas de acordo com a nota. Essa função combinável mostra o número de estrelas com base na classificação.

Barra de classificação com quatro estrelas

import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.pluralStringResource

@Composable
fun RatingDisplay(rating: Int, modifier: Modifier = Modifier) {
   val displayDescription = pluralStringResource(R.plurals.number_of_stars, count = rating)
   Row(
       // Content description is added here to support accessibility
       modifier.semantics {
           contentDescription = displayDescription
       }
   ) {
       repeat(rating) {
           // Star [contentDescription] is null as the image is for illustrative purpose
           Image(
               modifier = Modifier.size(32.dp),
               painter = painterResource(R.drawable.star),
               contentDescription = null
           )
       }
   }
}

Para criar o drawable de estrela no Compose, é necessário criar o recurso de vetor de estrelas.

  1. No painel Project, clique com o botão direito do mouse em drawable > New > Vector Asset.

201431ca3d212113.png

  1. Na caixa de diálogo do Asset Studio, procure um ícone de estrela. Selecione o ícone de estrela preenchida.

9956ed24371f61ac.png

5a79bac6f3982b72.png

  1. Mude o valor de cor da estrela para 625B71.

44d4bdfa93bc369a.png

  1. Clique em Next > Finish.
  2. Um drawable aparece na pasta res/drawable.

64bb8d9f05019229.png

  1. Adicione um elemento combinável de prévia para JuiceDetails.
@Preview
@Composable
fun PreviewJuiceDetails() {
    JuiceDetails(Juice(1, "Sweet Beet", "Apple, carrot, beet, and lemon", "Red", 4))
}

Com descrição do nome do suco e barra de nota

Criar um elemento combinável do botão de exclusão

  1. No JuiceListAdapter.kt, adicione outra função combinável chamada DeleteButton(), que usa uma função de callback de lambda e um modificador.
  2. Defina a lambda como o argumento onClick e transmita o Icon() conforme mostrado no seguinte código:
import androidx.compose.ui.res.painterResource
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton

@Composable
fun DeleteButton(onDelete: () -> Unit, modifier: Modifier = Modifier) {
    IconButton(
        onClick = { onDelete() },
        modifier = modifier
    ) {
        Icon(
            painter = painterResource(R.drawable.ic_delete),
            contentDescription = stringResource(R.string.delete)
        )
    }
}
  1. Adicione uma função de prévia para o botão de exclusão.
@Preview
@Composable
fun PreviewDeleteIcon() {
    DeleteButton({})
}

Prévia do ícone de exclusão no Android Studio

5. Implementar a função ListItem

Agora que você tem todos os elementos combináveis necessários para mostrar o item de lista, eles podem ser organizados em um layout. Observe a função ListItem() definida na etapa anterior.

@Composable
fun ListItem(
   input: Juice,
   onEdit: (Juice) -> Unit,
   onDelete: (Juice) -> Unit,
   modifier: Modifier = Modifier
) {
}

No JuiceListAdapter.kt, conclua as etapas a seguir para implementar a função ListItem().

  1. Adicione um layout Row dentro do lambda Mdc3Theme {}.
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import com.google.accompanist.themeadapter.material3.Mdc3Theme

Mdc3Theme {
   Row(
       modifier = modifier,
       horizontalArrangement = Arrangement.SpaceBetween
   ) {

   }
}
  1. No lambda Row, chame os três elementos combináveis (JuiceIcon, JuiceDetails e DeleteButton) que você criou como elementos filhos.
JuiceIcon(input.color)
JuiceDetails(input, Modifier.weight(1f))
DeleteButton({})

A transmissão do Modifier.weight(1f) para o elemento combinável JuiceDetails() garante que os detalhes do suco ocupem o espaço horizontal restante depois de medir os elementos filhos sem peso.

  1. Transmita a lambda onDelete(input) e o modificador com alinhamento superior como parâmetros para o elemento combinável DeleteButton.
DeleteButton(
   onDelete = {
       onDelete(input)
   },
   modifier = Modifier.align(Alignment.Top)
)
  1. Crie uma função de prévia para o elemento combinável ListItem.
@Preview
@Composable
fun PreviewListItem() {
   ListItem(Juice(1, "Sweet Beet", "Apple, carrot, beet, and lemon", "Red", 4), {})
}

Prévia do item de lista do Android Studio com detalhes do suco de beterraba

  1. Vincule o elemento combinável ListItem ao armazenador de visualização. Chame onEdit(input) na função lambda clickable() para abrir a caixa de diálogo de edição quando houver um clique no item da lista.

Na classe JuiceListViewHolder, dentro da função bind(), você precisa hospedar o elemento combinável. Use a ComposeView, que é uma visualização do Android que pode hospedar conteúdo da interface do Compose utilizando o método setContent.

fun bind(input: Juice) {
    composeView.setContent {
        ListItem(
            input,
            onDelete,
            modifier = Modifier
                .fillMaxWidth()
                .clickable {
                    onEdit(input)
                }
                .padding(vertical = 8.dp, horizontal = 16.dp),
       )
   }
}
  1. Execute o app e adicione seu suco favorito. Observe o item de lista do Compose.

aadccf32ab952d0f.png. 8aa751f4cf63bf98.png

Parabéns! Você acabou de criar seu primeiro app de interoperabilidade do Compose que usa elementos do Compose em um app baseado em visualização.

6. Acessar o código da solução

Para baixar o código do codelab concluído, use estes comandos git:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker.git
$ cd basic-android-kotlin-compose-training-juice-tracker
$ git checkout views-with-compose

Se preferir, você pode baixar o repositório como um arquivo ZIP, descompactar e abrir no Android Studio.

Se você quiser conferir o código da solução, acesse o GitHub (link em inglês).

7. Saiba mais

Documentação do desenvolvedor Android

Codelab [intermediário]