Conexión a internet
Corrutinas en Android
Las corutinas en Android son una forma de escribir código asíncrono de forma más sencilla y concisa. Las corutinas te permiten ejecutar tareas en segundo plano de forma eficiente y reactiva, sin bloquear el hilo principal de la aplicación. Las corutinas en Android se basan en el concepto de suspensión, que te permite pausar la ejecución de una tarea hasta que se complete una operación asíncrona, como una petición HTTP o una consulta a la base de datos.
Las corutinas en Android se utilizan principalmente para realizar operaciones asíncronas, como peticiones HTTP, consultas a la base de datos y operaciones de E/S. Las corutinas te permiten escribir código asíncrono de forma más sencilla y reactiva, sin tener que utilizar callbacks o interfaces de usuario complejas. Las corutinas en Android se basan en el concepto de suspensión, que te permite pausar la ejecución de una tarea hasta que se complete una operación asíncrona.
Para implementar una clase que se ejecute en segundo plano debe crearse una suspend function, que es una función que puede pausar su ejecución. Para ello, se utiliza la palabra clave suspend
antes de la declaración de la función.
Por ejemplo, el siguiente código muestra un objeto que comienza un conteo en segundo plano y lo actualiza cada segundo:
object Timer {
suspend fun start(callback: (Int) -> Unit) {
var count = 0
while (true) {
delay(1000)
count++
callback(count)
}
}
}
En este ejemplo, la función start
es una suspend function que inicia un conteo en segundo plano y llama a un callback cada segundo con el valor actual del contador.
La función delay
se utiliza para pausar la ejecución de la tarea durante un segundo.
Para llamar a una suspend function desde una función principal, se utiliza la función runBlocking
, que crea un bloque de código que ejecuta la tarea de forma síncrona. Por ejemplo, el siguiente código muestra cómo llamar a la función start
desde una función principal:
fun main() {
runBlocking {
Timer.start { count ->
println("Count: $count")
}
}
}
En este ejemplo, la función main
llama a la función start
de forma síncrona utilizando runBlocking
. La función start
inicia un conteo en segundo plano y llama a un callback cada segundo con el valor actual del contador.
Las corutinas en Android te permiten escribir código asíncrono de forma más sencilla y reactiva, sin tener que utilizar callbacks o interfaces de usuario complejas. Las corutinas se basan en el concepto de suspensión, que te permite pausar la ejecución de una tarea hasta que se complete una operación asíncrona.
La palabra clave suspend en kotlin se utiliza para marcar una función que puede pausar su ejecución y reanudarla más tarde. Las funciones suspendidas se utilizan en las corutinas para realizar operaciones asíncronas de forma reactiva y eficiente.
LaunchedEffect para ejecutar tareas en segundo plano en Jetpack Compose
LaunchedEffect es una función de Jetpack Compose que te permite ejecutar una tarea en segundo plano cuando un composable se coloca en la jerarquía de composición.
Por ejemplo, el siguiente código muestra un composable que muestra un contador que se actualiza cada segundo:
@Composable
fun Timer() {
var count by remember { mutableStateOf(0) }
LaunchedEffect(Unit) {
while (true) {
delay(1000)
count++
}
}
Text(text = "Count: $count")
}
En este ejemplo, el composable Timer
muestra un contador que se actualiza cada segundo. La función LaunchedEffect
se utiliza para iniciar un bucle infinito que actualiza el contador cada segundo. La función delay
se utiliza para pausar la ejecución del bucle durante un segundo.
Alcance de las corutinas en Jetpack Compose
En Jetpack Compose, las corutinas se ejecutan en el alcance de un composable, lo que significa que una corutina se cancela automáticamente cuando el composable se elimina de la jerarquía de composición. Esto garantiza que las corutinas se cancelen de forma segura y eficiente cuando ya no son necesarias, evitando posibles fugas de memoria y problemas de rendimiento.
Por ejemplo, el siguiente código muestra un composable que inicia una corutina cuando se coloca en la jerarquía de composición y la cancela cuando se elimina:
@Composable
fun Timer() {
var count by remember { mutableStateOf(0) }
LaunchedEffect(Unit) {
while (true) {
delay(1000)
count++
}
}
DisposableEffect(Unit) {
onDispose {
// Cancela la corutina cuando el composable se elimina
coroutineContext.cancel()
}
}
Text(text = "Count: $count")
}
En este ejemplo, el composable Timer
inicia una corutina cuando se coloca en la jerarquía de composición utilizando LaunchedEffect
y la cancela cuando se elimina utilizando DisposableEffect
. La función onDispose
se utiliza para realizar tareas de limpieza cuando el composable se elimina, como cancelar la corutina.
CoroutineScope en Jetpack Compose
En Jetpack Compose, puedes utilizar coroutineScope
para crear un alcance de corutina que se cancela automáticamente cuando el composable se elimina de la jerarquía de composición. coroutineScope
es una función de Jetpack Compose que te permite crear un alcance de corutina local que se cancela automáticamente cuando el composable se elimina.
Por ejemplo, el siguiente código muestra un composable que inicia una corutina utilizando coroutineScope
y la cancela cuando se elimina:
@Composable
fun Timer() {
var count by remember { mutableStateOf(0) }
coroutineScope {
launch {
while (true) {
delay(1000)
count++
}
}
}
Text(text = "Count: $count")
}
En este ejemplo, el composable Timer
inicia una corutina utilizando coroutineScope
y la cancela automáticamente cuando el composable se elimina. La función launch
se utiliza para iniciar una corutina en el alcance de coroutineScope
que actualiza el contador cada segundo.
coroutineScope
es una función de Jetpack Compose que te permite crear un alcance de corutina local que se cancela automáticamente cuando el composable se elimina. coroutineScope
es útil para iniciar corutinas en un composable y garantizar que se cancelen de forma segura y eficiente cuando ya no son necesarias.
CoroutineScope en un LaunchedEffect
En Jetpack Compose, puedes utilizar coroutineScope
en un LaunchedEffect
para crear un alcance de corutina local que se cancela automáticamente cuando el composable se elimina de la jerarquía de composición. coroutineScope
es una función de Jetpack Compose que te permite crear un alcance de corutina local que se cancela automáticamente cuando el composable se elimina.
Por ejemplo, el siguiente código muestra un composable que inicia una corutina utilizando coroutineScope
en un LaunchedEffect
y la cancela cuando se elimina:
@Composable
fun Timer() {
var count by remember { mutableStateOf(0) }
LaunchedEffect(Unit) {
coroutineScope {
launch {
while (true) {
delay(1000)
count++
}
}
}
}
Text(text = "Count: $count")
}
En este ejemplo, el composable Timer
inicia una corutina utilizando coroutineScope
en un LaunchedEffect
y la cancela automáticamente cuando el composable se elimina. La función launch
se utiliza para iniciar una corutina en el alcance de coroutineScope
que actualiza el contador cada segundo.
coroutineScope
es una función de Jetpack Compose que te permite crear un alcance de corutina local que se cancela automáticamente cuando el composable se elimina. coroutineScope
es útil para iniciar corutinas en un composable y garantizar que se cancelen de forma segura y eficiente cuando ya no son necesarias.
Un ejemplo de uso podemos encontrarlo en el siguiente Codelab en el que se implemmenta una carrera ficticia entre dos jugadores:
Codelab: Carrera de coches ficticia
El contexto de la corrutina es un objeto que proporciona información sobre la corutina, como su alcance, su trabajo y su estado. El contexto de la corutina se utiliza para gestionar la ejecución de la corutina y proporcionar información sobre su estado y progreso.
LaunchedEffect es una función de Jetpack Compose que te permite ejecutar una tarea en segundo plano cuando un composable se coloca en la jerarquía de composición. LaunchedEffect es útil para realizar operaciones asíncronas, como peticiones HTTP, consultas a la base de datos y operaciones de E/S.
Una de las principales ventajas del uso de LaunchedEffect es que se ejecuta en el alcance de un composable, lo que significa que se cancela automáticamente cuando el composable se elimina de la jerarquía de composición. Esto garantiza que las tareas en segundo plano se cancelen de forma segura y eficiente cuando ya no son necesarias, evitando posibles fugas de memoria y problemas de rendimiento. Esto hace que no sea necesario preocuparse por la cancelación manual de las tareas en segundo plano, ya que Jetpack Compose se encarga de ello de forma automática. Tampoco debemos preocuparnos de proporcionar un componente Dispatcher de forma explícita, ya que LaunchedEffect utiliza el Dispatcher predeterminado de la corrutina para ejecutar la tarea en segundo plano.
Formas de conectarse a internet desde una app
Para conectarse a internet en una aplicación Android con Jetpack Compose, puedes utilizar las siguientes opciones (existen más opciones, pero estas son las más comunes):
-
Retrofit: Retrofit es una biblioteca de cliente HTTP para Android y Java que facilita la conexión a servicios web RESTful. Puedes utilizar Retrofit para realizar peticiones HTTP a un servidor y obtener los datos necesarios para tu aplicación.
-
Ktor: Ktor es un framework de cliente y servidor web en Kotlin que te permite crear aplicaciones web
-
Volley: Volley es una biblioteca de red que facilita la conexión a servicios web en Android. Puedes utilizar Volley para realizar peticiones HTTP y gestionar las respuestas de forma sencilla.
Vamos a centrarnos en la primera opción, Retrofit, que es una de las bibliotecas más utilizadas para conectarse a servicios web en Android.
¿Qué es Retrofit?
Retrofit es una biblioteca de cliente HTTP para Android y Java que facilita la conexión a servicios web RESTful. Retrofit te permite definir una interfaz de servicio web con anotaciones que describen las operaciones disponibles en el servicio, como las peticiones GET, POST, PUT y DELETE. Retrofit se encarga de convertir las respuestas del servidor en objetos Java/Kotlin y de gestionar la comunicación con el servidor de forma eficiente.
- GET: Se utiliza para obtener datos del servidor. Por ejemplo, puedes utilizar una petición GET para obtener una lista de usuarios de un servidor.
- POST: Se utiliza para enviar datos al servidor. Por ejemplo, puedes utilizar una petición POST para enviar un formulario con los datos de un nuevo usuario al servidor.
- PUT: Se utiliza para actualizar datos en el servidor. Por ejemplo, puedes utilizar una petición PUT para actualizar los datos de un usuario existente en el servidor.
- DELETE: Se utiliza para eliminar datos del servidor. Por ejemplo, puedes utilizar una petición DELETE para eliminar un usuario del servidor.
Implementación de Retrofit en Jetpack Compose
Para implementar Retrofit en una aplicación Android con Jetpack Compose, puedes seguir los siguientes pasos:
-
Definir la interfaz de servicio web: Define una interfaz que contenga las operaciones disponibles en el servicio web y anótala con las anotaciones de Retrofit, como
@GET
,@POST
,@PUT
y@DELETE
. Por ejemplo, puedes definir una interfazApiService
que contenga métodos para obtener y enviar datos al servidor. -
Crear una instancia de Retrofit: Crea una instancia de Retrofit utilizando el constructor de
Retrofit.Builder
y configura la URL base del servicio web y el convertidor de JSON. Por ejemplo, puedes crear una instancia de Retrofit que se conecte a un servidor enhttps://api.example.com
y utilice el convertidor de JSON de Gson. -
Crear una instancia del servicio web: Crea una instancia del servicio web a partir de la interfaz de servicio web y la instancia de Retrofit. Por ejemplo, puedes crear una instancia del servicio web a partir de la interfaz
ApiService
y la instancia de Retrofit. -
Realizar peticiones HTTP: Utiliza la instancia del servicio web para realizar peticiones HTTP al servidor y obtener los datos necesarios para tu aplicación. Por ejemplo, puedes utilizar el método
getUsers()
de la interfazApiService
para obtener una lista de usuarios del servidor.
Ejemplo sencillo de implementación
A continuación, se muestra un ejemplo de implementación de Retrofit en una aplicación Android con Jetpack Compose:
// Define la interfaz de servicio web con las operaciones disponibles
interface ApiService {
@GET("users")
suspend fun getUsers(): List<User>
}
// Crea una instancia de Retrofit con la URL base y el convertidor de JSON
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com")
.addConverterFactory(GsonConverterFactory.create())
.build()
// Crea una instancia del servicio web a partir de la interfaz de servicio web y la instancia de Retrofit
val apiService = retrofit.create(ApiService::class.java)
// Realiza una petición HTTP al servidor para obtener una lista de usuarios
val users = apiService.getUsers()
En este ejemplo, se define una interfaz ApiService
con el método getUsers()
que realiza una petición GET al servidor para obtener una lista de usuarios. Se crea una instancia de Retrofit con la URL base https://api.example.com
y el convertidor de JSON de Gson. A continuación, se crea una instancia del servicio web a partir de la interfaz ApiService
y la instancia de Retrofit. Por último, se utiliza la instancia del servicio web para realizar una petición HTTP al servidor y obtener una lista de usuarios.
Con Retrofit, puedes conectarte a servicios web RESTful de forma sencilla y eficiente en una aplicación Android con Jetpack Compose. Retrofit te permite definir una interfaz de servicio web con anotaciones que describen las operaciones disponibles en el servicio y gestionar la comunicación con el servidor de forma automática.
Todo este código suele ordenarse y distribuirse en diferentes archivos y paquetes para mantener una estructura limpia y organizada. A continuación veremos cada uno de estos pasos en detalle.
Implementación de Retrofit en Android
Permiso de Internet
Para conectarse a internet en una aplicación Android, debes añadir el permiso de internet al archivo AndroidManifest.xml
de la aplicación. Puedes hacerlo añadiendo la siguiente línea al archivo AndroidManifest.xml
:
<uses-permission android:name="android.permission.INTERNET" />
Este permiso permite que la aplicación se conecte a internet y realice peticiones HTTP al servidor. Sin este permiso, la aplicación no podrá conectarse a internet y no podrá realizar peticiones HTTP al servidor.
Importar las dependencias de Retrofit
Para utilizar Retrofit en una aplicación Android, primero debes importar las dependencias de Retrofit en el archivo build.gradle
del módulo de la aplicación. Puedes hacerlo añadiendo las siguientes líneas al archivo build.gradle
:
dependencies {
// Retrofit
implementation("com.squareup.retrofit2:retrofit:2.9.0")
// Retrofit with Scalar Converter
implementation("com.squareup.retrofit2:converter-scalars:2.9.0")
}
Estas líneas importan las dependencias de Retrofit y el convertidor de escalares en la aplicación. El convertidor de escalares se utiliza para convertir las respuestas del servidor en cadenas de texto.
Definir la interfaz de servicio web
A continuación, debes definir una interfaz de servicio web que contenga las operaciones disponibles en el servicio.
Puedes hacerlo creando una interfaz en un archivo Kotlin y anotándola con las anotaciones de Retrofit, como @GET
, @POST
, @PUT
y @DELETE
.
Por ejemplo, puedes definir una interfaz ApiService
que contenga métodos para obtener y enviar datos al servidor:
import retrofit2.http.GET
interface ApiService {
@GET("users")
suspend fun getUsers(): List<User>
}
En este ejemplo, la interfaz ApiService
contiene un método getUsers()
que realiza una petición GET al servidor para obtener una lista de usuarios.
La interfaz de servicio web suele ubicarse en un paquete de datos, network o servicios del proyecto para mantener una estructura limpia y organizada.
Las anotaciones de Retrofit se utilizan para describir las operaciones disponibles en el servicio web. Por ejemplo, la anotación @GET
se utiliza para realizar una petición GET al servidor, la anotación @POST
se utiliza para realizar una petición POST al servidor, y así sucesivamente.
Crear una instancia de Retrofit
Una vez que hayas definido la interfaz de servicio web, debes crear una instancia de Retrofit utilizando el constructor de Retrofit.Builder
.
Puedes hacerlo creando una instancia de Retrofit en un archivo Kotlin y configurando la URL base del servicio web y el convertidor de JSON.
Por ejemplo, puedes crear una instancia de Retrofit que se conecte a un servidor en https://api.example.com
y utilice el convertidor de JSON de Gson:
private const val BASE_URL = "https://api.example.com"
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
Para utilizar Gson en tu proyecto, debes importar la dependencia de Gson en el archivo build.gradle
del módulo de la aplicación. Puedes hacerlo añadiendo la siguiente línea al archivo build.gradle
:
dependencies {
// Gson
implementation("com.google.code.gson:gson:2.8.8")
}
En este ejemplo, se crea una instancia de Retrofit con la URL base https://api.example.com
y el convertidor de JSON de Gson.
La instancia de Retrofit suele ubicarse en un archivo Kotlin en el paquete de datos, network o servicios del proyecto para mantener una estructura limpia y organizada.
Retrofit utiliza convertidores para convertir las respuestas del servidor en objetos Java/Kotlin y viceversa.
Puedes utilizar convertidores como Gson, Moshi, Jackson y Scalars para convertir los datos del servidor en objetos Java/Kotlin y viceversa.
Cada uno de estos convertidores tiene sus propias ventajas y desventajas, por lo que debes elegir el que mejor se adapte a tus necesidades.
En este ejemplo, se utiliza el convertidor de Gson para convertir los datos del servidor en objetos Java/Kotlin.
Sin embargo, si prefieres utilizar otro convertidor, puedes cambiar el convertidor de Gson por el convertidor de tu elección.
Por ejemplo, para hacer el mismo ejemplo anterior pero usando Scalars, puedes cambiar la línea de configuración del convertidor de Gson por la siguiente:
.addConverterFactory(ScalarsConverterFactory.create())
Crear una instancia del servicio web
Una vez que hayas creado una instancia de Retrofit, debes crear una instancia del servicio web a partir de la interfaz de servicio web y la instancia de Retrofit.
Puedes hacerlo creando una instancia del servicio web en un archivo Kotlin y utilizando el método create()
de Retrofit.
Por ejemplo, puedes crear una instancia del servicio web a partir de la interfaz ApiService
y la instancia de Retrofit:
val apiService = retrofit.create(ApiService::class.java)
En este ejemplo, se crea una instancia del servicio web a partir de la interfaz ApiService
y la instancia de Retrofit.
Esta instancia puede implementarse a través de un patrón Singleton para garantizar que solo haya una instancia del servicio web en la aplicación, sin embargo, esto dependerá de las necesidades de tu aplicación. En muchas ocasiones es preferible utilizar inyección de dependencias para proporcionar instancias de servicios web en lugar de crearlas manualmente. De esta forma los objetos se pueden reutilizar y se facilita la gestión de dependencias en la aplicación.
Esto se debe a que los Singleton representan estados globales en la aplicación y pueden ser difíciles de gestionar y probar. Por otro lado, la inyección de dependencias permite desacoplar las dependencias de los objetos y facilita la gestión de las dependencias en la aplicación.
Vamos a acostumbrarnos a gestionar las dependencias de esta forma, ya que es una buena práctica de programación y facilita la gestión de dependencias en la aplicación.
Para esto separaremos claramente la capa de datos de la de UI a través de un repositorio o Repository, que será el encargado de gestionar las peticiones al servidor y devolver los datos a la capa de UI.
Este repositorio se encargará de gestionar las peticiones al servidor y devolver los datos a la capa de UI.
Creación de un repositorio
Para separar claramente la capa de datos de la de UI, vamos a crear un repositorio que se encargue de gestionar las peticiones al servidor y devolver los datos a la capa de UI.
Para ello, crearemos un nuevo paquete llamado data
en el que crearemos una clase DataRepository
que contendrá los métodos para obtener y enviar datos al servidor.
Es habitual que esta clase implemente una interfaz que defina los métodos necesarios para interactuar con el servidor, de esta forma se facilita la gestión de dependencias y se mejora la modularidad de la aplicación.
interface DataRepository {
suspend fun getUsers(): List<User>
}
class DataRepositoryImpl(private val apiService: ApiService) : DataRepository {
override suspend fun getUsers(): List<User> {
return apiService.getUsers()
}
}
En este ejemplo, la interfaz DataRepository
define un método getUsers()
que obtiene una lista de usuarios del servidor. La clase DataRepositoryImpl
implementa la interfaz DataRepository
y utiliza la instancia del servicio web para obtener los datos del servidor.
El repositorio suele ubicarse en un paquete de datos o repository del proyecto para mantener una estructura limpia y organizada.
Al implementar el repositorio, nos aseguramos de que la capa de datos esté separada de la de UI y que las operaciones de red se gestionen de forma eficiente y reactiva.
Inyección de dependencias
La inyección de dependencias no es más que un patrón de diseño que se utiliza para desacoplar las dependencias de los objetos y facilitar la gestión de las dependencias en la aplicación.
Para implementar la inyección de dependencias en la aplicación, puedes utilizar una biblioteca de inyección de dependencias como Dagger, Hilt o Koin. Estas bibliotecas te permiten definir módulos que proporcionan instancias de objetos y componentes que gestionan la inyección de dependencias en la aplicación.
Sin embargo, en este caso vamos a utilizar la inyección de dependencias manual para proporcionar instancias de objetos en la aplicación.
Para ello, vamos a crear un contenedor de dependencias dentro del propio paquete de datos que se encargue de proporcionar instancias de objetos en la aplicación.
Es interesante que nuestra clase DataContainer
sea un objeto Singleton que proporcione instancias de objetos en la aplicación. De esta forma, garantizamos que solo haya una instancia del contenedor de dependencias en la aplicación y que los objetos se puedan reutilizar en toda la aplicación.
También debería de heredar de una interfaz que defina los métodos necesarios para proporcionar instancias de objetos en la aplicación.
De esta forma, facilitamos la gestión de dependencias y mejoramos la modularidad de la aplicación.
private const val BASE_URL = "https://api.example.com"
interface DataContainer {
val provideDataRepository: DataRepository
}
object DataContainerImpl : DataContainer {
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
private val apiService by lazy { retrofit.create(ApiService::class.java) }
private val dataRepository = DataRepositoryImpl(apiService)
override val provideDataRepository: DataRepository by lazy { dataRepository }
}
En este ejemplo, la interfaz DataContainer
define un método provideDataRepository
que proporciona una instancia del repositorio de datos en la aplicación. El objeto DataContainerImpl
implementa la interfaz DataContainer
y proporciona una instancia del repositorio de datos utilizando la instancia del servicio web.
El contenedor de dependencias suele ubicarse en un paquete de datos o repository del proyecto para mantener una estructura limpia y organizada.
Al implementar la inyección de dependencias, nos aseguramos de que las dependencias de los objetos se gestionen de forma eficiente y reactiva en la aplicación.
Si en lugar de utilizar un Singleton utilizasemos una clase normal, deberíamos de proporcionar una instancia del contenedor de dependencias en la aplicación para que pueda proporcionar instancias de objetos en la aplicación.
El mismo ejemplo de contenedor anterior pero utilizando una clase quedaría tal que así:
private const val BASE_URL = "https://api.example.com"
interface DataContainer {
val provideDataRepository: DataRepository
}
class DataContainerImpl : DataContainer {
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
private val apiService by lazy { retrofit.create(ApiService::class.java) }
private val dataRepository = DataRepositoryImpl(apiService)
override val provideDataRepository: DataRepository by lazy { dataRepository }
}
La elección de si utilizar un Singleton o una clase normal dependerá de las necesidades de tu aplicación y de cómo quieras gestionar las dependencias en la aplicación.
Adjuntar el contenedor de dependencias a la aplicación
Una vez que hayas creado el contenedor de dependencias, debes adjuntarlo a la aplicación para que pueda proporcionar instancias de objetos en la aplicación.
Para ello, puedes adjuntar el contenedor de dependencias a la aplicación en el archivo Application
de la aplicación.
class MyApplication : Application() {
lateinit var container: DataContainer
override fun onCreate() {
super.onCreate()
container = DataContainerImpl
}
}
En este ejemplo, la clase MyApplication
extiende de Application
y adjunta el contenedor de dependencias a la aplicación en el método onCreate()
.
Si utilizamos una clase para el contenedor en lugar de un objeto Singleton, la implementación sería la siguiente:
class MyApplication : Application() {
lateinit var container: DataContainer
override fun onCreate() {
super.onCreate()
container = DataContainerImpl()
}
}
La clase Application
suele ubicarse en el paquete de la aplicación para mantener una estructura limpia y organizada.
Al adjuntar el contenedor de dependencias a la aplicación, garantizamos que las instancias de objetos se proporcionen de forma eficiente y reactiva en la aplicación.
Debemos también actualizar el archivo AndroidManifest.xml
para que la aplicación utilice la clase MyApplication
como clase de aplicación principal.
<application
android:name=".MyApplication"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.BasicAndroidKotlinCompose"
android:allowBackup="true"
android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/network_security_config"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
En este ejemplo, se actualiza el atributo android:name
de la etiqueta <application>
para que la aplicación utilice la clase MyApplication
como clase de aplicación principal.
Al actualizar el archivo AndroidManifest.xml
, garantizamos que la aplicación utilice la clase MyApplication
como clase de aplicación principal.
Utilizar el repositorio en la capa de UI (En el ViewModel)
Para añadir los datos del servidor a la capa de UI, debes utilizar el repositorio en la capa de UI (en el ViewModel) para obtener los datos del servidor y mostrarlos en la pantalla.
Para ello, en la declaración del ViewModel agregaremos un parámetro al constructor principal, que será privado y del tipo del Repositorio.
Al hacer esto, en la función get del repositorio ya no necesitamos pasarle el parámetro del repositorio, ya que este se inyectará automáticamente en el ViewModel.
Además, ya que el propio framework de Android no nos permite pasar valores a los ViewModel cuándo estos se crean, debemos implementar un objeto ViewModelFactory que se encargue de proporcionar instancias de ViewModel en la aplicación.
Este objeto es una implentación del patrón Factoría o Factory que se encarga de la creación de objetos, podemos utilizarlo para usar el propio contenedor de dependencias para proporcionar instancias de ViewModel en la aplicación.
class MainViewModel(private val repository: DataRepository) : ViewModel() {
var _state = mutableStateOf(MainState())
val state: State<MainState> = _state.asState()
init {
getUsers()
}
private fun getUsers() {
viewModelScope.launch {
val users = repository.getUsers()
_state.value = MainState(users = users)
}
}
companion object {
val Factory: ViewModelProvider.Factory = ViewModelFactory {
initializer {
// [APLICATION_KEY] es una constante que se utiliza para obtener la instancia de la aplicación
val application = this[APLICATION_KEY] as Application
val repository = application.container.provideDataRepository
MainViewModel(repository)
}
}
}
}
En este ejemplo, la clase MainViewModel
extiende de ViewModel
y utiliza el repositorio para obtener los datos del servidor y mostrarlos en la pantalla. La función getUsers()
utiliza el repositorio para obtener una lista de usuarios del servidor y mostrarlos en la pantalla.
Además, se ha añadido un objeto Factory que se encarga de proporcionar instancias de ViewModel en la aplicación.
El ViewModel suele ubicarse en un paquete de viewmodel o ui del proyecto para mantener una estructura limpia y organizada.
La constante APLICATION_KEY
se utiliza para obtener la instancia de la aplicación en el ViewModelFactory. Esta constante se utiliza para acceder a la instancia de la aplicación en el ViewModelFactory y proporcionar instancias de ViewModel en la aplicación.
La palabra reservada companion en Kotlin se utiliza para definir miembros estáticos en una clase.
Los miembros estáticos son miembros de una clase que pertenecen a la clase en lugar de a una instancia de la clase.
En este ejemplo, la palabra reservada companion se utiliza para definir un objeto Factory que proporciona instancias de ViewModel en la aplicación.
Utilizar el ViewModel en la capa de UI (En el Composable)
Para añadir los datos del servidor a la capa de UI, debes utilizar el ViewModel en la capa de UI (en el Composable) para obtener los datos del servidor y mostrarlos en la pantalla.
Para ello, en la declaración del Composable agregaremos un parámetro al constructor principal, que será privado y del tipo del ViewModel.
Al hacer esto, en la función get del ViewModel ya no necesitamos pasarle el parámetro del ViewModel, ya que este se inyectará automáticamente en el Composable.
@Composable
fun MainScreen(viewModel: MainViewModel) {
val state by viewModel.state.collectAsState()
Column {
Text("Users:")
state.users.forEach { user ->
Text(user.name)
}
}
}
En este ejemplo, el composable MainScreen
utiliza el ViewModel para obtener los datos del servidor y mostrarlos en la pantalla. La función collectAsState()
se utiliza para obtener el estado del ViewModel y mostrar los datos en la pantalla.
El Composable suele ubicarse en un paquete de ui del proyecto para mantener una estructura limpia y organizada.
Recursos
-
Codelab: Cómo agregar un repositorio e inyección de dependencias de forma manual
-
Ejercicio resuelto de ejemplo - TriviaApp versión con Retrofit
En el ejemplo de TriviaApp se ha resuelto uno de los ejercicios propuestos, en concreto la rama del repositorio adjunta al enlace anterior hace referencia a una versión del proyecto que utiliza Retrofit para obtener las preguntas de una API externa.
La api de la que se obtienen las preguntas es la siguiente: Open Trivia Database