Criar um app Dice Roller interativo

1. Antes de começar

Neste codelab, você vai criar um app Dice Roller interativo que permite que os usuários toquem em um elemento combinável Button para jogar um dado. O resultado da jogada aparece com um elemento combinável Image na tela.

Você usa o Jetpack Compose com o Kotlin para criar o layout do app e, em seguida, programa a lógica de negócios para processar o que acontece quando o elemento combinável Button é tocado.

Pré-requisitos

  • Saber criar e executar um app básico do Compose no Android Studio.
  • Saber como usar o elemento de composição Text em um app.
  • Saber como extrair texto em um recurso de string para facilitar a tradução do app e a reutilização de strings.
  • Conhecimento básico de programação em Kotlin.

O que você vai aprender

  • Como adicionar um elemento combinável Button a um app Android com o Compose.
  • Como adicionar um comportamento a um elemento de composição Button em um app Android com o Compose.
  • Como abrir e modificar o código da Activity de um app Android.

O que você vai criar

  • Um app Android interativo chamado Dice Roller. Nele, os usuários jogam um dado e o resultado da jogada aparece na tela.

O que é necessário

  • Um computador com o Android Studio instalado.

O app ficará assim quando você concluir este codelab:

3e9a9f44c6c84634.png

2. Definir um valor de referência

Criar um projeto

  1. No Android Studio, clique em File > New > New Project.
  2. Na caixa de diálogo New Project, selecione Empty Activity e clique em Next.

39373040e14f9c59.png

  1. No campo Name, digite Dice Roller.
  2. No campo Minimum SDK, selecione o nível de API mínimo 24 (Nougat) no menu e clique em Finish.

8fd6db761068ca04.png

3. Criar a infraestrutura do layout

Visualizar o projeto

Para visualizar o projeto:

  • Clique em Build & Refresh no painel Split ou Design.

9f1e18365da2f79c.png

Há uma visualização no painel Design. Se a visualização parecer pequena, não se preocupe, porque ela muda quando o layout é modificado.

b5c9dece74200185.png

Reestruturar o exemplo de código

É necessário mudar parte do código gerado para se assemelhar ao tema de um app para jogar dados.

Como você viu na captura de tela do app final, há a imagem de um dado e um botão para jogar. Você vai estruturar as funções combináveis para refletir essa arquitetura.

Para reestruturar o exemplo de código:

  1. Remova a função GreetingPreview().
  2. Crie uma função DiceWithButtonAndImage() com a anotação @Composable.

Essa função de composição representa os componentes de IU do layout e também contém a lógica de clique no botão e de exibição de imagem.

  1. Remova a função Greeting(name: String, modifier: Modifier = Modifier).
  2. Crie uma função DiceRollerApp() com as anotações @Preview e @Composable.

Como esse app só tem um botão e uma imagem, ele pode ser visto como uma função de composição. É por isso que ele é chamado de função DiceRollerApp().

MainActivity.kt

@Preview
@Composable
fun DiceRollerApp() {

}

@Composable
fun DiceWithButtonAndImage() {

}

Como você removeu a função Greeting(), a chamada para Greeting("Android") no corpo da lambda DiceRollerTheme() é destacada em vermelho. Isso ocorre porque o compilador não consegue mais encontrar uma referência a essa função.

  1. Exclua todo o código da lambda setContent{} encontrada no método onCreate().
  2. No corpo da lambda setContent{}, chame a lambda DiceRollerTheme{} e, dentro da lambda DiceRollerTheme{}, chame a função DiceRollerApp().

MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        DiceRollerTheme {
            DiceRollerApp()
        }
    }
}
  1. Na função DiceRollerApp(), chame a função DiceWithButtonAndImage().

MainActivity.kt

@Preview
@Composable
fun DiceRollerApp() {
    DiceWithButtonAndImage()
}

Adicionar um modificador

O Compose usa um objeto Modifier, que é uma coleção de elementos usados para decorar ou modificar o comportamento dos elementos da IU do Compose. Ele vai ser usado para definir o estilo dos componentes da interface do app Dice Roller.

Para adicionar um modificador:

  1. Modifique a função DiceWithButtonAndImage() para aceitar um argumento modifier do tipo Modifier e atribuir a ele um valor padrão de Modifier.

MainActivity.kt

@Composable
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
}

O snippet de código anterior pode ser confuso, então vamos explicar. A função permite que um parâmetro modifier seja transmitido. O valor padrão do parâmetro modifier é um objeto Modifier, portanto usamos = Modifier na assinatura do método. O valor padrão de um parâmetro permite que qualquer pessoa que chame esse método no futuro decida transmitir ou não um valor ao parâmetro. Se alguém transmitir o próprio objeto Modifier, vai poder personalizar o comportamento e a decoração da IU. Se ela optar por não transmitir um objeto Modifier, o valor padrão (que é o objeto Modifier simples) vai ser usado. Você pode aplicar essa prática a qualquer parâmetro. Para mais informações sobre argumentos padrão, consulte Argumentos padrão (link em inglês).

  1. Agora que o elemento combinável DiceWithButtonAndImage() tem um parâmetro modificador, transmita um modificador quando o elemento for chamado. Como a assinatura do método para a função DiceWithButtonAndImage() mudou, um objeto Modifier com as decorações desejadas precisa ser transmitido quando for chamado. A classe Modifier é responsável pela decoração ou pela adição do comportamento a um elemento combinável na função DiceRollerApp(). Nesse caso, há algumas decorações importantes para adicionar ao objeto Modifier, que é transmitido à função DiceWithButtonAndImage().

Você talvez queira saber por que se preocupar em transmitir um argumento Modifier quando há um padrão. Isso acontece porque os elementos de composição podem passar por uma recomposição, o que significa que o bloco de código no método @Composable é executado novamente. Se um objeto Modifier for criado em um bloco de código, ele vai poder ser recriado, e isso não é eficiente. A recomposição será abordada mais adiante neste codelab.

MainActivity.kt

DiceWithButtonAndImage(modifier = Modifier)
  1. Encadeie um método fillMaxSize() no objeto Modifier para que o layout preencha toda a tela.

Esse método especifica que os componentes precisam preencher o espaço disponível. No início deste codelab, você conheceu uma captura de tela da interface final do app Dice Roller. Um recurso interessante é que os dados e o botão ficam centralizados na tela. O método wrapContentSize() especifica que o espaço disponível precisa ser pelo menos tão grande quanto os componentes dentro dele. No entanto, como o método fillMaxSize() é usado, se os componentes dentro do layout forem menores que o espaço disponível, um objeto Alignment poderá ser transmitido ao método wrapContentSize() que especifica como os componentes precisam se alinhar dentro do espaço disponível.

MainActivity.kt

DiceWithButtonAndImage(modifier = Modifier
    .fillMaxSize()
)
  1. Encadeie o método wrapContentSize() no objeto Modifier e transmita Alignment.Center como um argumento para centralizar os componentes. Alignment.Center especifica que um componente é centralizado na vertical e na horizontal.

MainActivity.kt

DiceWithButtonAndImage(modifier = Modifier
    .fillMaxSize()
    .wrapContentSize(Alignment.Center)
)

4. Criar um layout vertical

No Compose, os layouts verticais são criados com a função Column().

A função Column() é um layout combinável que coloca os filhos em uma sequência vertical. No design esperado do app, é possível conferir que a imagem do dado é mostrada logo acima do botão de jogar:

7d70bb14948e3cc1.png

Para criar um layout vertical:

  1. Na função DiceWithButtonAndImage(), adicione uma função Column().
  1. Transmita o argumento modifier da assinatura do método DiceWithButtonAndImage() para o argumento do modificador Column().

O argumento modifier garante que os elementos combináveis na função Column() sigam as restrições chamadas na instância modifier.

  1. Transmita um argumento horizontalAlignment para a função Column() e defina-o com um valor de Alignment.CenterHorizontally.

Isso garante que os filhos dentro da coluna fiquem centralizados na tela do dispositivo em relação à largura.

MainActivity.kt

fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
    Column (
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {}
}

5. Adicionar um botão

  1. No arquivo strings.xml, adicione uma string e defina-a como um valor de Roll.

res/values/strings.xml

<string name="roll">Roll</string>
  1. No corpo da lambda da Column(), adicione uma função Button().
  1. No arquivo MainActivity.kt, adicione uma função Text() ao Button() no corpo da lambda da função.
  2. Transmita o ID do recurso da string roll à função stringResource() e transmita o resultado ao elemento de composição Text.

MainActivity.kt

Column(
    modifier = modifier,
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Button(onClick = { /*TODO*/ }) {
        Text(stringResource(R.string.roll))
    }
}

6. Adicionar uma imagem

Outro componente essencial do app é a imagem do dado, que exibe o resultado quando o usuário toca no botão Roll. Você adiciona a imagem com um elemento de composição Image, mas ela exige um recurso de imagem. Portanto, primeiro é necessário fazer o download de algumas imagens fornecidas para esse app.

Fazer o download das imagens de dados

  1. Abra este URL para fazer o download de um arquivo ZIP com imagens de dados e aguarde a conclusão.

Localize o arquivo no computador. É provável que esteja na pasta Downloads.

  1. Descompacte o arquivo ZIP para criar uma nova pasta dice_images que contém seis arquivos de imagem de dados com valores de 1 a 6.

Adicionar imagens de dados ao app

  1. No Android Studio, clique em View > Tool Windows > Resource Manager.
  2. Clique em + > Import Drawables para abrir um navegador de arquivos.

12f17d0b37dd97d2.png

  1. Encontre e selecione a pasta com as seis imagens de dados e continue o upload.

As imagens enviadas aparecerão desta forma:

4f66c8187a2c58e2.png

  1. Clique em Próxima.

688772df9c792264.png

A caixa de diálogo Import Drawables aparece e mostra onde os arquivos de recursos ficam na estrutura de arquivos.

  1. Clique em Import para confirmar que você quer importar as seis imagens.

As imagens vão aparecer no painel Resource Manager.

c2f08e5311f9a111.png

Bom trabalho! Na próxima tarefa, você vai usar essas imagens no app.

Adicionar um elemento combinável Image

A imagem do dado precisa aparecer acima do botão Roll. O Compose coloca naturalmente os componentes da IU de maneira sequencial. Em outras palavras, o elemento combinável é declarado e mostrado primeiro. Isso pode indicar que a primeira declaração é mostrada acima ou antes do elemento combinável declarado depois dela. Os elementos de composição dentro de um Column vão ser exibidos acima / abaixo um do outro no dispositivo. Neste app, você usa uma Column para empilhar os elementos combináveis na vertical. Portanto, o combinável que é declarado primeiro dentro da função Column() é exibido antes dos combináveis que são declarados depois dele na mesma função Column().

Para adicionar um elemento combinável Image:

  1. No corpo da função Column(), crie uma função Image() antes da função Button().

MainActivity.kt

Column(
    modifier = modifier,
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Image()
    Button(onClick = { /*TODO*/ }) {
      Text(stringResource(R.string.roll))
    }
}
  1. Transmita um argumento painter à função Image() e atribua a ele um valor painterResource que aceite um argumento de ID de recurso drawable. Por enquanto, transmita este ID de recurso: argumento R.drawable.dice_1.

MainActivity.kt

Image(
    painter = painterResource(R.drawable.dice_1)
)
  1. Sempre que você criar uma imagem no app, forneça o que é chamado de "descrição de conteúdo". As descrições de conteúdo são uma parte importante do desenvolvimento para Android. Elas anexam descrições aos respectivos componentes de IU para aumentar a acessibilidade. Para mais informações sobre as descrições de conteúdo, consulte Descrever cada elemento da IU. É possível transmitir uma descrição de conteúdo para a imagem como um parâmetro.

MainActivity.kt

Image(
    painter = painterResource(R.drawable.dice_1),
    contentDescription = "1"
)

Agora, todos os componentes de interface necessários estão presentes. No entanto, o Button e a Image estão perto demais.

54b27140071ac2fa.png

  1. Para corrigir isso, adicione um elemento combinável Spacer entre os elementos Image e Button. Um Spacer usa um Modifier como parâmetro. Nesse caso, a Image está acima do Button, de modo que precisa haver um espaço vertical entre eles. Portanto, a altura do Modifier pode ser definida e aplicada ao Spacer. Tente definir a altura como 16.dp. Normalmente, as dimensões dp são mudadas em incrementos de 4.dp.

MainActivity.kt

Spacer(modifier = Modifier.height(16.dp))
  1. No painel Preview, clique em Build & Refresh.

Vai aparecer algo como esta imagem:

73eea4c166f7e9d2.png

7. Criar a lógica para jogar dados

Agora que todos os elementos de composição necessários estão presentes, você vai modificar o app para que um toque no botão jogue o dado.

Tornar o botão interativo

  1. Na função DiceWithButtonAndImage() antes da função Column(), crie uma variável result e a defina como um valor 1.
  2. Observe o elemento combinável Button. Você vai perceber que ele está transmitindo um parâmetro onClick, que está definido como um par de chaves com o comentário /*TODO*/ dentro delas. Nesse caso, as chaves representam o que é conhecido como lambda e a área dentro delas é o corpo da lambda. Quando uma função é transmitida como um argumento, ela também pode ser chamada de "callback" (link em inglês).

MainActivity.kt

Button(onClick = { /*TODO*/ })

Um lambda é um literal de função, que é uma função como qualquer outra. No entanto, em vez de ser declarada separadamente com a palavra-chave fun, ela é escrita in-line e transmitida como uma expressão. O elemento combinável Button espera que uma função seja transmitida como o parâmetro onClick. Esse é o lugar perfeito para usar um lambda. Você vai escrever o corpo do lambda nesta seção.

  1. Na função Button(), remova o comentário /*TODO*/ do valor do corpo da lambda do parâmetro onClick.
  2. A jogada do dado é aleatória. Para refletir isso no código, é preciso usar a sintaxe correta para gerar um número aleatório. Em Kotlin, você pode usar o método random() em um intervalo numérico. No corpo da lambda onClick, defina a variável result como um intervalo entre 1 e 6 e, em seguida, chame o método random() nesse intervalo. Em Kotlin, os intervalos são designados por dois pontos finais entre o primeiro e o último número.

MainActivity.kt

fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
    var result = 1
    Column(
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Image(
            painter = painterResource(R.drawable.dice_1),
            contentDescription = "1"
        )
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = { result = (1..6).random() }) {
            Text(stringResource(R.string.roll))
        }
    }
}

Agora, o botão pode ser tocado, mas um toque ainda não causa mudanças visuais porque você ainda precisa criar essa funcionalidade.

Adicionar uma condição ao app de jogar dados

Na seção anterior, você criou uma variável result e a codificou como um valor de 1. Por fim, o valor da variável result é redefinido quando o usuário toca no botão Roll e a imagem que vai ser exibida é determinada.

Por padrão, os elementos combináveis são sem estado, o que significa que eles não contêm um valor e podem ser recompostos a qualquer momento pelo sistema, resultando na redefinição do valor. No entanto, o Compose oferece uma maneira prática de evitar isso. As funções combináveis podem armazenar um objeto na memória usando o elemento combinável remember.

  1. Transforme a variável result em um elemento combinável remember.

O elemento combinável remember exige que uma função seja transmitida.

  1. No corpo do elemento de composição remember, transmita uma função mutableStateOf() e um argumento 1.

A função mutableStateOf() retorna um elemento observável. Você vai aprender mais sobre os elementos observáveis no futuro, mas, por enquanto, isso significa que, quando o valor da variável result muda, uma recomposição é acionada, o valor do resultado é refletido e a interface é atualizada.

MainActivity.kt

var result by remember { mutableStateOf(1) }

Agora, quando o usuário tocar no botão, a variável result vai ser atualizada com um valor numérico aleatório.

A variável result agora pode ser usada para determinar qual imagem vai aparecer.

  1. Abaixo da instanciação da variável result, crie uma variável imageResource imutável, definida como uma expressão when, que aceite uma variável result e defina cada resultado possível para o drawable.

MainActivity.kt

val imageResource = when (result) {
    1 -> R.drawable.dice_1
    2 -> R.drawable.dice_2
    3 -> R.drawable.dice_3
    4 -> R.drawable.dice_4
    5 -> R.drawable.dice_5
    else -> R.drawable.dice_6
}
  1. Mude o ID transmitido ao parâmetro painterResource do elemento combinável Image do drawable R.drawable.dice_1 para a variável imageResource.
  2. Mude o parâmetro contentDescription do elemento de composição Image para refletir o valor da variável result. Para isso, converta a variável result em uma string com toString() e transmita-a como contentDescription.

MainActivity.kt

Image(
   painter = painterResource(imageResource),
   contentDescription = result.toString()
)
  1. Execute o app.

O app Dice Roller vai funcionar totalmente agora.

3e9a9f44c6c84634.png

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

Para baixar o código do codelab concluído, use este comando git:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-dice-roller.git

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).

  1. Navegue até a página do repositório do GitHub fornecida para o projeto.
  2. Verifique se o nome da ramificação corresponde ao especificado no codelab. Por exemplo, na captura de tela a seguir, o nome da ramificação é main.

1e4c0d2c081a8fd2.png

  1. Na página do GitHub do projeto, clique no botão Code, que vai mostrar uma janela pop-up.

1debcf330fd04c7b.png

  1. Na janela pop-up, clique no botão Download ZIP para salvar o projeto no seu computador. Aguarde a conclusão do download.
  2. Localize o arquivo no computador. Geralmente ele é salvo na pasta Downloads.
  3. Clique duas vezes para descompactar o arquivo ZIP. Isso cria uma nova pasta com os arquivos do projeto.

Abrir o projeto no Android Studio

  1. Inicie o Android Studio.
  2. Na janela Welcome to Android Studio, clique em Open.

d8e9dbdeafe9038a.png

Observação: caso o Android Studio já esteja aberto, selecione a opção File > Open.

8d1fda7396afe8e5.png

  1. No navegador de arquivos, vá até a pasta descompactada do projeto, que provavelmente está na pasta Downloads.
  2. Clique duas vezes nessa pasta do projeto.
  3. Aguarde o Android Studio abrir o projeto.
  4. Clique no botão Run 8de56cba7583251f.png para criar e executar o app. Confira se ele é criado da forma esperada.

9. Conclusão

Você criou um app Dice Roller interativo para Android com o Compose.

Resumo

  • Defina funções de composição.
  • Crie layouts com composições.
  • Crie um botão com o elemento combinável Button.
  • Importe recursos drawable.
  • Mostre uma imagem com o elemento combinável Image.
  • Use elementos combináveis para fazer uma interface interativa.
  • Use o elemento combinável remember para armazenar objetos em uma composição na memória.
  • Atualize a interface com a função mutableStateOf() para criar um elemento observável.

Saiba mais