1. Antes de comenzar
Introducción
Hasta ahora, aprendiste todo sobre la compilación de apps para Android con Compose, lo cual es algo bueno. Compose es una herramienta muy potente que puede simplificar el proceso de desarrollo, pero las apps para Android no siempre se compilaron con IUs declarativas. Compose es una herramienta muy reciente en la historia de las apps para Android. Originalmente, las IUs de Android se compilaban con objetos View. Por lo tanto, es muy probable que te encuentres con ellos a medida que avanzas en tu camino como desarrollador de Android. En este codelab, aprenderás los conceptos básicos sobre la compilación de apps para Android antes de Compose: con XML, objetos View, y fragmentos y vinculación de vistas.
Requisitos previos:
- Haber completado el trabajo de curso de Aspectos básicos de Android con Compose hasta la unidad 7
Requisitos
- Una computadora con acceso a Internet y Android Studio instalado
- Un dispositivo o emulador
- El código de partida de la app de Juice Tracker
Qué compilarás
En este codelab, completarás la app de Juice Tracker. Esta app te permite hacer un seguimiento de los jugos más populares con la compilación de una lista de elementos detallados. Agregarás y modificarás fragmentos y XML para completar la IU y el código de partida. Específicamente, compilarás el formulario de entrada para crear un nuevo jugo, incluidas la IU y cualquier lógica o navegación asociadas. El resultado es una app con una lista vacía a la que puedes agregar tus propios jugos.
2. Obtén el código de partida
- En Android Studio, abre la carpeta
basic-android-kotlin-compose-training-juice-tracker
. - Abre el código de la app de Juice Tracker en Android Studio.
3. Crea un diseño
Cuando compilas una app con Views
, creas la IU dentro de un elemento Layout. Por lo general, los diseños se declaran con archivos de diseño XML que se encuentran en el directorio de recursos, en res > layout. Estos contienen los componentes de la IU, que se conocen como View
. La sintaxis XML consiste en etiquetas, elementos y atributos. Para obtener más información de la sintaxis XML, consulta el codelab Cómo crear diseños XML para Android.
En esta sección, compilarás un diseño XML para el diálogo de entrada "Type of juice" (Tipo de jugo) que se ve en la imagen.
- Crea un nuevo archivo de recursos de diseño en el directorio main > res > layout llamado
fragment_entry_dialog
.
El diseño del elemento fragment_entry_dialog.xml
contiene los componentes de la IU que la app mostrará al usuario.
Ten en cuenta que el elemento raíz es un ConstraintLayout
. Este tipo de diseño es un ViewGroup
que te permite posicionar y ajustar el tamaño de los objetos View de manera flexible dentro de las restricciones. Un ViewGroup
es un tipo de View
que contiene otras View
, llamadas View
s secundarias. En los siguientes pasos, se aborda este tema con más detalle, pero puedes obtener más información sobre ConstraintLayout
en Cómo compilar una IU responsiva con ConstraintLayout.
- Después de crear el archivo, define el espacio de nombres de la app en
ConstraintLayout
.
fragment_entry_dialog.xml
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>
- Agrega los siguientes lineamientos a
ConstraintLayout
.
fragment_entry_dialog.xml
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="16dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_middle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_top"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="16dp" />
Estos elementos Guideline
funcionarán como relleno para otros objetos View. Los lineamientos limitan el texto del encabezado "Type of juice" (tipo de jugo).
- Crea un elemento
TextView
. EsteTextView
representa el título del fragmento de detalles.
- Establece
TextView
enid
deheader_title
. - Establece
layout_width
en0dp
. En última instancia, las restricciones del diseño definen el ancho de este elementoTextView
. Por lo tanto, definir un ancho solo agrega cálculos innecesarios durante el diseño de la IU; definir un ancho de0dp
evita los cálculos adicionales. - Establece el atributo
TextView text
en@string/juice_type
. - Establece la
textAppearance
en@style/TextAppearance.MaterialComponents.Headline5
.
fragment_entry_dialog.xml
<TextView
android:id="@+id/header_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/juice_type"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5" />
Por último, define las restricciones. A diferencia de los Guideline
, que usan dimensiones como restricciones, los lineamientos en sí limitan estos elementos TextView
. Para lograr este resultado, puedes hacer referencia al ID de Guideline
con el que deseas restringir la vista.
- Restringe la parte superior del encabezado a la parte inferior de
guideline_top
.
fragment_entry_dialog.xml
<TextView
android:id="@+id/header_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/juice_type"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
app:layout_constraintTop_toBottomOf="@+id/guideline_top" />
- Restringe el final al inicio de
guideline_middle
y el principio al inicio deguideline_left
para completar el posicionamiento deTextView
. Ten en cuenta que la manera de restringir una vista determinada depende por completo de cómo quieres que se vea la IU.
fragment_entry_dialog.xml
<TextView
android:id="@+id/header_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/juice_type"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
app:layout_constraintTop_toBottomOf="@+id/guideline_top"
app:layout_constraintEnd_toStartOf="@+id/guideline_middle"
app:layout_constraintStart_toStartOf="@+id/guideline_left" />
Intenta compilar el resto de la IU con estas capturas de pantalla como modelo. Puedes encontrar el archivo fragment_entry_dialog.xml
completo en la solución.
4. Crea un fragmento con objetos View
En Compose, puedes compilar diseños de forma declarativa con Kotlin o Java. Puedes navegar a diferentes elementos componibles, por lo general, dentro de la misma actividad, para acceder a diferentes "pantallas". Cuando compilas una app con objetos View, el concepto de "pantalla" componible se reemplaza con un fragmento que aloja el diseño XML.
En esta sección, crearás un Fragment
para alojar el diseño de fragment_entry_dialog
y proporcionar datos a la IU.
- En el paquete
juicetracker
, crea una nueva clase llamadaEntryDialogFragment
. - Haz que
EntryDialogFragment
extienda elBottomSheetDialogFragment
.
EntryDialogFragment.kt
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
class EntryDialogFragment : BottomSheetDialogFragment() {
}
El DialogFragment
es un Fragment
que muestra un diálogo flotante. El BottomSheetDialogFragment
hereda de la clase DialogFragment
, pero muestra una hoja con el ancho de la pantalla fijado a su parte inferior. Este enfoque coincide con el diseño que se mostró anteriormente.
- Vuelve a compilar el proyecto. Esto causará que los archivos de vinculación de vista basados en el diseño
fragment_entry_dialog
se generen automáticamente. Las vinculaciones de vista te permiten acceder a objetosView
declarados en formato XML, además de interactuar con ellos. Para obtener más información al respecto, consulta la documentación sobre la vinculación de vistas. - En la clase
EntryDialogFragment
, implementa la funciónonCreateView()
. Como su nombre lo indica, esta función crea el elementoView
para elFragment
.
EntryDialogFragment.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return super.onCreateView(inflater, container, savedInstanceState)
}
La función onCreateView()
muestra una View
, pero, por el momento, no es una View
útil.
- Muestra la
View
que se genera con el aumento de laFragmentEntryDialogViewBinding
en lugar de mostrarsuper.onCreateView()
.
EntryDialogFragment.kt
import com.example.juicetracker.databinding.FragmentEntryDialogBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return FragmentEntryDialogBinding.inflate(inflater, container, false).root
}
- Crea una instancia de
EntryViewModel
fuera de la funciónonCreateView()
, pero dentro de la claseEntryDialogFragment
. - Implementa la función
onViewCreated()
.
Después de aumentar la vinculación de vista, puedes acceder a las View
del diseño y modificarlas. Se llama al método onViewCreated()
después de onCreateView()
en el ciclo de vida. El método onViewCreated()
es el lugar recomendado para acceder a las View
y modificarlas dentro del diseño.
- Llama al método
bind()
enFragmentEntryDialogBinding
para crear una instancia de vinculación de vista.
En este punto, tu código debería verse como el siguiente ejemplo:
EntryDialogFragment.kt
import androidx.fragment.app.viewModels
import com.example.juicetracker.ui.AppViewModelProvider
import com.example.juicetracker.ui.EntryViewModel
class EntryDialogFragment : BottomSheetDialogFragment() {
private val entryViewModel by viewModels<EntryViewModel> { AppViewModelProvider.Factory }
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return FragmentEntryDialogBinding.inflate(inflater, container, false).root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val binding = FragmentEntryDialogBinding.bind(view)
}
}
Puedes acceder a los objetos View desde la vinculación y configurarlos. Por ejemplo, puedes establecer un elemento TextView
con el método setText()
.
binding.name.setText("Apple juice")
En la IU del diálogo de entrada, el usuario puede crear un nuevo elemento, pero también puedes usarla para modificar un elemento existente. Por lo tanto, el fragmento debe recuperar un elemento en el que se hizo clic. El componente de Navigation facilita la navegación al EntryDialogFragment
y la recuperación de un elemento en el que se hizo clic.
La EntryDialogFragment
aún no está completa, pero no te preocupes. Por ahora, pasa a la siguiente sección para obtener más información para usar el componente de Navigation en una app con View
.
5. Modifica el componente de Navigation
En esta sección, usarás el componente de navegación para iniciar el diálogo de entrada y recuperar un elemento, si corresponde.
Compose ofrece la oportunidad de renderizar diferentes elementos componibles con solo llamarlos. Sin embargo, los fragmentos funcionan de manera diferente. El componente de Navigation coordina los "destinos" del fragmento, lo que proporciona una manera fácil de desplazarse entre diferentes fragmentos y los objetos View que contienen.
Usa el componente de Navigation para coordinar la navegación a tu EntryDialogFragment
.
- Abre el archivo
nav_graph.xml
y asegúrate de que esté seleccionada la pestaña Design. - Haz clic en el ícono para agregar un destino nuevo.
- Selecciona el destino
EntryDialogFragment
. Esta acción declara elentryDialogFragment
en el gráfico de navegación para que las acciones de navegación puedan acceder a él.
Debes iniciar el EntryDialogFragment
desde TrackerFragment
. Por lo tanto, una acción de navegación debe realizar esta tarea.
- Arrastra el cursor sobre el
trackerFragment
. Selecciona el punto gris y arrastra la línea alentryDialogFragment
. - La vista de diseño de nav_graph te permite declarar argumentos para un destino. Para ello, selecciona el destino y haz clic en el ícono junto al menú desplegable Arguments. Usa esta función para agregar un argumento
itemId
de tipoLong
aentryDialogFragment
; el valor predeterminado debe ser0L
.
Ten en cuenta que el TrackerFragment
contiene una lista de elementos Juice
. Si haces clic en uno de ellos, se iniciará el EntryDialogFragment
.
- Vuelve a compilar el proyecto. Ahora se puede acceder al argumento
itemId
desde elEntryDialogFragment
.
6. Completa el fragmento
Con los datos de los argumentos de navegación, completa el diálogo de entrada.
- Recupera los
navArgs()
en el métodoonViewCreated()
delEntryDialogFragment
. - Recupera el
itemId
de losnavArgs()
. - Implementa un
saveButton
para guardar el jugo nuevo o modificado con elViewModel
.
Recuerda que el valor predeterminado del color de la IU del diálogo de entrada es rojo. Por ahora, pasa esto como un marcador de posición.
Pasa el ID de elemento de los argumentos cuando llames a saveJuice()
.
EntryDialogFragment.kt
import androidx.navigation.fragment.navArgs
import com.example.juicetracker.data.JuiceColor
class EntryDialogFragment : BottomSheetDialogFragment() {
//...
var selectedColor: JuiceColor = JuiceColor.Red
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val binding = FragmentEntryDialogBinding.bind(view)
val args: EntryDialogFragmentArgs by navArgs()
val juiceId = args.itemId
binding.saveButton.setOnClickListener {
entryViewModel.saveJuice(
juiceId,
binding.name.text.toString(),
binding.description.text.toString(),
selectedColor.name,
binding.ratingBar.rating.toInt()
)
}
}
}
- Después de guardar los datos, descarta el diálogo con el método
dismiss()
.
EntryDialogFragment.kt
class EntryDialogFragment : BottomSheetDialogFragment() {
//...
var selectedColor: JuiceColor = JuiceColor.Red
//...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val binding = FragmentEntryDialogBinding.bind(view)
val args: EntryDialogFragmentArgs by navArgs()
binding.saveButton.setOnClickListener {
entryViewModel.saveJuice(
juiceId,
binding.name.text.toString(),
binding.description.text.toString(),
selectedColor.name,
binding.ratingBar.rating.toInt()
)
dismiss()
}
}
}
Ten en cuenta que el código anterior no completa el EntryDialogFragment
. Aún debes implementar una serie de elementos, como la propagación de los campos con datos de Juice
existentes (si corresponde), la selección de un color de colorSpinner
y la implementación de cancelButton
, entre otras tareas. Sin embargo, este código no es exclusivo de los elementos Fragment
, y puedes implementarlo por tu cuenta. Intenta implementar el resto de la funcionalidad. Como último recurso, puedes consultar el código de la solución de este codelab.
7. Inicia el diálogo de entrada
La última tarea es usar el componente de Navigation para iniciar el diálogo de entrada. El diálogo de entrada debe iniciarse siempre que el usuario haga clic en el botón de acción flotante (BAF). También debe iniciarse y pasar el ID correspondiente cuando el usuario hace clic en un elemento.
- En el
onClickListener()
del BAF, llama anavigate()
en el controlador de navegación.
TrackerFragment.kt
import androidx.navigation.findNavController
//...
binding.fab.setOnClickListener { fabView ->
fabView.findNavController().navigate(
)
}
//...
- En la función de navegación, pasa la acción para navegar del rastreador al diálogo de entrada.
TrackerFragment.kt
//...
binding.fab.setOnClickListener { fabView ->
fabView.findNavController().navigate(
TrackerFragmentDirections.actionTrackerFragmentToEntryDialogFragment()
)
}
//...
- Repite esta acción en el cuerpo de lambda para el método
onEdit()
en elJuiceListAdapter
, pero esta vez, pasa elid
deJuice
.
TrackerFragment.kt
//...
onEdit = { drink ->
findNavController().navigate(
TrackerFragmentDirections.actionTrackerFragmentToEntryDialogFragment(drink.id)
)
},
//...
8. Obtén el código de la solución
Para descargar el código del codelab terminado, puedes usar estos comandos de git:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker.git $ cd basic-android-kotlin-compose-training-juice-tracker $ git checkout views
También puedes descargar el repositorio como un archivo ZIP, descomprimirlo y abrirlo en Android Studio.
Si deseas ver el código de la solución, puedes hacerlo en GitHub.