WhatsApp

  

Corrutinas en Android Studio

Generalmente se dice que las corrutinas son un patrón de diseño de simultaneidad que puedes usar en Android para simplificar el código que se ejecuta de forma asíncrona.
Estas corrutinas te ayudan a administrar tareas de larga duración que, de lo contrario, podrían bloquear el subproceso principal y hacer que tu app deje de responder. Más del 50% de los desarrolladores profesionales que usan corrutinas informaron que vieron un aumento en la productividad. En este blog, aprenderás cómo puedes usar las corrutinas de Kotlin para solucionar estos problemas, lo que te permitirá escribir código de apps más limpio y conciso.

Como de costumbre, tienes que agregar la dependencia en el archivo build.gradle del módulo app: 


dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")
}

Cuando haces una solicitud en la red en el subproceso principal, este espera o se bloquea hasta recibir una respuesta.

Considera el siguiente ejemplo:


sealed class Result<out R> {
    data class Success<out T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
}
class LoginRepository(private val responseParser: LoginResponseParser) {
    private const val loginUrl = "https://example.com/login"
    fun loginRequest(
        jsonBody: String
    ): Result<LoginResponse> {
        val url = URL(loginUrl)
        val conn = url.openConnection() as? HttpURLConnection
        return conn?.run {
            requestMethod = "POST"
            setRequestProperty("Content-Type", "application/json; utf-8")
            setRequestProperty("Accept", "application/json")
            doOutput = true
            outputStream.use { it.write(jsonBody.toByteArray()) }
            Result.Success(responseParser.parse(inputStream))
        } ?: Result.Error(Exception("Cannot open HttpURLConnection"))
    }
}

En ese ejemplo, cuando el subproceso está bloqueado, el sistema operativo no puede llamar a onDraw(), lo que puede provocar que tu app se bloquee y no responda.
Para hacer esto más digerible para el usuario, debes ejecutar estas operaciones en un subproceso en segundo plano.

Observa el ejemplo anterior:
La clase makeLoginRequest es síncrona y bloquea el subproceso de llamada. Para modelar la respuesta de la solicitud de red, defines tu propia clase Result.

El ViewModel activa la solicitud de red cuando el usuario hace clic, por ejemplo, en un botón:


class LoginViewModel(
    private val loginRepository: LoginRepository
) : ViewModel() {
    fun login(username: String, token: String) {
        val jsonBody = "{ username: \"$username\", token: \"$token\"}"
        loginRepository.makeLoginRequest(jsonBody)
    }
}

Con el código anterior, el LoginViewModel bloquea el subproceso de IU cuando se realiza la solicitud de red.

La solución más simple para quitar la ejecución del subproceso principal consiste en crear una nueva corrutina y ejecutar la solicitud de red en un subproceso de E/S:


class LoginViewModel(
    private val loginRepository: LoginRepository
) : ViewModel() {
    fun login(username: String, token: String) {
        // Creas una nueva corrutina para mover la ejecución fuera del hilo de IU
        viewModelScope.launch(Dispatchers.IO) {
            val jsonBody = "{ username: \"$username\", token: \"$token\"}"
            loginRepository.makeLoginRequest(jsonBody)
        }
    }
}

Analicemos el código de corrutinas en la función login:

  • viewModelScope es un CoroutineScope predefinido que se incluye con las extensiones KTX de ViewModel. Ten en cuenta que todas las corrutinas deben ejecutarse en un alcance. CoroutineScope administra una o más corrutinas relacionadas.

  • launch es una función que crea una corrutina y despacha la ejecución de sus funciones al despachador correspondiente.

  • Dispatchers.IO indica que esta corrutina debe ejecutarse en un subproceso reservado para operaciones de E/S.

La función login se ejecuta de la siguiente manera:

  • La app llama a la función login desde la capa View del subproceso principal.

  • launch crea una nueva corrutina y se realiza la solicitud de red de forma independiente en un subproceso reservado para las operaciones de E/S.

  • Mientras se ejecuta la corrutina, la función login continúa su ejecución y se muestra antes de que finalice la solicitud de red. Ten en cuenta que, para simplificar el proceso, se ignora por ahora la respuesta de la red.

Dado que esta corrutina se inicia con viewModelScope, se ejecuta en el alcance de ViewModel. Si se destruye el ViewModel porque el usuario se aleja de la pantalla, se cancela automáticamente viewModelScope, y todas las corrutinas en ejecución también se cancelan.

En el ejemplo anterior, el problema radica en que cualquier llamada a makeLoginRequest debe recordar quitar la ejecución de manera explícita del subproceso principal. Para resolver este problema, debes modificar el Repository.

Considera que una función es segura para el subproceso principal cuando no bloquea las actualizaciones de la IU en este subproceso. La función makeLoginRequest no es segura, ya que, cuando llaman a makeLoginRequest desde el subproceso principal, se bloquea la IU. Usen la función withContext() de la biblioteca de corrutinas para trasladar la ejecución de una corrutina a un subproceso diferente:


class LoginRepository(...) {
    ...
    suspend fun makeLoginRequest(
        jsonBody: String
    ): Result<LoginResponse> {
        // Trasladen la ejecución de la corrutina al despachador de E/S
        return withContext(Dispatchers.IO) {
            // Código bloqueante de la solicitud de red
        }
    }
}

withContext(Dispatchers.IO) traslada la ejecución de la corrutina a un subproceso de E/S, lo que hace que su función de llamada sea segura y permita que la IU se actualice según sea necesario.

La clase makeLoginRequest también está marcada con la palabra clave suspend, que es la forma en que Kotlin indica que una función se ejecuta desde una corrutina.
Nota: Para realizar pruebas con más facilidad, les recomendamos inyectar Dispatchers en la capa Repository. Para obtener más información, consulten
Testing coroutines on Android (Cómo probar corrutinas en Android).

En el siguiente ejemplo, la corrutina se crea en el LoginViewModel. A medida que makeLoginRequest quita la ejecución del subproceso principal, pueden ejecutar la corrutina de la función login en el subproceso principal:


class LoginViewModel(
    private val loginRepository: LoginRepository
) : ViewModel() {
    fun login(username: String, token: String) {
        // Creen una nueva corrutina en el hilo de IU
        viewModelScope.launch {
            val jsonBody = "{ username: \"$username\", token: \"$token\"}"
            // Realicen la llamada de red y suspendan la ejecución hasta que termine
            val result = loginRepository.makeLoginRequest(jsonBody)
            // Muestren el resultado de la solicitud de red al usuario
            when (result) {
                is Result.Success<LoginResponse> -> // Caso exitoso
                else -> // Muestren error en la IU
            }
        }
    }
}

Tengan en cuenta que la corrutina sigue siendo necesaria, ya que makeLoginRequest es una función suspend y todas las funciones suspend deben ejecutarse en una corrutina.

Este código tiene las siguientes diferencias respecto al ejemplo anterior:

  • launch no toma un parámetro Dispatchers.IO. Cuando no pasan un Dispatcher a launch, cualquier corrutina iniciada desde viewModelScope se ejecuta en el subproceso principal.

  • Ahora, el resultado de la solicitud de red se utiliza para mostrar la IU de éxito o error.

La función de acceso ahora se ejecuta de la siguiente manera:

  1. La app llama a la función login() desde la capa View del subproceso principal.

  2. launch crea una corrutina nueva en el subproceso principal y esta comienza a ejecutarse.

  3. Dentro de la corrutina, la llamada a loginRepository.makeLoginRequest() suspende la ejecución hasta que el bloque withContext de makeLoginRequest() termina de ejecutarse.

  4. Una vez que finaliza el bloque withContext, la corrutina de login() reanuda la ejecución en el subproceso principal con el resultado de la solicitud de red.

Nota: Para comunicarse con View desde la capa ViewModel, usen LiveData como se recomienda en la Guía de arquitectura de apps. Cuando siguen este patrón, el código de ViewModel se ejecuta en el subproceso principal, por lo que pueden llamar a la función setValue() de MutableLiveData directamente.








 

Jesús Alejandro Tenorio 1 diciembre, 2025
Compartir
Iniciar sesión dejar un comentario

  
Android -MVVM con Jetpack Compose-
Aprende MVVM en android studio