Calcula una propina personalizada

1. Antes de comenzar

En este codelab, usarás el código de la solución del codelab Introducción al estado en Compose para compilar una calculadora interactiva de propinas que puede calcular y redondear automáticamente un importe de propina cuando ingresas el importe de la factura y el porcentaje de propina. Puedes ver la app final en esta imagen:

89cf25ec4ad0dc37.png

Requisitos previos

  • Haber completado el codelab Introducción al estado en Compose
  • Poder agregar los elementos Text y TextField componibles a una app
  • Conocer la función remember, el estado, la elevación de estado y la diferencia entre las funciones de componibilidad con y sin estado

Qué aprenderás

  • Cómo agregar un botón de acción a un teclado virtual
  • Cómo configurar las acciones del teclado
  • Qué es un elemento Switch componible y cómo usarlo

Qué compilarás

  • Una app de Tip Time que calcula los importes de las propinas según el costo del servicio y el porcentaje de propina ingresados por el usuario

Requisitos

2. Obtén el código de inicio

Para comenzar, descarga el código de inicio:

Descargar ZIP

Como alternativa, puedes clonar el repositorio de GitHub para el código:

$ 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

Puedes explorar el código en el repositorio de GitHub de Tip Calculator.

Mira el video con instrucciones para compilar (opcional)

Si quieres ver cómo uno de los instructores del curso completa el codelab, reproduce el siguiente video.

Este paso es opcional.

3. Descripción general de la app de inicio

Este codelab comienza con la app de Tip Time del codelab anterior Introducción al estado en Compose, que proporciona la interfaz de usuario necesaria para calcular una propina con un porcentaje fijo de propina. El cuadro de texto Cost of Service (costo de servicio) le permite al usuario ingresar el costo del servicio. La app calcula y muestra el importe de la propina en un elemento Text componible.

La imagen muestra el cuadro de texto Cost of Service.

La imagen muestra el cuadro de texto Cost of Service: 100.

Ejecuta la app de Tip Time

  1. Abre el proyecto Tip Time en Android Studio y ejecuta la app en un emulador o dispositivo.
  2. Ingresa un costo de servicio. La app calculará y mostrará el importe de la propina automáticamente.

7b959576dc65d7d8.png

En la implementación actual, el porcentaje de la propina se codifica al 15%. En este codelab, extenderás esta función con un campo de texto que le permite a la app calcular un porcentaje de propina personalizado y redondear el importe.

Agrega los recursos de strings necesarios

  1. En la pestaña Project, haz clic en res > values > strings.xml.
  2. Entre las etiquetas <resources> del archivo strings.xml, agrega estos recursos de strings:
<string name="how_was_the_service">Tip (%)</string>
<string name="round_up_tip">Round up tip?</string>

El archivo strings.xml debería verse como este fragmento de código, que incluye las cadenas del codelab anterior:

strings.xml

<resources>
   <string name="app_name">Tip Time</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>
  1. Cambia Cost of Service por Bill Amount (importe de la factura). En algunos países, servicio significa propina, por lo que este cambio evita confusiones. En la cadena Cost of Service, haz clic con el botón derecho en el name cost_of_service del atributo y selecciona Refactor > Rename. Se abrirá el diálogo Rename….

a2f301b95a8c0e3f.png

  1. En el cuadro de diálogo Rename, reemplaza cost_of_service por bill_amount y haz clic en Refactor. Esto actualiza todos los casos del recurso de strings cost_of_service en el proyecto, por lo que no necesitas cambiar el código de Compose de forma manual.

f525a371c2851d08.png

  1. En el archivo strings.xml, cambia el valor de string a Bill Amount desde Cost of Service:
<string name="bill_amount">Bill Amount</string>
  1. Navega al archivo MainActivity.kt y ejecuta la app. La etiqueta se actualizará en el cuadro de texto como se muestra en esta imagen:

el campo de texto muestra el importe de la factura en lugar del costo de servicio

4. Agrega un campo de texto de porcentaje de propina

Un cliente podría querer una propina mayor o menor según la calidad del servicio proporcionado y otros motivos. Para adecuarse a esto, la app debe permitir que el usuario calcule una propina personalizada. En esta sección, agregarás un campo de texto para que el usuario ingrese un porcentaje de propina personalizado, como se muestra en esta imagen:

572d1e933a38cdcd.png

Ya tienes un elemento componible de campo de texto para Bill Amount en tu app, que es la función de componibilidad sin estado EditNumberField(). En el codelab anterior, elevaste el estado amountInput del elemento EditNumberField() componible al elemento TipTimeScreen() componible, lo que hizo que el elemento EditNumberField() no tuviera estado.

Para agregar un campo de texto, puedes volver a usar el mismo elemento EditNumberField() componible, pero con una etiqueta diferente. Para realizar este cambio, debes pasar la etiqueta como parámetro, en lugar de codificarla dentro de la función de componibilidad EditNumberField().

Haz que la función de componibilidad EditNumberField() sea reutilizable:

  1. En el archivo MainActivity.kt, en los parámetros de la función de componibilidad EditNumberField(), agrega un recurso de strings label de tipo Int:
@Composable
fun EditNumberField(
    label: Int,
    value: String,
    onValueChange: (String) -> Unit
)
  1. Agrega un argumento modifier de tipo Modifier a la función de componibilidad EditNumberField():
@Composable
fun EditNumberField(
    label: Int,
    value: String,
    onValueChange: (String) -> Unit,
    modifier: Modifier = Modifier
)
  1. En el cuerpo de la función, reemplaza el ID de recurso de strings codificado con el parámetro label:
@Composable
fun EditNumberField(
   //...
) {
   TextField(
       //...
       label = { Text(stringResource(label)) },
       //...
   )
}
  1. Para indicar que se espera que el parámetro label sea una referencia de recursos de strings, anota el parámetro de la función con la anotación @StringRes:
@Composable
fun EditNumberField(
   @StringRes label: Int,
   value: String,
   onValueChange: (String) -> Unit,
   modifier: Modifier = Modifier
)
  1. Importa lo siguiente:
import androidx.annotation.StringRes
  1. En la llamada a función EditNumberField() de la función de componibilidad TipTimeScreen(), establece el parámetro label en el recurso de cadenas R.string.bill_amount:
EditNumberField(
   label = R.string.bill_amount,
   value = amountInput,
   onValueChange = { amountInput = it }
)
  1. En el panel Design, haz clic en 2d40b921003ab5eb.png Build & Refresh. La IU de la app debería verse de la siguiente manera:

a84cd50c50235a9f.png

  1. En la función de componibilidad TipTimeScreen(), después de la llamada a función EditNumberField(), agrega otro campo de texto para el porcentaje de propina personalizado. Realiza una llamada a la función de componibilidad EditNumberField() con estos parámetros:
EditNumberField(
   label = R.string.how_was_the_service,
   value = "",
   onValueChange = { }
)

Esto agrega otro cuadro de texto para el porcentaje de propina personalizada.

  1. En el panel Split, haz clic en 2d40b921003ab5eb.png Build & Refresh. La vista previa de la app ahora muestra un campo de texto Tip (%) (porcentaje de propina) como se puede ver en esta imagen:

9d2c01d577d077ae.png

  1. En la parte superior de la función de componibilidad TipTimeScreen(), agrega una propiedad var llamada tipInput para la variable de estado del campo de texto agregado. Usa mutableStateOf("") para inicializar la variable y rodea la llamada con la función remember:
var tipInput by remember { mutableStateOf("") }
  1. En la nueva llamada a función EditNumberField(), configura el parámetro con nombre value para la variable tipInput y, luego, actualiza la variable tipInput en la expresión lambda onValueChange:
EditNumberField(
   label = R.string.how_was_the_service,
   value = tipInput,
   onValueChange = { tipInput = it }
)
  1. En la función TipTimeScreen(), después de la definición de la variable tipInput. Define un elemento val llamado tipPercent que convierta la variable tipInput en un tipo Double. Usa un operador Elvis y muestra 0 si el valor es null. Este valor podría ser null si el campo de texto está vacío
val tipPercent = tipInput.toDoubleOrNull() ?: 0.0
  1. En la función TipTimeScreen(), actualiza la llamada a función calculateTip() y pasa la variable tipPercent como segundo parámetro:
val tip = calculateTip(amount, tipPercent)

El código para la función TipTimeScreen() debería verse de la siguiente manera:

@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
       )
   }
}
  1. Ejecuta la app en un emulador o dispositivo y, luego, ingresa un importe para la factura y el porcentaje de propina. ¿La app calcula el importe correcto de propina?

fbb17b7f66b1047c.png

5. Establece un botón de acción

En el codelab anterior, exploraste cómo usar la clase KeyboardOptions para establecer el tipo de teclado. En esta sección, aprenderás a configurar el botón de acción del teclado con el mismo KeyboardOptions. El botón de acción de teclado es el que se encuentra al final del teclado. Puedes ver algunos ejemplos en esta tabla:

Propiedad

Botón de acción del teclado

ImeAction.Search
Se usa cuando el usuario quiere ejecutar una búsqueda.

La imagen representa el ícono de búsqueda para ejecutar una búsqueda.

ImeAction.Send
Se usa cuando el usuario quiere enviar el texto del campo de entrada.

La imagen representa el ícono de Enviar para enviar el texto del campo de entrada.

ImeAction.Go
Se usa cuando el usuario quiere navegar al destino del texto de la entrada.

La imagen representa el ícono de Ir para navegar al destino del texto de la entrada.

En esta tarea, establecerás dos botones de acción diferentes para los cuadros de texto:

  • Un botón de acción Next (siguiente) para el cuadro de texto Bill Amount, que indica que el usuario ya completó la entrada actual y quiere pasar al siguiente cuadro de texto.
  • Un botón de acción Done (listo) para el cuadro de texto Tip (%), que indica que el usuario terminó de proporcionar la entrada.

Puedes ver ejemplos de teclados con estos botones de acción en las siguientes imágenes:

Agrega opciones del teclado:

  1. En la llamada a función TextField() de la función EditNumberField(), pasa el constructor KeyboardOptions, un argumento con nombre imeAction establecido en un valor ImeAction.Next. Usa la función KeyboardOptions.Default.copy() para usar las otras opciones predeterminadas.
@Composable
fun EditNumberField(
   //...
) {
   TextField(
       //...
       keyboardOptions = KeyboardOptions.Default.copy(
           keyboardType = KeyboardType.Number,
           imeAction = ImeAction.Next
       )
   )
}
  1. Ejecuta la app en un emulador o dispositivo. El teclado ahora muestra el botón de acción Next, como se puede ver en esta imagen:

El teclado muestra el botón de acción Next cuando se selecciona el campo de texto Bill Amount.

El teclado muestra el botón de acción Next cuando se selecciona el campo de texto Tip (%).

Sin embargo, te conviene usar dos botones de acción diferentes para los campos de texto. En breve, solucionarás este problema.

  1. Examina la función EditNumberField(). El parámetro keyboardOptions de la función TextField() está codificado. Si deseas crear botones de acción diferentes para los campos de texto, debes pasar el objeto KeyboardOptions como argumento, y lo harás en el paso siguiente.
// 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
       )
   )
}
  1. En la definición de la función EditNumberField(), agrega un parámetro keyboardOptions de tipo KeyboardOptions. En el cuerpo de la función, asigna el parámetro con nombre keyboardOptions de la función TextField():
@Composable
fun EditNumberField(
   @StringRes label: Int,
   keyboardOptions: KeyboardOptions,
   value: String,
   onValueChange: (String) -> Unit
){
   TextField(
       //...
       keyboardOptions = keyboardOptions
   )
}
  1. En la función TipTimeScreen(), actualiza la primera llamada a función EditNumberField() y pasa el parámetro con nombre keyboardOptions para el campo de texto Bill Amount.
EditNumberField(
   label = R.string.bill_amount,
   keyboardOptions = KeyboardOptions.Default.copy(
       keyboardType = KeyboardType.Number,
       imeAction = ImeAction.Next
   ),
   value = amountInput,
   onValueChange = { amountInput = it }
)
  1. En la segunda llamada a función EditNumberField(), cambia el imeAction del campo de texto Tip (%) a ImeAction.Done. Tu función debería verse como este fragmento de código:
EditNumberField(
   label = R.string.how_was_the_service,
   keyboardOptions = KeyboardOptions.Default.copy(
       keyboardType = KeyboardType.Number,
       imeAction = ImeAction.Done
   ),
   value = tipInput,
   onValueChange = { tipInput = it }
)
  1. Ejecuta la app. Se mostrarán los botones de acción Next y Done, como se puede ver en estas imágenes:

El teclado muestra el botón de acción Next cuando se selecciona el campo de texto Bill Amount.

El teclado muestra el botón de acción Done cuando se selecciona el campo de texto Tip (%).

  1. Ingresa un importe de factura y haz clic en el botón de acción Next. Luego, ingresa un porcentaje de propina y haz clic en el botón de acción Done. No ocurre nada porque aún no agregaste ninguna funcionalidad a los botones. Puedes hacerlo en la próxima sección.

6. Establece acciones del teclado

En esta sección, implementarás la funcionalidad para mover el enfoque al siguiente campo de texto y cerrar el teclado para mejorar la experiencia del usuario con la clase KeyboardActions, que permite a los desarrolladores especificar acciones que se activan en respuesta a la acción de IME (Editor de método de entrada) en el teclado en pantalla. Un ejemplo de una acción de IME es cuando el usuario hace clic en el botón de acción Next o Done.

Implementarás lo siguiente:

  • En la acción Next, mueve el enfoque al siguiente campo de texto [el cuadro de texto Tip (%)].
  • En la acción Done, cierra el teclado virtual.
  1. En la función TipTimeScreen(), agrega una variable val llamada focusManager y asígnale un valor de la propiedad LocalFocusManager.current:
val focusManager = LocalFocusManager.current

La interfaz LocalFocusManager se usa para controlar el enfoque en Compose. Usarás esta variable para mover el enfoque a los cuadros de texto y fuera de ellos.

  1. Importa lo siguiente:
import androidx.compose.ui.platform.LocalFocusManager
  1. En la firma de la función EditNumberField(), agrega un parámetro keyboardActions de tipo KeyboardActions:
@Composable
fun EditNumberField(
   @StringRes label: Int,
   keyboardOptions: KeyboardOptions,
   keyboardActions: KeyboardActions,
   value: String,
   onValueChange: (String) -> Unit
) {
   //...
}
  1. En el cuerpo de la función EditNumberField(), actualiza la llamada a función TextField() y establece el parámetro keyboardActions en el parámetro que se pasó keyboardActions.
@Composable
fun EditNumberField(
   //...
) {
   TextField(
       //...
       keyboardActions = keyboardActions
   )
}

Ahora puedes personalizar los campos de texto con diferentes funciones para cada botón de acción.

  1. En la llamada a función TipTimeScreen(), actualiza la primera llamada a función EditNumberField() para que incluya un parámetro con nombre keyboardActions como argumento nuevo. Asígnale un valor, KeyboardActions( onNext = { } ):
// Bill amount text field
EditNumberField(
   //...
   keyboardActions = KeyboardActions(
       onNext = { }
   ),
   //...
)

La expresión lambda del parámetro onNext nombrado se ejecuta cuando el usuario presiona el botón de acción Next en el teclado.

  1. Define la lambda, solicita FocusManager para mover el enfoque hacia abajo al siguiente elemento componible Tip (%). En la expresión lambda, llama a la función moveFocus() en el objeto focusManager y pasa el argumento FocusDirection.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 }
)

La función moveFocus() mueve el enfoque en la dirección especificada, que se encuentra hacia abajo hasta el campo de texto, en este caso Tip (%).

  1. Importa lo siguiente:
import androidx.compose.ui.focus.FocusDirection
  1. Agrega una implementación similar al campo de texto Tip (%). La diferencia es que debes definir un parámetro llamado onDone, en lugar de onNext.
// Tip% text field
EditNumberField(
   //...
   keyboardActions = KeyboardActions(
       onDone = { }
   ),
   //...
)
  1. Una vez que el usuario ingresa la propina personalizada, la acción Done en el teclado debe borrar el enfoque, lo que, a su vez, cierra el teclado. Define la lambda y solicita FocusManager para borrar el enfoque. En la expresión lambda, llama a la función clearFocus() en el objeto focusManager:
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 }
)

La función clearFocus() borra el enfoque del componente enfocado.

  1. Ejecuta la app. Las acciones del teclado ahora cambian el enfoque del componente, como se puede ver en este GIF:

3164e7a2f39a2d7b.gif

7. Agrega un interruptor

Un interruptor activa o desactiva el estado de un solo elemento. Existen dos estados en un interruptor que permiten al usuario seleccionar entre dos opciones. Un botón de activación consiste en un círculo y una barra, como se puede ver en estas imágenes:

En la imagen, se muestra el botón de activación con las opciones de círculo y barra.

En la imagen, se muestra el botón de activación con las opciones numeradas de círculo y barra.
1. Círculo
2. Barra

El interruptor es un control de selección que se puede usar para ingresar decisiones o declarar preferencias, como los parámetros que se pueden ver en estas imágenes:

a90c4e22e48b30e0.png

El usuario puede arrastrar el círculo hacia adelante y hacia atrás para elegir la opción seleccionada, o simplemente presionar el botón de interruptor. Puedes ver otro ejemplo de un interruptor en este GIF, en el que la configuración de Opciones visuales está activada (Modo oscuro):

91b7bd7a6e02e5ff.gif

Para obtener más información, consulta la documentación sobre interruptores.

Usas el elemento componible Switch para que el usuario pueda elegir si redondea la propina al número entero más cercano como se muestra en la siguiente imagen:

2f041a33215d0664.png

Agrega una fila para los elementos Text y Switch componibles:

  1. Después de la función EditNumberField(), agrega una función de componibilidad RoundTheTipRow() y pasa un Modifier predeterminado, como argumentos similares a la función EditNumberField():
@Composable
fun RoundTheTipRow(modifier: Modifier = Modifier) {
}
  1. Implementa la función RoundTheTipRow(), agrega un elemento de diseño componible Row con el siguiente modifier para establecer el ancho de los elementos secundarios al máximo en la pantalla, centrar la alineación y garantizar un tamaño de 48dp:
Row(
   modifier = modifier
       .fillMaxWidth()
       .size(48.dp),
   verticalAlignment = Alignment.CenterVertically
) {
}
  1. Importa lo siguiente:
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
  1. En el bloque de lambda del elemento de componibilidad de diseño Row, agrega un elemento de componibilidad Text y usa el recurso de strings R.string.round_up_tip para mostrar una string Round up tip?:
Text(text = stringResource(R.string.round_up_tip))
  1. Después del elemento Text que admite composición, agrega un elemento Switch que también admite composición, pasa un parámetro con nombre checked establecido en roundUp y un parámetro con nombre onCheckedChange establecido en onRoundUpChanged.
Switch(
    checked = roundUp,
    onCheckedChange = onRoundUpChanged,
)

Esta tabla contiene información sobre estos parámetros, que son los mismos que definiste para la función RoundTheTipRow():

Parámetro

Descripción

checked

Indica si el interruptor está marcado. Este es el estado del elemento que admite composición Switch.

onCheckedChange

Es la devolución de llamada a la que se llamará cuando se haga clic en el interruptor.

  1. Importa lo siguiente:
import androidx.compose.material.Switch
  1. En la función RoundTheTipRow(), agrega un parámetro roundUp de tipo Boolean y una función lambda onRoundUpChanged que tome un Boolean, pero no muestre nada:
@Composable
fun RoundTheTipRow(
   roundUp: Boolean,
   onRoundUpChanged: (Boolean) -> Unit,
   modifier: Modifier = Modifier
)

Con esta acción, se eleva el estado del interruptor.

  1. En el elemento de componibilidad Switch, agrega este modifier para alinear el elemento de componibilidad Switch al final de la pantalla:
       Switch(
           modifier = modifier
               .fillMaxWidth()
               .wrapContentWidth(Alignment.End),
           //...
       )
  1. Importa lo siguiente:
import androidx.compose.foundation.layout.wrapContentWidth
  1. En la función TipTimeScreen(), agrega una variable var para el estado del elemento componible Switch. Crea una variable var llamada roundUp y establécela en mutableStateOf(), con false como valor inicial. Rodea la llamada con remember { }.
fun TipTimeScreen() {
   //...
   var roundUp by remember { mutableStateOf(false) }

   //...
   Column(
       ...
   ) {
     //...
  }
}

Esta es la variable del estado componible Switch y el valor falso será el predeterminado.

  1. En el bloque Column de la función TipTimeScreen(), después del campo de texto Tip (%), llama a la función RoundTheTipRow() con los siguientes argumentos: un parámetro con nombre roundUp establecido en roundUp y un parámetro con nombre onRoundUpChanged establecido en una devolución de llamada lambda que actualice el valor roundUp:
@Composable
fun TipTimeScreen() {
   //...

   Column(
       ...
   ) {
       Text(
           ...
       )
       Spacer(...)
       EditNumberField(
           ...
       )
       EditNumberField(
           ...
       )
       RoundTheTipRow(roundUp = roundUp, onRoundUpChanged = { roundUp = it })
       Spacer(...)
       Text(
           ...
       )
   }
}

Se mostrará la fila Round up tip? (¿Quieres redondear la propina?).

  1. Ejecuta la app. Esta muestra el botón de activación Round up tip?, pero el círculo es apenas visible, como se ve en esta imagen:

Interruptores no seleccionados y seleccionados con números que identifican sus 2 elementos y estados
1. Círculo
2. Barra

En los próximos pasos, mejorarás la visibilidad del círculo volviéndolo gris oscuro.

  1. En el elemento que admite composición Switch() de la función RoundTheTipRow(), agrega un parámetro llamado colors.
  2. Configura ese parámetro colors en una función SwitchDefaults.colors() que acepte un parámetro llamado uncheckedThumbColor establecido en un argumento Color.DarkGray.
Switch(
   //...
   colors = SwitchDefaults.colors(
       uncheckedThumbColor = Color.DarkGray
   )
)
  1. Importa lo siguiente:
import androidx.compose.material.SwitchDefaults
import androidx.compose.ui.graphics.Color

La función de componibilidad RoundTheTipRow() ahora debería verse como este fragmento de código:

@Composable
fun RoundTheTipRow(roundUp: Boolean, onRoundUpChanged: (Boolean) -> Unit, modifier: Modifier = Modifier) {
   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
           )
       )
   }
}
  1. Ejecuta la app. Como puedes ver en la imagen, el círculo del interruptor es de otro color:

23ef96fe41888fe9.png

  1. Ingresa un importe de la factura y un porcentaje de propina y selecciona el botón de activación Round up tip?. El importe de la propina no se redondea porque todavía necesitas actualizar la función calculateTip(); lo haremos en la siguiente sección.

Actualiza la función calculateTip() para redondear la propina.

Modifica la función calculateTip() de modo que acepte una variable Boolean para redondear la propina al número entero más cercano:

  1. Para redondear la propina, la función calculateTip() debe conocer el estado del interruptor, que es un Boolean. En la función calculateTip(), agrega un parámetro roundUp de tipo Boolean:
private fun calculateTip(
   amount: Double,
   tipPercent: Double = 15.0,
   roundUp: Boolean
): String {
   //...
}
  1. En la función calculateTip(), antes de la sentencia return, agrega una condición if() que verifique el valor roundUp. Si roundUp es true, define una variable tip, establécela en kotlin.math.ceil() y pasa la función tip como argumento:
if (roundUp)
   tip = kotlin.math.ceil(tip)

La función calculateTip() completa debería verse como este fragmento 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)
}
  1. En la función TipTimeScreen(), actualiza la llamada a la función calculateTip() y pasa un parámetro roundUp:
val tip = calculateTip(amount, tipPercent, roundUp)
  1. Ejecuta la app. Ahora, esta redondea el importe de la propina, como puedes ver en las siguientes imágenes:

En las imágenes, se muestra que el importe de la propina no se redondea hacia arriba.

En las imágenes, se muestra que el importe de la propina se redondea hacia arriba.

8. Obtén el código de la solución

Para descargar el código del codelab terminado, puedes usar este comando de git:

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

También puedes descargar el repositorio como un archivo ZIP, descomprimirlo y abrirlo en Android Studio.

Descargar ZIP

Si deseas ver el código de la solución, puedes hacerlo en GitHub.

9. Conclusión

¡Felicitaciones! Agregaste la funcionalidad de propina personalizada a tu app de Tip Time. Ahora, la app permite que los usuarios ingresen un porcentaje de propina personalizado y redondeen el importe correspondiente. Comparte tu trabajo en redes sociales con el hashtag #AndroidBasics.

Más información