1. Antes de começar
Este codelab ensina sobre o estado e como ele pode ser usado e manipulado pelo Jetpack Compose.
Basicamente, o estado em um app é qualquer valor que pode mudar ao longo do tempo. Essa é uma definição bem ampla que inclui tudo, desde um banco de dados até uma variável no app. Você vai saber mais sobre os bancos de dados em outra unidade. Por enquanto, basta saber que eles são coleções organizadas de informações estruturadas, como arquivos do seu computador.
Todos os apps Android mostram o estado para o usuário. Veja alguns exemplos de estado em apps Android:
- Uma mensagem que mostra quando não é possível estabelecer uma conexão de rede.
- Formulários, como formulários de registro. O estado pode ser preenchido e enviado.
- Controles em que o usuário pode tocar, como botões. O estado pode ser não tocado, em processo de toque (animação na tela) ou tocado (uma ação
onClick
).
Neste codelab, você vai aprender a usar e avaliar o estado ao usar o Compose. Para isso, vamos criar um app de calculadora de gorjetas chamado Tip Time com estes elementos de interface integrados no Compose:
- Um elemento combinável
TextField
para inserir e editar texto. - Um elemento combinável
Text
para mostrar texto - Um elemento combinável
Spacer
para mostrar espaços vazios entre os elementos da interface.
Ao final deste codelab, você terá criado uma calculadora de gorjetas interativa que calcula automaticamente o valor da gorjeta ao receber o valor do serviço. Esta imagem mostra a aparência do app final:
Pré-requisitos
- Noções básicas do Compose, como a anotação
@Composable
. - Conhecimento básico dos layouts do Compose, como os elementos combináveis de layout
Row
eColumn
. - Conhecimento básico sobre modificadores, como a função
Modifier.padding()
. - Familiaridade com o elemento combinável
Text
.
O que você vai aprender
- Como pensar sobre o estado em uma interface.
- Como o Compose usa o estado para mostrar dados.
- Como adicionar uma caixa de texto ao app.
- Como elevar um estado.
O que você vai criar
- Um app de calculadora de gorjetas chamado Tip Time que calcula o valor da gorjeta com base no valor do serviço.
O que é necessário
- Um computador com acesso à Internet e um navegador da Web.
- Conhecimento sobre Kotlin.
- A versão mais recente do Android Studio.
2. Começar
- Acesse a calculadora on-line de gorjetas do Google. Este é apenas um exemplo e não é o app Android que você vai criar neste curso.
- Insira valores diferentes nas caixas Bill (conta) e Tip % (porcentagem da gorjeta). O valor da gorjeta e o total mudam de acordo com as informações inseridas.
Quando você informa os valores, as informações em Tip e Total são atualizadas. No final do codelab a seguir, você vai desenvolver um app de calculadora de gorjetas semelhante no Android.
Neste módulo, você vai criar um app simples de calculadora de gorjetas para o Android.
Os desenvolvedores geralmente trabalham assim: primeiro, criam uma versão simples e funcional do app, mesmo que ela não tenha uma aparência muito boa, e depois adicionam recursos e a deixam mais bonita.
Ao final deste codelab, seu app de calculadora de gorjetas vai ficar parecido com as capturas de tela. Quando o usuário inserir o valor de uma conta, seu app vai mostrar um valor de gorjeta sugerido. Por enquanto, a porcentagem da gorjeta está fixada em 15%. No próximo codelab, você continuará trabalhando no app e vai adicionar outros recursos, como uma porcentagem personalizada.
3. Receber código inicial
O código inicial é um código pré-escrito que pode ser usado como ponto de partida para um novo projeto. Com ele, você pode se concentrar nos novos conceitos ensinados neste codelab.
Faça o download do código inicial para começar:
Outra opção é 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 starter
Procure o código inicial no repositório do GitHub do TipTime
(link em inglês).
Visão geral do app inicial
Para se familiarizar com o código inicial, siga estas etapas:
- Abra o projeto com o código inicial no Android Studio.
- Execute o app em um dispositivo Android ou emulador.
- Há dois componentes de texto: um deles para um rótulo, e o outro para mostrar o valor da gorjeta.
Código inicial etapa por etapa
O código inicial tem os elementos combináveis de texto. Neste Programa de treinamentos, você vai adicionar um campo de texto para receber entradas dos usuários. Confira a seguir um breve tutorial sobre alguns arquivos para começar.
res > values > strings.xml
<resources>
<string name="app_name">Tip Time</string>
<string name="calculate_tip">Calculate Tip</string>
<string name="bill_amount">Bill Amount</string>
<string name="tip_amount">Tip Amount: %s</string>
</resources>
Este é o arquivo string.xml
nos recursos com todas as strings que você vai usar neste app.
MainActivity
Esse arquivo contém principalmente o código gerado por modelo e as funções a seguir.
- A função
TipTimeLayout()
contém um elementoColumn
com dois elementos combináveis de texto que aparecem nas capturas de tela. Ela também tem o combinávelspacer
para adicionar espaço por motivos estéticos. - A função
calculateTip()
aceita o valor da conta e calcula um valor de gorjeta de 15%. O parâmetrotipPercent
é definido como um valor de argumento padrão15.0
. Com isso, você vai definir o valor de gorjeta padrão como 15%, por enquanto. No próximo codelab, você vai saber o valor da gorjeta do usuário.
@Composable
fun TipTimeLayout() {
Column(
modifier = Modifier
.statusBarsPadding()
.padding(horizontal = 40.dp)
.verticalScroll(rememberScrollState())
.safeDrawingPadding(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = stringResource(R.string.calculate_tip),
modifier = Modifier
.padding(bottom = 16.dp, top = 40.dp)
.align(alignment = Alignment.Start)
)
Text(
text = stringResource(R.string.tip_amount, "$0.00"),
style = MaterialTheme.typography.displaySmall
)
Spacer(modifier = Modifier.height(150.dp))
}
}
private fun calculateTip(amount: Double, tipPercent: Double = 15.0): String {
val tip = tipPercent / 100 * amount
return NumberFormat.getCurrencyInstance().format(tip)
}
No bloco Surface()
da função onCreate()
, a função TipTimeLayout()
está sendo chamada. Com isso, o layout do app é exibido no dispositivo ou emulador.
override fun onCreate(savedInstanceState: Bundle?) {
//...
setContent {
TipTimeTheme {
Surface(
//...
) {
TipTimeLayout()
}
}
}
}
No bloco TipTimeTheme
da função TipTimeLayoutPreview()
, a função TipTimeLayout()
está sendo chamada. Com isso, o layout do app é exibido no Design e no painel Split.
@Preview(showBackground = true)
@Composable
fun TipTimeLayoutPreview() {
TipTimeTheme {
TipTimeLayout()
}
}
Receber entrada do usuário
Nesta seção, você vai adicionar o elemento de interface que permite inserir o valor do serviço no app. Confira a aparência dele:
O app usa um estilo e um tema personalizados.
Estilos e temas são uma coleção de atributos que especificam a aparência de um único elemento da interface. Um estilo pode especificar atributos que podem ser aplicados a todo o app, como cor e tamanho da fonte, cor do plano de fundo e muito mais. Outros codelabs vão abordar a implementação esses atributos no seu app. Por enquanto, isso já foi feito para deixar seu app mais bonito.
Para entender melhor, confira uma comparação lado a lado da versão de solução do aplicativo com e sem um tema personalizado.
Sem um tema personalizado. | Com um tema personalizado. |
A função combinável TextField
permite que o usuário insira texto em um app. Por exemplo, observe a caixa de texto na tela de login do app Gmail nesta imagem:
Adicione o elemento combinável TextField
ao app:
- No arquivo
MainActivity.kt
, adicione uma função combinávelEditNumberField()
, que usa um parâmetroModifier
. - No corpo da função
EditNumberField()
abaixo deTipTimeLayout()
, adicione umTextField
que aceite um parâmetro chamadovalue
, definido como uma string vazia e um parâmetro chamadoonValueChange
definido como uma expressão lambda vazia:
@Composable
fun EditNumberField(modifier: Modifier = Modifier) {
TextField(
value = "",
onValueChange = {},
modifier = modifier
)
}
- Observe os parâmetros que você transmitiu:
- O parâmetro
value
é uma caixa de texto que mostra o valor da string que você transmite aqui. - O parâmetro
onValueChange
é o callback lambda acionado quando o usuário insere texto na caixa.
- Importe esta função:
import androidx.compose.material3.TextField
- No combinável
TipTimeLayout()
, na linha após a primeira função combinável de texto, chame a funçãoEditNumberField()
, transmitindo o modificador a seguir.
import androidx.compose.foundation.layout.fillMaxWidth
@Composable
fun TipTimeLayout() {
Column(
modifier = Modifier
.statusBarsPadding()
.padding(horizontal = 40.dp)
.verticalScroll(rememberScrollState())
.safeDrawingPadding(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
...
)
EditNumberField(modifier = Modifier.padding(bottom = 32.dp).fillMaxWidth())
Text(
...
)
...
}
}
A caixa de texto será mostrada na tela.
- No painel Design, aparecem o texto
Calculate Tip
, uma caixa de texto vazia e o elemento combinávelTip Amount
.
4. Usar o estado no Compose
O estado em um app é qualquer valor que pode mudar ao longo do tempo. Neste app, o estado é o valor da conta.
Adicione uma variável para armazenar o estado:
- No início da função
EditNumberField()
, use a palavra-chaveval
para adicionar uma variávelamountInput
e defini-la com o valor"0"
:
val amountInput = "0"
Este é o estado do app para o valor da conta.
- Defina o parâmetro chamado
value
com um valoramountInput
:
TextField(
value = amountInput,
onValueChange = {},
)
- Verificar a visualização. A caixa de texto mostra o valor definido para a variável de estado, como mostrado nesta imagem:
- Execute o app no emulador e tente inserir um valor diferente. O estado fixado no código continua o mesmo, porque o elemento combinável
TextField
não é atualizado automaticamente. Ele muda quando o parâmetrovalue
, definido como a propriedadeamountInput
, é modificado.
A variável amountInput
representa o estado da caixa de texto. Ter um estado fixado no código não é útil, já que ele não pode ser modificado e não reflete a entrada do usuário. É necessário atualizar o estado do app quando o usuário atualiza o valor da conta.
5. A combinação
Os elementos combináveis no app descrevem uma interface que mostra uma coluna com texto, um espaçador e uma caixa de texto. O texto mostra Calculate Tip
, e a caixa de texto mostra um valor 0
ou o valor padrão.
O Compose é um framework de interface declarativo, ou seja, você declara como será a interface no código. Se você quiser que a caixa de texto mostre inicialmente um valor 100
, defina no código o valor inicial dos elementos de composição como 100
.
O que acontece se você quiser que a interface mude enquanto o app está em execução ou quando o usuário interage com ele? Por exemplo, o que acontece quando você atualiza a variável amountInput
com o valor inserido pelo usuário e a mostra na caixa de texto? É então que você precisa usar um processo chamado recomposição para atualizar a composição do app.
A composição é uma descrição da interface criada pelo Compose quando ele executa elementos combináveis. Os apps do Compose chamam funções combináveis para transformar dados em elementos da interface. Se uma mudança de estado ocorrer, o Compose vai executar novamente as funções combináveis afetadas com o novo estado, criando uma interface atualizada. Isso é chamado de recomposição. O Compose programa uma recomposição para você.
Quando o Compose executa seus elementos combináveis pela primeira vez durante a composição inicial, ele acompanha os elementos chamados para descrever a interface em uma composição. A recomposição acontece quando o Compose executa novamente os elementos de composição que foram modificados em resposta a mudanças de dados e, em seguida, atualiza a composição para refletir essas mudanças.
A composição só pode ser produzida por uma composição inicial e atualizada por recomposição. A única maneira de modificar uma composição é pela recomposição. Para fazer isso, o Compose precisa saber qual estado será acompanhado para programar a recomposição ao receber uma atualização. No seu caso, é a variável amountInput
. Então, sempre que o valor mudar, o Compose vai programar uma recomposição.
Você usa os tipos State
e MutableState
no Compose para tornar o estado no app observável ou monitorado pelo Compose. O tipo State
é imutável, ou seja, o valor dele só será lido, enquanto o tipo MutableState
for mutável. Você pode usar a função mutableStateOf()
para criar um MutableState
observável. Ele recebe um valor inicial como um parâmetro envolvido por um objeto State
, que torna o value
dele observável.
O valor retornado pela função mutableStateOf()
:
- mantém o estado, que é o valor da conta;
- é mutável, então o valor pode ser mudado;
- é observável, então o Compose observa as mudanças no valor e aciona uma recomposição para atualizar a interface.
Adicione um estado de custo de serviço:
- Na função
EditNumberField()
, mude a palavra-chaveval
antes da variável de estadoamountInput
para a palavra-chavevar
:
var amountInput = "0"
Isso torna o amountInput
mutável.
- Use o tipo
MutableState<String>
em vez da variávelString
fixada no código para que o Compose saiba que precisa acompanhar o estadoamountInput
e depois transmitir uma string"0"
, que é o valor padrão inicial para a variável de estadoamountInput
:
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
var amountInput: MutableState<String> = mutableStateOf("0")
A inicialização de amountInput
também pode ser programada desta forma com inferência de tipo:
var amountInput = mutableStateOf("0")
A função mutableStateOf()
recebe um valor inicial de "0"
como argumento, fazendo com que o amountInput
seja observável. O resultado é este aviso de compilação no Android Studio, mas isso poderá ser corrigido em breve:
Creating a state object during composition without using remember.
- Na função combinável
TextField
, use a propriedadeamountInput.value
:
TextField(
value = amountInput.value,
onValueChange = {},
modifier = modifier
)
O Compose monitora cada elemento combinável que lê propriedades de estado value
e aciona uma recomposição quando o value
é modificado.
O callback onValueChange
é acionado quando a entrada na caixa de texto é mudada. Na expressão lambda, a variável it
contém o novo valor.
- Na expressão lambda do parâmetro chamado
onValueChange
, defina a propriedadeamountInput.value
como a variávelit
:
@Composable
fun EditNumberField(modifier: Modifier = Modifier) {
var amountInput = mutableStateOf("0")
TextField(
value = amountInput.value,
onValueChange = { amountInput.value = it },
modifier = modifier
)
}
Você está atualizando o estado de TextField
(a variável amountInput
), quando TextField
notifica que há uma mudança no texto usando a função de callback onValueChange
.
- Execute o app e digite o texto. A caixa de texto ainda mostra um valor
0
, como nesta imagem:
Quando o usuário digita na caixa de texto, o callback onValueChange
é chamado, e a variável amountInput
é atualizada com o novo valor. O estado amountInput
é acompanhado pelo Compose. Assim, quando o valor muda, a recomposição é programada, e a função de composição EditNumberField()
é executada novamente. Nessa função de composição, a variável amountInput
é redefinida para o valor inicial de 0
. Assim, a caixa de texto mostra um valor 0
.
Com o código adicionado, as mudanças de estado fazem com que as recomposições sejam programadas.
No entanto, é necessário preservar o valor da variável amountInput
nas recomposições para que ela não seja redefinida como 0
sempre que a função EditNumberField()
é recomposta. Vamos resolver esse problema na próxima seção.
6. Usar a função remember para salvar o estado
Os métodos de composição podem ser chamados várias vezes graças à recomposição. Se não for salvo, o elemento de composição vai redefinir o estado durante a recomposição.
As funções de composição podem armazenar um objeto nas recomposições com a função remember
. Um valor calculado pela função remember
é armazenado na composição durante a composição inicial e é retornado durante a recomposição. Normalmente, as funções remember
e mutableStateOf
são usadas em conjunto nas funções combináveis para que o estado e as atualizações dele sejam refletidos corretamente na interface.
Use a função remember
na EditNumberField()
:
- Na função
EditNumberField()
, inicialize a variávelamountInput
com o delegado de propriedadeby
remember
do Kotlin, envolvendo a chamada para a funçãomutableStateOf
()
comremember
. - Na função
mutableStateOf
()
, transmita uma string vazia em vez de uma string"0"
estática:
var amountInput by remember { mutableStateOf("") }
Agora, a string vazia é o valor padrão inicial da variável amountInput
. by
é uma delegação de propriedade do Kotlin (link em inglês). As funções getter e setter padrão da propriedade amountInput
são delegadas às funções getter e setter da classe remember
, respectivamente.
- Importe estas funções:
import androidx.compose.runtime.remember
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
A adição das importações getter e setter do delegado permite ler e definir amountInput
sem referenciar a propriedade value
do MutableState
.
A função EditNumberField()
atualizada vai ficar assim:
@Composable
fun EditNumberField(modifier: Modifier = Modifier) {
var amountInput by remember { mutableStateOf("") }
TextField(
value = amountInput,
onValueChange = { amountInput = it },
modifier = modifier
)
}
- Execute o app e digite na caixa de texto. O texto digitado é mostrado agora.
7. Estado e recomposição em ação
Nesta seção, você vai definir um ponto de interrupção e depurar a função de composição EditNumberField()
para ver como a composição inicial e a recomposição funcionam.
Defina um ponto de interrupção e depure o app em um emulador ou dispositivo:
- Na função
EditNumberField()
, ao lado do parâmetro chamadoonValueChange
, defina um ponto de interrupção de linha. - No menu de navegação, clique em Debug 'app'. O app será iniciado no emulador ou dispositivo. A execução do app é pausada pela primeira vez quando o elemento
TextField
é criado.
- No painel Debug, clique em Resume Program. A caixa de texto será criada.
- No emulador ou dispositivo, digite uma letra na caixa de texto. A execução do app é pausada novamente quando atinge o ponto de interrupção definido.
Quando você insere o texto, o callback onValueChange
é chamado. Dentro da lambda, it
tem o novo valor que você digitou no teclado.
Depois que o valor de "it" é atribuído a amountInput
, o Compose aciona a recomposição com os novos dados, já que o valor observável mudou.
- No painel Debug, clique em Resume Program. O texto inserido no emulador ou no dispositivo é mostrado ao lado da linha com o ponto de interrupção, conforme mostrado nesta imagem:
Esse é o estado do campo de texto.
- Clique em Resume Program. O valor inserido é exibido no emulador ou no dispositivo.
8. Modificar a aparência
Na seção anterior, você configurou o funcionamento do campo de texto. Nesta, vamos melhorar a interface.
Adicionar um rótulo à caixa de texto
Cada caixa de texto precisa ter um rótulo que informe aos usuários quais informações eles podem inserir. Na primeira parte da imagem de exemplo a seguir, o texto do rótulo fica no meio de um campo de texto e alinhado à linha de entrada. Na segunda parte, o rótulo é movido mais para cima na caixa de texto quando o usuário clica nela. Para saber mais sobre a anatomia do campo de texto, consulte Anatomia (link em inglês).
Modifique a função EditNumberField()
para adicionar um rótulo ao campo de texto:
- Na função de composição
TextField()
da funçãoEditNumberField()
, adicione um parâmetro chamadolabel
definido como uma expressão lambda vazia:
TextField(
//...
label = { }
)
- Na expressão lambda, chame a função
Text()
que aceita umstringResource
(R.string.
bill_amount
)
:
label = { Text(stringResource(R.string.bill_amount)) },
- Na função combinável
TextField()
, adicione o parâmetro chamadosingleLine
como um valortrue
:
TextField(
// ...
singleLine = true,
)
Essa ação condensa a caixa de texto em uma única linha rolável horizontalmente.
- Adicione o parâmetro
keyboardOptions
definido como umKeyboardOptions()
:
import androidx.compose.foundation.text.KeyboardOptions
TextField(
// ...
keyboardOptions = KeyboardOptions(),
)
O Android oferece uma opção para configurar o teclado mostrado na tela para inserir dígitos, endereços de e-mail, URLs, senhas, entre outros. Para saber mais sobre outros tipos de teclado, consulte KeyboardType.
- Defina o tipo de teclado como teclado numérico para inserir dígitos. Transmita a função
KeyboardOptions
com um parâmetro chamadokeyboardType
definido comoKeyboardType.Number
:
import androidx.compose.ui.text.input.KeyboardType
TextField(
// ...
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
)
A função EditNumberField()
concluída vai ficar parecida com este snippet de código:
@Composable
fun EditNumberField(modifier: Modifier = Modifier) {
var amountInput by remember { mutableStateOf("") }
TextField(
value = amountInput,
onValueChange = { amountInput = it },
singleLine = true,
label = { Text(stringResource(R.string.bill_amount)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = modifier
)
}
- Execute o app.
Confira as mudanças no teclado nesta captura de tela:
9. Mostrar o valor da gorjeta
Nesta seção, você vai implementar a funcionalidade principal do app, que é a capacidade de calcular e mostrar o valor da gorjeta.
No arquivo MainActivity.kt
, uma função private
calculateTip()
é fornecida como parte do código inicial. Use essa função para calcular o valor da gorjeta:
private fun calculateTip(amount: Double, tipPercent: Double = 15.0): String {
val tip = tipPercent / 100 * amount
return NumberFormat.getCurrencyInstance().format(tip)
}
No método acima, você está usando NumberFormat
para exibir o formato da gorjeta como moeda.
Agora, seu app pode calcular a gorjeta, mas ela ainda precisa ser formatada e mostrada com a classe.
Usar a função calculateTip()
O texto inserido pelo usuário no elemento de composição de campo de texto é retornado à função de callback onValueChange
como uma String
, mesmo que o usuário tenha inserido um número. Para corrigir isso, você precisa converter o valor amountInput
, que contém a quantia inserida pelo usuário.
- Na função combinável
EditNumberField()
, crie uma nova variável com o nomeamount
após a definição deamountInput
. Chame a funçãotoDoubleOrNull
na variávelamountInput
para converterString
emDouble
:
val amount = amountInput.toDoubleOrNull()
A função toDoubleOrNull()
é uma função predefinida do Kotlin que analisa uma string como um número Double
e retorna o resultado ou null
caso a string não seja uma representação válida de um número.
- No final da instrução, adicione um operador Elvis
?:
que retorne um valor0.0
quando aamountInput
for nula:
val amount = amountInput.toDoubleOrNull() ?: 0.0
- Após a variável
amount
, crie outra variávelval
chamadatip
. Inicialize comcalculateTip()
, transmitindo o parâmetroamount
.
val tip = calculateTip(amount)
A função EditNumberField()
vai ficar parecida com este snippet de código:
@Composable
fun EditNumberField(modifier: Modifier = Modifier) {
var amountInput by remember { mutableStateOf("") }
val amount = amountInput.toDoubleOrNull() ?: 0.0
val tip = calculateTip(amount)
TextField(
value = amountInput,
onValueChange = { amountInput = it },
label = { Text(stringResource(R.string.bill_amount)) },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}
Mostrar o valor da gorjeta calculado
Você programou a função para calcular o valor da gorjeta. A próxima etapa é exibir o valor calculado:
- Na função
TipTimeLayout()
, no final do blocoColumn()
, observe o elemento combinável de texto que exibe$0.00
. Você vai atualizar esse valor para o valor calculado da gorjeta.
@Composable
fun TipTimeLayout() {
Column(
modifier = Modifier
.statusBarsPadding()
.padding(horizontal = 40.dp)
.verticalScroll(rememberScrollState())
.safeDrawingPadding(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
// ...
Text(
text = stringResource(R.string.tip_amount, "$0.00"),
style = MaterialTheme.typography.displaySmall
)
// ...
}
}
Você precisa acessar a variável amountInput
na função TipTimeLayout()
para calcular e mostrar o valor da gorjeta. No entanto, a variável amountInput
é o estado do campo de texto definido na função combinável EditNumberField()
. Ela ainda não pode ser chamada pela função TipTimeLayout()
. Esta imagem ilustra a estrutura do código:
Essa estrutura não permite que você mostre o valor da gorjeta no novo elemento combinável Text
, já que o elemento Text
precisa acessar a variável amount
calculada com a variável amountInput
. Você precisa expor a variável amount
à função TipTimeLayout()
. Esta imagem ilustra a estrutura de código desejada, que faz com que o elemento combinável EditNumberField()
não tenha estado:
Esse padrão é chamado de elevação de estado. Na próxima seção, você vai elevar o estado de um elemento combinável para que ele fique sem estado.
10. Elevação de estado
Nesta seção, você vai aprender onde definir seu estado para reutilizar e compartilhar seus elementos de composição.
Em uma função combinável, você pode definir variáveis que contenham o estado para exibição na interface. Por exemplo, você definiu a variável amountInput
como estado no elemento de composição EditNumberField()
.
Quando o app fica mais complexo e outros elementos de composição precisam de acesso ao estado dentro do elemento EditNumberField()
, é necessário elevar, ou extrair, o estado da função de composição EditNumberField()
.
Elementos de composição com e sem estado
Você vai precisar elevar o estado sempre que for necessário:
- compartilhar o estado com várias funções de composição;
- criar um elemento combinável sem estado para ser reutilizado no app.
Quando você extrai o estado de uma função combinável, a função resultante é chamada de sem estado. Ou seja, as funções combináveis podem ficar sem estado ao terem o estado extraído.
Um elemento combinável sem estado é aquele que não contém nenhum estado, ou seja, que não armazena, define nem modifica um novo estado. Por outro lado, um combinável com estado tem um estado que pode mudar ao longo do tempo.
Elevação de estado é um padrão para mover um estado para cima e fazer com que um componente passe a não ter estado.
Quando aplicado a elementos combináveis, esse processo geralmente introduz dois parâmetros ao elemento:
- Um parâmetro
value: T
, que é o valor atual a ser mostrado. - Um lambda de callback
onValueChange: (T) -> Unit
, que é acionado quando o valor muda para que o estado possa ser atualizado em outro lugar, como quando um usuário digita na caixa de texto.
Eleve o estado na função EditNumberField()
:
- Atualize a definição da função
EditNumberField()
para elevar o estado adicionando os parâmetrosvalue
eonValueChange
:
@Composable
fun EditNumberField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier
) {
//...
O parâmetro value
é do tipo String
, e o onValueChange
é do tipo (String) -> Unit
. Portanto, essa é uma função que usa um valor String
como entrada e não tem valor de retorno. O parâmetro onValueChange
é usado como o callback onValueChange
transmitido no combinável TextField
.
- Na função
EditNumberField()
, atualize a função combinávelTextField()
para usar os parâmetros transmitidos:
TextField(
value = value,
onValueChange = onValueChange,
// Rest of the code
)
- Faça uma elevação, movendo o estado lembrado da função
EditNumberField()
para aTipTimeLayout()
:
@Composable
fun TipTimeLayout() {
var amountInput by remember { mutableStateOf("") }
val amount = amountInput.toDoubleOrNull() ?: 0.0
val tip = calculateTip(amount)
Column(
//...
) {
//...
}
}
- Você elevou o estado para
TipTimeLayout()
. Agora, ele precisa ser transmitido paraEditNumberField()
. Na funçãoTipTimeLayout()
, atualize a chamada de funçãoEditNumberField
()
para usar o estado elevado:
EditNumberField(
value = amountInput,
onValueChange = { amountInput = it },
modifier = Modifier
.padding(bottom = 32.dp)
.fillMaxWidth()
)
Isso deixa EditNumberField
sem estado. Você elevou o estado da interface para seu ancestral, TipTimeLayout()
. TipTimeLayout()
é o proprietário do estado (amountInput
) agora.
Formatação posicional
A formatação posicional é usada para exibir conteúdo dinâmico em strings. Por exemplo, vamos supor que você queira que a caixa de texto Tip amount (Valor da gorjeta) mostre um valor xx.xx
, que pode ser qualquer valor calculado e formatado na sua função. Para fazer isso no arquivo strings.xml
, você precisa definir o recurso de string com um argumento marcador de posição, como este snippet de código:
// No need to copy.
// In the res/values/strings.xml file
<string name="tip_amount">Tip Amount: %s</string>
No código de combinação, você pode ter vários argumentos de marcador e de qualquer tipo. Um marcador string
é %s
.
Observe o elemento de combinável de texto em TipTimeLayout()
. Transmita a gorjeta formatada como um argumento para a função stringResource()
.
// No need to copy
Text(
text = stringResource(R.string.tip_amount, "$0.00"),
style = MaterialTheme.typography.displaySmall
)
- Na função,
TipTimeLayout()
, use a propriedadetip
para exibir o valor da gorjeta. Atualize o parâmetrotext
do elemento combinávelText
para usar a variáveltip
como parâmetro.
Text(
text = stringResource(R.string.tip_amount, tip),
// ...
As funções TipTimeLayout()
e EditNumberField()
concluídas serão semelhantes a este snippet de código:
@Composable
fun TipTimeLayout() {
var amountInput by remember { mutableStateOf("") }
val amount = amountInput.toDoubleOrNull() ?: 0.0
val tip = calculateTip(amount)
Column(
modifier = Modifier
.statusBarsPadding()
.padding(horizontal = 40.dp)
.verticalScroll(rememberScrollState())
.safeDrawingPadding(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = stringResource(R.string.calculate_tip),
modifier = Modifier
.padding(bottom = 16.dp, top = 40.dp)
.align(alignment = Alignment.Start)
)
EditNumberField(
value = amountInput,
onValueChange = { amountInput = it },
modifier = Modifier
.padding(bottom = 32.dp)
.fillMaxWidth()
)
Text(
text = stringResource(R.string.tip_amount, tip),
style = MaterialTheme.typography.displaySmall
)
Spacer(modifier = Modifier.height(150.dp))
}
}
@Composable
fun EditNumberField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier
) {
TextField(
value = value,
onValueChange = onValueChange,
singleLine = true,
label = { Text(stringResource(R.string.bill_amount)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = modifier
)
}
Resumindo, você elevou o estado amountInput
do EditNumberField()
para o elemento de composição TipTimeLayout()
. Para que a caixa de texto funcione como antes, é necessário transmitir dois argumentos à função combinável EditNumberField()
: o valor amountInput
e o callback lambda que atualiza o valor amountInput
com base na entrada do usuário. Essas mudanças permitem calcular a gorjeta da propriedade amountInput
no TipTimeLayout()
para que ela seja mostrada ao usuário.
- Execute o app no emulador ou dispositivo e insira um valor na caixa de texto bill amount (valor da gorjeta). O valor de 15% do valor da conta é mostrado como na imagem:
11. Acessar o código da solução
Para fazer o download do código do codelab concluído, use estes comandos git:
$ 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
Se preferir, você pode fazer o download do 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 (em inglês).
12. Conclusão
Parabéns! Você concluiu este codelab e aprendeu a usar o estado em um app do Compose.
Resumo
- O estado em um app é qualquer valor que pode mudar ao longo do tempo.
- A composição é uma descrição da interface criada pelo Compose quando ele executa elementos combináveis. Os apps do Compose chamam funções combináveis para transformar dados em elementos da interface.
- A composição inicial é uma criação da interface pelo Compose ao executar funções combináveis pela primeira vez.
- A recomposição é o processo de executar novamente os mesmos elementos combináveis para atualizar a árvore quando os dados deles mudam.
- Elevação de estado é um padrão para mover um estado para cima e fazer com que um componente passe a não ter estado.