1. Antes de começar
O Relay (link em inglês) é um kit de ferramentas que ajuda equipes a projetar componentes de interface no Figma e usá-los diretamente em projetos do Jetpack Compose. Você não vai precisar mais de especificações longas de design nem ciclos de controle de qualidade para criar interfaces incríveis para Android com mais rapidez.
Neste codelab, vamos ensinar você a integrar pacotes de interface do Relay ao seu processo de desenvolvimento do Compose. Vamos focar nas técnicas de integração, e não no fluxo de trabalho completo. Para saber mais, confira o Tutorial básico do Relay.
Pré-requisitos
- Experiência básica com o Compose. Conclua o codelab de Noções básicas do Jetpack Compose caso ainda não tenha feito isso.
- Experiência com a sintaxe do Kotlin.
O que você vai aprender
- Como importar pacotes de interface.
- Como integrar pacotes de interface com a navegação e a arquitetura de dados.
- Como unir pacotes de interface com a lógica do controlador.
- Como mapear estilos do Figma para o tema do Compose.
- Como substituir pacotes de interface com os combináveis existentes no código gerado.
O que você vai criar
- Um design de app realista com base nos pacotes do Relay fornecidos por um designer. O app se chama Reflect e serve para promover mindfulness e bons hábitos no dia a dia. Ele contém uma coleção de itens que podem ser usados para criar lembretes e acompanhar suas atividades diárias e oferece uma interface para os gerenciar ou adicionar outros. O app vai ficar parecido com esta imagem:
O que é necessário
- A versão mais recente do Android Studio.
- Uma conta no Figma gratuita e um token de acesso pessoal (links em inglês).
2. Começar a configuração
Buscar o código
Para encontrar o código deste codelab, realize uma destas ações:
- Clone o repositório
relay-codelabs
(link em inglês) do GitHub:
$ git clone https://github.com/googlecodelabs/relay-codelabs
- Navegue até o repositório
relay-codelabs
(link em inglês) no GitHub, selecione uma ramificação e clique em Code > Download zip, depois descompacte o arquivo ZIP transferido.
Em ambos os casos, a ramificação main
contém o código inicial e a end
contém o código da solução.
Instalar o plug-in do Relay para o Android Studio
Se ainda não tiver o plug-in do Relay para o Android Studio, siga estas etapas:
- No Android Studio, clique em Settings > Plugins.
- Digite
Relay for Android Studio
na caixa de texto. - Clique em Install na extensão mostrada nos resultados da pesquisa.
- Se uma caixa de diálogo Third-party plugins privacy note aparecer, clique em Accept.
- Clique em OK > Restart.
- Se uma caixa de diálogo Confirm exit aparecer, clique em Exit.
Conectar o Android Studio ao Figma
O Relay extrai pacotes de interface com a API Figma. Para usá-la, você precisa de uma conta gratuita no Figma e um token de acesso pessoal (links em inglês), como mencionamos na seção O que é necessário.
Caso ainda não tenha conectado o Android Studio ao Figma, siga estas etapas:
- Na conta do Figma, clique no ícone do perfil na parte de cima da página e selecione Settings.
- Na seção Personal access tokens, insira uma descrição na caixa de texto e pressione
Enter
(oureturn
no macOS). Um token será gerado. - Clique em Copy this token.
- No Android Studio, selecione Tools > Relay Settings. Uma caixa de diálogo Relay settings vai aparecer.
- Na caixa de texto Figma Access Token, cole o token de acesso e clique em OK. Seu ambiente está configurado.
3. Revisar o design do app
Trabalhamos com um designer para ajudar a definir a cor, o layout, a tipografia e o comportamento do app Reflect. Criamos os designs conforme as convenções do Material Design 3 para que o app funcione facilmente com os componentes e temas do Material Design.
Revisar a tela inicial
A tela inicial mostra uma lista de itens definidos pelo usuário. Ela também tem affordances para mudar o dia ativo e criar outros itens.
No Figma, nosso designer dividiu a tela entre vários componentes, definiu as APIs e as empacotou com o Plug-in Relay para Figma (link em inglês). Você pode importar os componentes já empacotados para seu projeto no Android Studio.
Revisar a tela adicionar/editar
Essa tela permite que os usuários adicionem ou editem os itens. O formulário mostrado é um pouco diferente com base no tipo de item.
De forma semelhante, a tela é dividida entre vários componentes empacotados.
Revisar o tema
As cores e a tipografia para este design são implementadas como estilos do Figma com base nos nomes de token do Material Design 3. Isso proporciona uma interoperabilidade melhor entre os temas do Compose e os componentes do Material Design.
4. Importar pacotes de interface
Acessar o link da origem do Figma
Antes de importar pacotes de interface para o projeto, é necessário fazer o upload da origem do design para o Figma.
Siga estas etapas para extrair o link da origem do Figma:
- No Figma, clique em Import file e selecione o arquivo
ReflectDesign.fig
localizado na pasta do projeto do codelab. - Clique nele com o botão direito do mouse e selecione Copy link. Você vai precisar dele na próxima seção.
Importar pacotes de interface para o projeto
- No Android Studio, abra o projeto
./CompleteAppCodelab
. - Selecione File > New > Import UI Packages. Uma caixa de diálogo Import UI Packages vai aparecer.
- Na caixa de diálogo Figma source URL, cole o URL copiado na seção anterior.
- Na caixa de texto App theme, insira
com.google.relay.example.reflect.ui.theme.ReflectTheme
. Isso garante que as visualizações geradas usem o tema personalizado. - Clique em Next. Uma prévia dos pacotes da interface do arquivo vai aparecer.
- Clique em Create. Os pacotes serão importados para seu projeto.
- Navegue até a guia Project e clique na seta
ao lado da pasta
ui-packages
.
- Clique na seta
ao lado de uma das pastas de pacotes. Ela contém um arquivo de origem
JSON
e dependências de recursos. - Abra o arquivo de origem
JSON
. O módulo do Relay mostra uma prévia do pacote e a API dele.
Criar e gerar código
- Na parte de cima do Android Studio, clique em
Make project. O código gerado para cada pacote é adicionado ao arquivo
java/com.google.relay.example.reflect
. Os combináveis gerados contêm todas as informações de layout e estilo do design do Figma. - Caso seja necessário, clique em Split para conferir os painéis de código e de prévia lado a lado.
- Abra o arquivo
range/Range.kt
. As prévias do Compose são criadas para cada variação de componente.
5. Integrar componentes
Nesta seção, detalhamos o código gerado para o botão do item.
- No Android Studio, abra o arquivo
com/google/relay/example/reflect/switch/Switch.kt
.
Switch.kt (código gerado)
/**
* This composable was generated from the switch UI Package.
* Generated code; don't edit directly.
*/
@Composable
fun Switch(
modifier: Modifier = Modifier,
isChecked: Boolean = false,
isPressed: Boolean = false,
emoji: String = "",
title: String = ""
) {
TopLevel(modifier = modifier) {
if (isChecked) {
ActiveOverlay(modifier = Modifier.rowWeight(1.0f).columnWeight(1.0f)) {}
}
if (isPressed) {
State(modifier = Modifier.rowWeight(1.0f).columnWeight(1.0f)) {}
}
TopLevelSynth {
Label(modifier = Modifier.rowWeight(1.0f)) {
Emoji(emoji = emoji)
Title(
title = title,
modifier = Modifier.rowWeight(1.0f)
)
}
if (isChecked) {
Checkmark {
Vector(modifier = Modifier.rowWeight(1.0f).columnWeight(1.0f))
}
}
}
}
}
- Confira se:
- Todo o layout e estilo do design do Figma foram gerados.
- Os subcomponentes estão divididos em combináveis separados.
- Prévias de elementos combináveis foram geradas para todas as variações de design.
- Os estilos de cor e tipografia foram fixados no código. Você pode corrigir isso depois.
Inserir o item
- No Android Studio, abra o arquivo
java/com/google/relay/example/reflect/ui/components/TrackerControl.kt
. Ele fornece a lógica de dados e interação para items de hábitos. No momento, o componente gera dados brutos usando o modelo do item.
- Importe o pacote
com.google.relay.example.reflect.switch.Switch
para o arquivo. - Crie um bloco
when
que recebe o campotrackerData.tracker.type
como parâmetro. - No corpo do bloco
when
, chame a funçãoSwitch()
Composable
quando o tipo forTrackerType.BOOLEAN
.
O código ficará assim:
TrackerControl.kt
when (trackerData.tracker.type) {
TrackerType.BOOLEAN ->
Switch(
title = trackerData.tracker.name,
emoji = trackerData.tracker.emoji
)
else ->
Text(trackerData.tracker.toString())
}
- Recrie o projeto. A página inicial vai renderizar o botão corretamente de acordo com o que foi projetado com dados reais.
6. Adicionar estado e interação
Os pacotes de interface não têm estado. Eles simplesmente renderizam o resultado dos parâmetros transmitidos. No entanto, apps reais precisam de interação e estado. Os gerenciadores de interações podem ser transferidos para elementos combináveis como qualquer outro parâmetro, mas onde armazenamos o estado manipulado por eles? Como podemos evitar a transferência do mesmo gerenciador a todas as instâncias? Como abstrair composições de pacotes em combináveis que podem ser usados novamente? Nesses casos, recomendamos unir os pacotes gerados em uma função Composable
personalizada.
Unir pacotes de interface em uma função Composable
do controlador
A união de pacotes de interface em funções Composable
do controlador ajuda a personalizar a apresentação ou a lógica de negócios e, se necessário, gerenciar o estado local. Os designers ainda podem atualizar o pacote original no Figma sem precisar pedir para você atualizar o código do wrapper.
Para criar um controlador para o botão do item, siga estas etapas:
- No Android Studio, abra o arquivo
java/com/google/relay/example/reflect/ui/components/SwitchControl.kt
. - Na função
SwitchControl()
Composable
, transmita estes parâmetros:
trackerData
: um objetoTrackerData
.modifier
: um objeto de decorador.onLongClick
: um callback de interação para ativar a ação "tocar e manter pressionado" nos itens para os editar ou excluir.
modifier
- Transmita um modificador
combinedClickable
para a funçãoSwitch()
para processar as ações de "clicar" e "tocar e manter pressionado". - Transmita valores do objeto
TrackerData
para a funçãoSwitch()
, incluindo o métodoisToggled()
.
A função SwitchControl()
concluída vai ficar parecida com este snippet de código:
SwitchControl.kt
package com.google.relay.example.reflect.ui.components
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.google.relay.example.reflect.model.Tracker
import com.google.relay.example.reflect.model.TrackerData
import com.google.relay.example.reflect.model.TrackerType
import com.google.relay.example.reflect.switch.Switch
/*
* A component for controlling switch-type trackers.
*
* SwitchControl is responsible for providing interaction and state management to the stateless
* composable [Switch] generated by Relay. [onLongClick] provides a way for callers to supplement
* the control's intrinsic interactions with, for example, a context menu.
*/
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun SwitchControl(
trackerData: TrackerData,
modifier: Modifier = Modifier,
onLongClick: (() -> Unit)? = null,
) {
Switch(
modifier
.height(64.dp)
.clip(shape = RoundedCornerShape(size = 32.dp))
.combinedClickable(onLongClick = onLongClick) {
trackerData.toggle()
},
emoji = trackerData.tracker.emoji,
title = trackerData.tracker.name,
isChecked = trackerData.isToggled(),
)
}
@Preview
@Composable
fun SwitchControllerPreview() {
val data = TrackerData(
Tracker(
emoji = "🍕",
name = "Ate Pizza",
type = TrackerType.BOOLEAN
)
)
SwitchControl(data)
}
- No arquivo
TrackerControl.kt
, remova oSwitch
importado e substitua a funçãoSwitch()
com uma chamada para a funçãoSwitchControl()
. - Adicione casos para as constantes
TrackerType.RANGE
eTrackerType.COUNT
do enumerador.
O bloco when
concluído vai ficar parecido com este snippet de código:
SwitchControl.kt
when (trackerData.tracker.type) {
TrackerType.BOOLEAN ->
SwitchControl(
trackerData = trackerData,
onLongClick = { expanded = true },
)
TrackerType.RANGE ->
RangeControl(
trackerData = trackerData,
onLongClick = { expanded = true },
)
TrackerType.COUNT ->
ValueControl(
trackerData = trackerData,
onLongClick = { expanded = true },
)
}
- Recrie o projeto. Agora, você pode mostrar e interagir com os itens. A tela inicial está pronta.
7. Mapear componentes já criados
O Relay ajuda os desenvolvedores a personalizar o código gerado substituindo pacotes de interface por elementos combináveis prontos. É uma ótima maneira de gerar componentes novos ou até mesmo personalizar sistemas de design no seu código.
Mapear um campo de texto
A imagem abaixo mostra o design do Switch Tracker Editor
na caixa de diálogo Add/edit tracker (adicionar editar item):
Nosso designer usou um ReflectTextField
. Já temos uma implementação para esse componente no código criado com base nos campos de texto do Material Design 3. O Figma não oferece suporte nativo a campos de texto, então o modo padrão gerado pelo Relay só tem a aparência do design, não sendo um controle funcional.
Para substituir a implementação real por esse elemento, é necessário criar duas coisas: um pacote de interface de campo de texto e um arquivo de mapeamento. Felizmente, nosso designer já empacotou os componentes do sistema de design no Figma e usou um componente de botão para o Tracker Editor
. Por padrão, esse pacote aninhado é gerado como dependência do pacote da barra de configurações, mas você pode usar o mapeamento de componentes para trocá-lo.
Criar um arquivo de mapeamento
O plug-in do Relay para o Android Studio oferece um atalho para a criação de arquivos de mapeamento de componentes.
Para criar um deles, siga estas etapas:
- No Android Studio, clique com o botão direito do mouse no pacote de interface
text_field
e selecione Generate mapping file.
- Insira este código no arquivo:
text_field.json
{
"target": "ReflectTextField",
"package": "com.google.relay.example.reflect.ui.components",
"generatePreviews": false
}
Os arquivos de mapeamento de componentes identificam um pacote e uma classe de destino do Compose, além de uma coleção opcional de objetos fieldMapping
. Esses mapeamentos de campo possibilitam a transformação de parâmetros de pacotes nos parâmetros esperados do Compose. Nesse caso, as APIs são idênticas, então você só precisa identificar a classe de destino.
- Recrie o projeto.
- No arquivo
trackersettings/
TrackerSettings.kt
, localize a função combinávelTitleFieldStyleFilledStateEnabledTextConfigurationsInputText()
que foi gerada. Ela inclui um componenteReflectTextField
gerado.
TrackerSettings.kt (gerado)
@Composable
fun TitleFieldStyleFilledStateEnabledTextConfigurationsInputText(
onTitleChanged: (String) -> Unit,
title: String,
modifier: Modifier = Modifier
) {
ReflectTextField(
onChange = onTitleChanged,
labelText = "Title",
leadingIcon = "search",
trailingIcon = "cancel",
supportingText = "Supporting text",
inputText = title,
state = State.Enabled,
textConfigurations = TextConfigurations.InputText,
modifier = modifier.requiredHeight(56.0.dp)
)
}
8. Mapear para temas do Compose
Por padrão, o Relay gera valores literais para cores e tipografia. Isso garante a precisão da translação, mas impede que os componentes usem o sistema de temas do Compose. Isso fica evidente quando abrimos o app no modo escuro:
O componente de navegação que mostra o dia fica quase invisível e as cores estão erradas. Para corrigir isso, use o recurso de mapeamento de estilos do Relay para vincular os estilos do Figma aos tokens de tema do Compose no código gerado. Isso aumenta a consistência visual entre o Relay e os componentes do Material Design 3, além de ativar o suporte ao tema escuro.
Criar um arquivo de mapeamento de estilos
- No Android Studio, navegue até a pasta
src/main/ui-package-resources/style-mappings
e crie um arquivofigma_styles.json
contendo este código:
figma_styles.json
{
"figma": {
"colors": {
"Reflect Light/background": "md.sys.color.background",
"Reflect Dark/background": "md.sys.color.background",
"Reflect Light/on-background": "md.sys.color.on-background",
"Reflect Dark/on-background": "md.sys.color.on-background",
"Reflect Light/surface": "md.sys.color.surface",
"Reflect Dark/surface": "md.sys.color.surface",
"Reflect Light/on-surface": "md.sys.color.on-surface",
"Reflect Dark/on-surface": "md.sys.color.on-surface",
"Reflect Light/surface-variant": "md.sys.color.surface-variant",
"Reflect Dark/surface-variant": "md.sys.color.surface-variant",
"Reflect Light/on-surface-variant": "md.sys.color.on-surface-variant",
"Reflect Dark/on-surface-variant": "md.sys.color.on-surface-variant",
"Reflect Light/primary": "md.sys.color.primary",
"Reflect Dark/primary": "md.sys.color.primary",
"Reflect Light/on-primary": "md.sys.color.on-primary",
"Reflect Dark/on-primary": "md.sys.color.on-primary",
"Reflect Light/primary-container": "md.sys.color.primary-container",
"Reflect Dark/primary-container": "md.sys.color.primary-container",
"Reflect Light/on-primary-container": "md.sys.color.on-primary-container",
"Reflect Dark/on-primary-container": "md.sys.color.on-primary-container",
"Reflect Light/secondary-container": "md.sys.color.secondary-container",
"Reflect Dark/secondary-container": "md.sys.color.secondary-container",
"Reflect Light/on-secondary-container": "md.sys.color.on-secondary-container",
"Reflect Dark/on-secondary-container": "md.sys.color.on-secondary-container",
"Reflect Light/outline": "md.sys.color.outline",
"Reflect Dark/outline": "md.sys.color.outline",
"Reflect Light/error": "md.sys.color.error",
"Reflect Dark/error": "md.sys.color.error"
},
"typography": {
"symbols": {
"Reflect/headline/large": "md.sys.typescale.headline-large",
"Reflect/headline/medium": "md.sys.typescale.headline-medium",
"Reflect/headline/small": "md.sys.typescale.headline-small",
"Reflect/title/large": "md.sys.typescale.title-large",
"Reflect/title/medium": "md.sys.typescale.title-medium",
"Reflect/title/small": "md.sys.typescale.title-small",
"Reflect/body/large": "md.sys.typescale.body-large",
"Reflect/body/medium": "md.sys.typescale.body-medium",
"Reflect/body/small": "md.sys.typescale.body-small",
"Reflect/label/large": "md.sys.typescale.label-large",
"Reflect/label/medium": "md.sys.typescale.label-medium",
"Reflect/label/small": "md.sys.typescale.label-small"
},
"subproperties": {
"fontFamily": "font",
"fontWeight": "weight",
"fontSize": "size",
"letterSpacing": "tracking",
"lineHeightPx": "line-height"
}
}
},
"compose": {
"colors": {
"md.sys.color.background": "MaterialTheme.colorScheme.background",
"md.sys.color.error": "MaterialTheme.colorScheme.error",
"md.sys.color.error-container": "MaterialTheme.colorScheme.errorContainer",
"md.sys.color.inverse-on-surface": "MaterialTheme.colorScheme.inverseOnSurface",
"md.sys.color.inverse-surface": "MaterialTheme.colorScheme.inverseSurface",
"md.sys.color.on-background": "MaterialTheme.colorScheme.onBackground",
"md.sys.color.on-error": "MaterialTheme.colorScheme.onError",
"md.sys.color.on-error-container": "MaterialTheme.colorScheme.onErrorContainer",
"md.sys.color.on-primary": "MaterialTheme.colorScheme.onPrimary",
"md.sys.color.on-primary-container": "MaterialTheme.colorScheme.onPrimaryContainer",
"md.sys.color.on-secondary": "MaterialTheme.colorScheme.onSecondary",
"md.sys.color.on-secondary-container": "MaterialTheme.colorScheme.onSecondaryContainer",
"md.sys.color.on-surface": "MaterialTheme.colorScheme.onSurface",
"md.sys.color.on-surface-variant": "MaterialTheme.colorScheme.onSurfaceVariant",
"md.sys.color.on-tertiary": "MaterialTheme.colorScheme.onTertiary",
"md.sys.color.on-tertiary-container": "MaterialTheme.colorScheme.onTertiaryContainer",
"md.sys.color.outline": "MaterialTheme.colorScheme.outline",
"md.sys.color.primary": "MaterialTheme.colorScheme.primary",
"md.sys.color.primary-container": "MaterialTheme.colorScheme.primaryContainer",
"md.sys.color.secondary": "MaterialTheme.colorScheme.secondary",
"md.sys.color.secondary-container": "MaterialTheme.colorScheme.secondaryContainer",
"md.sys.color.surface": "MaterialTheme.colorScheme.surface",
"md.sys.color.surface-variant": "MaterialTheme.colorScheme.surfaceVariant",
"md.sys.color.tertiary": "MaterialTheme.colorScheme.tertiary",
"md.sys.color.tertiary-container": "MaterialTheme.colorScheme.tertiaryContainer"
},
"typography": {
"symbols": {
"md.sys.typescale.display-large": "MaterialTheme.typography.displayLarge",
"md.sys.typescale.display-medium": "MaterialTheme.typography.displayMedium",
"md.sys.typescale.display-small": "MaterialTheme.typography.displaySmall",
"md.sys.typescale.headline-large": "MaterialTheme.typography.headlineLarge",
"md.sys.typescale.headline-medium": "MaterialTheme.typography.headlineMedium",
"md.sys.typescale.headline-small": "MaterialTheme.typography.headlineSmall",
"md.sys.typescale.title-large": "MaterialTheme.typography.titleLarge",
"md.sys.typescale.title-medium": "MaterialTheme.typography.titleMedium",
"md.sys.typescale.title-small": "MaterialTheme.typography.titleSmall",
"md.sys.typescale.body-large": "MaterialTheme.typography.bodyLarge",
"md.sys.typescale.body-medium": "MaterialTheme.typography.bodyMedium",
"md.sys.typescale.body-small": "MaterialTheme.typography.bodySmall",
"md.sys.typescale.label-large": "MaterialTheme.typography.labelLarge",
"md.sys.typescale.label-medium": "MaterialTheme.typography.labelMedium",
"md.sys.typescale.label-small": "MaterialTheme.typography.labelSmall"
},
"subproperties": {
"font": "fontFamily",
"weight": "fontWeight",
"size": "fontSize",
"tracking": "letterSpacing",
"line-height": "lineHeight"
}
},
"options": {
"packages": {
"MaterialTheme": "androidx.compose.material3"
}
}
}
}
Os arquivos de mapeamento de tema são estruturados com dois objetos de nível superior: figma
e compose
. Dentro desses objetos, as definições de cor e tipo estão vinculadas entre os dois ambientes com tokens intermediários. Isso ajuda os estilos do Figma a mapearem uma única entrada de tema do Compose, o que é útil quando há suporte para os temas claro e escuro.
- Revise o arquivo de mapeamento, especialmente a forma como ele remapeia propriedades de tipografia do Figma para o que é esperado pelo Compose.
Importar pacotes de interface novamente
Depois de criar um arquivo de mapeamento, você precisa importar de novo todos os pacotes de interface para o projeto porque todos os valores de estilo do Figma foram descartados na importação inicial, já que nenhum arquivo de mapeamento foi fornecido.
Para importar os pacotes de interface novamente, siga estas etapas:
- No Android Studio, selecione File > New > Import UI Packages. Uma caixa de diálogo Import UI Packages vai aparecer.
- Na caixa de texto Figma source URL, insira o URL do arquivo de origem do Figma.
- Selecione a caixa Translate Figma styles to Compose theme.
- Clique em Next. Uma prévia dos pacotes da interface do arquivo vai aparecer.
- Clique em Create. Os pacotes serão importados para seu projeto.
- Recrie o projeto, depois abra o arquivo
switch/Switch.kt
para acessar o código gerado.
Switch.kt (gerado)
@Composable
fun ActiveOverlay(
modifier: Modifier = Modifier,
content: @Composable RelayContainerScope.() -> Unit
) {
RelayContainer(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
isStructured = false,
radius = 32.0,
content = content,
modifier = modifier.fillMaxWidth(1.0f).fillMaxHeight(1.0f)
)
}
- Observe que o parâmetro
backgroundColor
está definido para o campoMaterialTheme.colorScheme.surfaceVariant
no objeto de tema do Compose. - No painel de visualização, troque para o tema escuro do app. O tema vai ser aplicado corretamente e os bugs visuais serão corrigidos.
9. Parabéns
Parabéns! Você aprendeu a integrar o Relay nos apps do Compose!