1. Antes de começar
Neste codelab, você vai usar o código da solução do codelab Introdução ao estado no Compose para criar uma calculadora interativa de gorjetas que pode calcular e arredondar um valor ao inserir a conta e a porcentagem da gorjeta. Confira o app final nesta imagem:
Pré-requisitos
- Já ter feito o codelab "Usar estado no Jetpack Compose".
- Saber adicionar os elementos combináveis
Text
eTextField
a um app. - Conhecimento sobre a função
remember
, estado, elevação de estado e diferença entre funções combináveis com e sem estado.
O que você vai aprender
- Como adicionar um botão de ação a um teclado virtual.
- Como configurar as ações do teclado.
- O que é um elemento combinável
Switch
e como usá-lo. - O que é o Layout Inspector.
O que você vai criar
- Um app Tip Time que calcula valores com base no custo de serviço inserido pelo usuário e na porcentagem da gorjeta.
O que é necessário
- Android Studio.
- O código da solução do codelab "Usar estado no Jetpack Compose".
2. Visão geral do app inicial
Este codelab começa com o app Tip Time do codelab anterior, que fornece a interface do usuário necessária para calcular uma gorjeta com uma porcentagem fixa. A caixa de texto Cost of service (custo do serviço) permite que o usuário insira o preço do serviço. O app calcula e mostra o valor da gorjeta em um elemento combinável Text
.
Acessar o código inicial
Para começar, faça o download do código inicial:
Como alternativa, é possível clonar o repositório do GitHub:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-tip-calculator.git $ cd basic-android-kotlin-compose-training-tip-calculator $ git checkout state
Procure o código no repositório do GitHub do Tip Calculator
(link em inglês).
Executar o app Tip Time
- Abra o projeto Tip Time no Android Studio e execute o app em um emulador ou dispositivo.
- Insira o custo do serviço. O app calcula e mostra automaticamente o valor da gorjeta.
Na implementação atual, a porcentagem da gorjeta está fixada no código como 15%. Neste codelab, você vai estender esse recurso com um campo de texto que permite ao app calcular uma porcentagem personalizada e arredondar o valor da gorjeta.
Adicionar os recursos de string necessários
- Na guia Project, clique em res > values > strings.xml.
- Entre as tags
<resources>
do arquivostrings.xml
, adicione estes recursos de string:
<string name="how_was_the_service">Tip (%)</string>
<string name="round_up_tip">Round up tip?</string>
O arquivo strings.xml
vai ser parecido com o snippet de código abaixo, que inclui as strings do codelab anterior:
strings.xml
<resources>
<string name="app_name">TipTime</string>
<string name="calculate_tip">Calculate Tip</string>
<string name="cost_of_service">Cost of Service</string>
<string name="how_was_the_service">Tip (%)</string>
<string name="round_up_tip">Round up tip?</string>
<string name="tip_amount">Tip Amount: %s</string>
</resources>
- Mude a string
Cost Of Service
paraBill Amount
. Em alguns países, serviço significa gorjeta, por isso essa mudança evita confusão. - Na string
Cost of Service
, clique com o botão direito do mouse emcost_of_service
, oname
do atributo, e selecione Refactor > Rename. Uma caixa de diálogo Rename é aberta.
- Na caixa de diálogo Rename, substitua
cost_of _service
porbill_amount
e clique em Refactor. Isso atualiza todas as ocorrências do recurso de stringcost_of_service
no projeto. Assim, não é necessário mudar o código do Compose manualmente.
- No arquivo
strings.xml
, mude o valor da string deCost of Service
paraBill Amount
:
<string name="bill_amount">Bill Amount</string>
- Navegue até o arquivo
MainActivity.kt
e execute o app. O identificador vai ser atualizado na caixa de texto, como mostra esta imagem:
3. Adicionar um campo de texto de porcentagem da gorjeta
É possível que um cliente queira aumentar ou diminuir a gorjeta com base na qualidade do serviço prestado e por vários outros motivos. Para acomodar isso, o app precisa permitir que o usuário calcule uma gorjeta personalizada. Nesta seção, você vai adicionar um campo de texto para que o usuário insira uma porcentagem da gorjeta, como mostrado nesta imagem:
Você já tem um campo de texto Bill amount (valor da conta) no app, que é a função combinável EditNumberField()
sem estado. No codelab anterior, você elevou o estado amountInput
do elemento combinável EditNumberField()
para a função TipTimeScreen()
, deixando o elemento EditNumberField()
sem estado.
Para adicionar um campo de texto, reutilize o mesmo elemento combinável EditNumberField()
, mas com um rótulo diferente. Para fazer essa mudança, você precisa transmitir o rótulo como um parâmetro em vez de fixá-lo na função combinável EditNumberField()
.
Torne a função combinável EditNumberField()
reutilizável:
- No arquivo
MainActivity.kt
nos parâmetros da função combinávelEditNumberField()
, adicione um recurso de stringlabel
do tipoInt
:
@Composable
fun EditNumberField(
label: Int,
value: String,
onValueChange: (String) -> Unit
)
- Adicione um argumento
modifier
do tipoModifier
à função combinávelEditNumberField()
:
@Composable
fun EditNumberField(
label: Int,
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier
)
- No corpo da função, substitua o ID do recurso de string fixado no código pelo parâmetro
label
:
@Composable
fun EditNumberField(
//...
) {
TextField(
//...
label = { Text(stringResource(label)) },
//...
)
}
- Para indicar que o parâmetro
label
é uma referência de recurso de string, faça a anotação@StringRes
no parâmetro da função:
@Composable
fun EditNumberField(
@StringRes label: Int,
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier
)
- Importe o seguinte:
import androidx.annotation.StringRes
- No elemento combinável
TextField
da funçãoEditNumberField()
, transmita o parâmetrolabel
à funçãostringResource()
.
@Composable
fun EditNumberField(
@StringRes label: Int,
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier
) {
TextField(
//...
label = { Text(stringResource(label)) },
//...
)
}
- Na chamada
EditNumberField()
da funçãoTipTimeScreen()
, defina o parâmetrolabel
como o recurso de stringR.string.bill_amount
:
EditNumberField(
label = R.string.bill_amount,
value = amountInput,
onValueChange = { amountInput = it }
)
- No painel "Design", clique em Build & Refresh. A interface do app vai ficar assim:
- Na função
TipTimeScreen()
após a chamadaEditNumberField()
, adicione outro campo de texto para a porcentagem de gorjeta personalizada. Chame a função combinávelEditNumberField()
com estes parâmetros:
EditNumberField(
label = R.string.how_was_the_service,
value = "",
onValueChange = { }
)
Isso adiciona outra caixa de texto para a porcentagem de gorjeta personalizada.
- No painel "Design", clique em Build & Refresh. A visualização do app agora mostra um campo de texto Tip (%) (porcentagem da gorjeta) como nesta imagem:
- Na parte de cima da função
TipTimeScreen()
, adicione uma propriedadevar
com o nometipInput
para a variável de estado do campo de texto adicionado. UsemutableStateOf("")
para inicializar a variável e cercar a chamada com a funçãoremember
:
var tipInput by remember { mutableStateOf("") }
- Na nova chamada de função
EditNumberField
()
, defina o parâmetro com nomevalue
como a variáveltipInput
e atualize a variáveltipInput
na expressão lambdaonValueChange
:
EditNumberField(
label = R.string.how_was_the_service,
value = tipInput,
onValueChange = { tipInput = it }
)
- Na função
TipTimeScreen()
após a definição da variáveltipInput
, defina uma variávelval
com o nometipPercent
que converte a variáveltipInput
em um tipoDouble
, use um operador elvis e retorne0.0
se o valor fornull
:
val tipPercent = tipInput.toDoubleOrNull() ?: 0.0
- Na função
TipTimeScreen()
, atualize a chamadacalculateTip()
e transmita a variáveltipPercent
como o segundo parâmetro:
val tip = calculateTip(amount, tipPercent)
O código da função TipTimeScreen()
vai ficar parecido com este snippet de código:
@Composable
fun TipTimeScreen() {
var amountInput by remember { mutableStateOf("") }
var tipInput by remember { mutableStateOf("") }
val tipPercent = tipInput.toDoubleOrNull() ?: 0.0
val amount = amountInput.toDoubleOrNull() ?: 0.0
val tip = calculateTip(amount, tipPercent)
Column(
modifier = Modifier.padding(32.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = stringResource(R.string.calculate_tip),
fontSize = 24.sp,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Spacer(Modifier.height(16.dp))
EditNumberField(
label = R.string.bill_amount,
value = amountInput,
onValueChange = { amountInput = it }
)
EditNumberField(
label = R.string.how_was_the_service,
value = tipInput,
onValueChange = { tipInput = it }
)
Spacer(Modifier.height(24.dp))
Text(
text = stringResource(R.string.tip_amount, tip),
modifier = Modifier.align(Alignment.CenterHorizontally),
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
}
}
- Execute o app em um emulador ou dispositivo e insira o valor da conta e a porcentagem da gorjeta. O app calcula o valor da gorjeta corretamente?
4. Definir um botão de ação
No codelab anterior, você explorou como usar a classe KeyboardOptions
para definir o tipo do teclado. Nesta seção, você vai aprender a definir o botão de ação do teclado com as mesmas KeyboardOptions
. Um botão de ação do teclado é um botão no final dele. Confira alguns exemplos nesta tabela:
Propriedade | Botão de ação no teclado |
| |
| |
|
Nesta tarefa, você vai definir dois botões de ação diferentes para as caixas de texto:
- Um botão de ação Next (próximo) para a caixa de texto Bill Amount (Valor da conta), que indica que o usuário terminou a entrada atual e quer passar para a próxima caixa de texto.
- Um botão de ação Done para a caixa de texto Tip %, que indica que o usuário terminou de digitar a gorjeta.
Você pode ver exemplos de teclados com esses botões de ação nestas imagens:
Adicione opções de teclado:
- Na chamada
TextField()
da funçãoEditNumberField()
, transmita ao construtorKeyboardOptions
um argumento com o nomeimeAction
definido como um valorImeAction.Next
. Use a funçãoKeyboardOptions.Default.copy
para aplicar as outras opções padrão, como letras maiúsculas e correção automática.
@Composable
fun EditNumberField(
//...
) {
TextField(
//...
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next
)
)
}
- Execute o app em um emulador ou dispositivo. O teclado agora exibe o botão de ação Next, como você pode ver nesta imagem:
No entanto, é recomendável ter dois botões de ação diferentes nos campos de texto. Esse problema vai ser corrigido em breve.
- Examine a função
EditNumberField()
. O parâmetrokeyboardOptions
na funçãoTextField()
está fixado no código. Para criar botões de ação diferentes para os campos de texto, transmita o objetoKeyboardOptions
como um argumento. Isso vai ser feito na próxima etapa.
// No need to copy, just examine the code.
fun EditNumberField(
@StringRes label: Int,
value: String,
onValueChange: (String) -> Unit
) {
TextField(
//...
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next
)
)
}
- Na definição da função
EditNumberField()
, adicione um parâmetrokeyboardOptions
do tipoKeyboardOptions
. No corpo da função, atribua-o ao parâmetrokeyboardOptions
da funçãoTextField()
:
@Composable
fun EditNumberField(
@StringRes label: Int,
keyboardOptions: KeyboardOptions,
value: String,
onValueChange: (String) -> Unit
){
TextField(
//...
keyboardOptions = keyboardOptions
)
}
- Na função
TipTimeScreen()
, atualize a primeira chamada de funçãoEditNumberField()
e transmita o parâmetrokeyboardOptions
para o campo de texto Bill Amount.
EditNumberField(
label = R.string.bill_amount,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next
),
value = amountInput,
onValueChange = { amountInput = it }
)
- Na segunda chamada de função
EditNumberField()
, mude aimeAction
do campo de texto Tip % paraImeAction.Done
. A função vai ser semelhante a este snippet de código:
EditNumberField(
label = R.string.how_was_the_service,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
value = tipInput,
onValueChange = { tipInput = it }
)
- Execute o app. Ele mostra os botões de ação Next e Done, como você pode ver nestas imagens:
- Digite qualquer valor para a conta e clique no botão de ação Next. Insira qualquer porcentagem de gorjeta e clique no botão de ação Done. Nada acontece porque você ainda não adicionou nenhuma funcionalidade aos botões. Vamos fazer isso na próxima seção.
5. Definir ações do teclado
Nesta seção, você vai implementar a função que move o foco para o próximo campo de texto e, para melhorar a experiência do usuário, fecha o teclado com a classe KeyboardActions
, que permite aos desenvolvedores especificar ações que são acionadas em resposta à ação do IME (editor de método de entrada, na sigla em inglês) dos usuários no teclado de software. Uma ação do IME ocorre, por exemplo, quando o usuário clica no botão de ação Next ou Done.
Implemente o seguinte:
- Na ação Next, mova o foco para o próximo campo de texto (a caixa de texto Tip %).
- Na ação Done, feche o teclado virtual.
- Na função
EditNumberField()
, adicione uma variávelval
com o nomefocusManager
e atribua a ela um valor da propriedadeLocalFocusManager.current
:
val focusManager = LocalFocusManager.current
A interface LocalFocusManager
é usada para controlar o foco no Compose. Use essa variável para mover o foco até as caixas de texto e o remover.
- Importe
import androidx.compose.ui.platform.LocalFocusManager
. - Na assinatura da função
EditNumberField()
, adicione outro parâmetrokeyboardActions
do tipoKeyboardActions
:
@Composable
fun EditNumberField(
@StringRes label: Int,
keyboardOptions: KeyboardOptions,
keyboardActions: KeyboardActions,
value: String,
onValueChange: (String) -> Unit
) {
//...
}
- No corpo da função
EditNumberField()
, atualize a chamadaTextField
()
e defina o parâmetrokeyboardActions
como o parâmetro transmitidokeyboardActions
.
@Composable
fun EditNumberField(
//...
) {
TextField(
//...
keyboardActions = keyboardActions
)
}
Agora você pode personalizar os campos de texto com funções diferentes para cada botão de ação.
- Na chamada de função
TipTimeScreen()
, atualize a primeira chamadaEditNumberField()
para incluir um parâmetrokeyboardActions
como um novo argumento. Atribua um valor a ele,KeyboardActions( onNext =
{ }
)
:
// Bill amount text field
EditNumberField(
//...
keyboardActions = KeyboardActions(
onNext = { }
),
//...
)
A expressão lambda do parâmetro onNext
é executada quando o usuário pressiona o botão de ação Next no teclado.
- Defina a lambda. Peça que o
FocusManager
mova o foco para baixo até o próximo elemento combinável, Tip %. Na expressão lambda, chame a funçãomoveFocus()
no objetofocusManager
e transmita o argumentoFocusDirection.Down
:
// Bill amount text field
EditNumberField(
label = R.string.bill_amount,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next
),
keyboardActions = KeyboardActions(
onNext = { focusManager.moveFocus(FocusDirection.Down) }
),
value = amountInput,
onValueChange = { amountInput = it }
)
A função moveFocus()
move o foco na direção especificada, que é o campo de texto Tip % nesse caso.
- Importe estas informações:
import androidx.compose.ui.focus.FocusDirection
- Adicione uma implementação semelhante ao campo de texto Tip %. A diferença é que você precisa definir um parâmetro
onDone
em vez deonNext
.
// Tip% text field
EditNumberField(
//...
keyboardActions = KeyboardActions(
onDone = { }
),
//...
)
- Depois que o usuário digita a gorjeta personalizada, a ação "Done" no teclado retira o foco, o que fecha o teclado. Defina a lambda e peça para o
FocusManager
retirar o foco. Na expressão lambda, chame a funçãoclearFocus()
no objetofocusManager
:
EditNumberField(
label = R.string.how_was_the_service,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = { focusManager.clearFocus() }),
value = tipInput,
onValueChange = { tipInput = it }
)
A função clearFocus()
retira o foco do componente que está em foco.
- Execute o app. As ações do teclado agora mudam o componente em foco, como você pode ver neste GIF:
6. Adicionar uma chave
Uma chave ativa ou desativa o estado de um único item. Há dois estados em um botão de ativação que permitem ao usuário selecionar entre duas opções. Um botão de alternância consiste em um círculo e uma faixa, conforme mostrado nestas imagens:
1. Círculo |
Uma chave é um controle de seleção que pode ser usado para inserir decisões ou declarar preferências, por exemplo, configurações, como você pode ver nesta imagem:
O usuário pode arrastar o círculo para frente e para trás a fim de escolher a opção selecionada ou simplesmente tocar na chave para alternar. Você pode ver outro exemplo de um botão de ativação neste GIF, em que a configuração Visual options (opções visuais) muda para Dark mode (modo escuro):
Para saber mais, consulte a documentação sobre chaves.
Use o elemento combinável Switch
para que o usuário arredonde a gorjeta para o número inteiro mais próximo, como você pode ver nesta imagem:
Adicione uma linha para os elementos combináveis Text
e Switch
.
- Depois da função
EditNumberField()
, adicione uma função combinávelRoundTheTipRow()
e transmita umModifier
padrão, como argumentos semelhantes à funçãoEditNumberField()
:
@Composable
fun RoundTheTipRow(modifier: Modifier = Modifier) {
}
- Implemente a função
RoundTheTipRow()
, adicione um elemento combinável de layoutRow
com omodifier
abaixo para definir a largura dos elementos filhos como o máximo na tela, centralize o alinhamento e garanta um tamanho de48
dp
:
Row(
modifier = Modifier
.fillMaxWidth()
.size(48.dp),
verticalAlignment = Alignment.CenterVertically
) {
}
- Importe estas informações:
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Size
- No bloco lambda do elemento combinável do layout
Row
, adicione um elementoText
que use o recurso de stringR.string.round_up_tip
para mostrar uma stringRound up tip?
:
Text(text = stringResource(R.string.round_up_tip))
- Depois do elemento combinável
Text
, adicione um elementoSwitch
e transmita um parâmetrochecked
definido comoroundUp
e um parâmetroonCheckedChange
definido comoonRoundUpChanged
.
Switch(
checked = roundUp,
onCheckedChange = onRoundUpChanged,
)
Esta tabela contém informações sobre esses parâmetros, que são os mesmos definidos para a função RoundTheTipRow()
:
Parâmetro | Descrição |
| Se a chave está marcada ou não. Esse é o estado do elemento combinável |
| O callback que é chamado quando a chave recebe um clique. |
- Importe o seguinte:
import androidx.compose.material.Switch
- Na função
RoundTipRow()
, adicione um parâmetroroundUp
do tipoBoolean
e uma função lambdaonRoundUpChanged
que usa umBoolean
e não retorna nada:
@Composable
fun RoundTheTipRow(
roundUp: Boolean,
onRoundUpChanged: (Boolean) -> Unit,
modifier: Modifier = Modifier
)
Isso eleva o estado da chave.
- No elemento combinável
Switch
, adicione estemodifier
para alinhar o elementoSwitch
ao final da tela:
Switch(
modifier = modifier
.fillMaxWidth()
.wrapContentWidth(Alignment.End),
//...
)
- Importe o seguinte:
import androidx.compose.foundation.layout.wrapContentWidth
- Na função
TipTimeScreen()
, adicione uma variável var para o estado do elemento combinávelSwitch
. Crie uma variávelvar
com o nomeroundUp
e a defina comomutableStateOf()
, comfalse
como o argumento padrão. Envolva a chamada comremember { }
.
fun TipTimeScreen() {
//...
var roundUp by remember { mutableStateOf(false) }
//...
Column(
...
) {
//...
}
}
Essa é a variável do estado do elemento combinável Switch
e "false" vai ser o estado padrão.
- No bloco
Column
da funçãoTipTimeScreen()
depois do campo de texto Tip %, chame a funçãoRoundTheTipRow()
com estes argumentos: parâmetroroundUp
definido comoroundUp
e um parâmetro nomeadoonRoundUpChanged
definido como um callback lambda que atualiza o valorroundUp
:
@Composable
fun TipTimeScreen() {
//...
Column(
...
) {
Text(
...
)
Spacer(...)
EditNumberField(
...
)
EditNumberField(
...
)
RoundTheTipRow(roundUp = roundUp, onRoundUpChanged = { roundUp = it })
Spacer(...)
Text(
...
)
}
}
A linha Round up tip (arredondar gorjeta) vai aparecer.
- Execute o app. Ele mostra o botão de ativação Round up tip? (Arredondar gorjeta?), mas o círculo do botão quase não está visível, como você pode ver nesta imagem:
1. Círculo |
Você vai melhorar a visibilidade do círculo nas próximas etapas alterando a cor dele para cinza escuro.
- No elemento combinável
Switch()
da funçãoRoundTheTipRow()
, adicione um parâmetro com o nomecolors
. - Defina o parâmetro
colors
como uma funçãoSwitchDefaults.colors()
que aceita um parâmetrouncheckedThumbColor
definido como um argumentoColor.DarkGray
.
Switch(
//...
colors = SwitchDefaults.colors(
uncheckedThumbColor = Color.DarkGray
)
)
- Importe o seguinte:
import androidx.compose.material.SwitchDefaults
import androidx.compose.ui.graphics.Color
A função combinável RoundTheTipRow()
agora vai ficar assim:
@Composable
fun RoundTheTipRow(roundUp: Boolean, onRoundUpChanged: (Boolean) -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.size(48.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(stringResource(R.string.round_up_tip))
Switch(
modifier = Modifier
.fillMaxWidth()
.wrapContentWidth(Alignment.End),
checked = roundUp,
onCheckedChange = onRoundUpChanged,
colors = SwitchDefaults.colors(
uncheckedThumbColor = Color.DarkGray
)
)
}
}
- Execute o app. A cor do círculo da chave é diferente, como você pode ver nesta imagem:
- Insira o valor da conta e a porcentagem da gorjeta e selecione o botão de alternância Round up tip?. O valor da gorjeta não é arredondado porque você ainda precisa atualizar a função
calculateTip()
, o que vai ser feito na próxima seção.
Atualizar a função calculateTip()
para arredondar a gorjeta
Modifique a função calculateTip()
para aceitar uma variável Boolean
e arredondar (link em inglês) a gorjeta para o número inteiro mais próximo:
- Para arredondar a gorjeta, a função
calculateTip()
precisa saber o estado da chave, que é umBoolean
. Na funçãocalculateTip()
, adicione um parâmetroroundUp
do tipoBoolean
:
private fun calculateTip(
amount: Double,
tipPercent: Double = 15.0,
roundUp: Boolean
): String {
//...
}
- Na função
calculateTip()
, antes da instruçãoreturn
, adicione uma condiçãoif()
que verifica o valorroundUp
. SeroundUp
fortrue
, defina uma variáveltip
como a funçãokotlin.math.
ceil
()
e, em seguida, transmita a funçãotip
como argumento:
if (roundUp)
tip = kotlin.math.ceil(tip)
A função calculateTip()
concluída vai ficar parecida com este snippet de código:
private fun calculateTip(amount: Double, tipPercent: Double = 15.0, roundUp: Boolean): String {
var tip = tipPercent / 100 * amount
if (roundUp)
tip = kotlin.math.ceil(tip)
return NumberFormat.getCurrencyInstance().format(tip)
}
- Na função
TipTimeScreen()
, atualize a chamadacalculateTip()
e transmita um parâmetroroundUp
:
val tip = calculateTip(amount, tipPercent, roundUp)
- Execute o app. Agora, o valor da gorjeta vai ser arredondado, como você pode ver nestas imagens:
7. Acessar o código da solução
Para fazer o download do código do codelab concluído, use este comando git:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-tip-calculator.git
Se preferir, você pode fazer o download do repositório como um arquivo ZIP, descompactar e abrir no Android Studio.
Se você quiser ver o código da solução, acesse o GitHub (link em inglês).
8. Conclusão
Parabéns! Você adicionou a função de gorjeta personalizada ao app Tip Time. Agora, o app permite que os usuários digitem uma porcentagem de gorjeta personalizada e arredondem o valor da gorjeta. Compartilhe seu trabalho nas mídias sociais usando a hashtag #AndroidBasics.