Coroutines en Android: Programación asíncrona moderna

Las coroutines de Kotlin son una herramienta poderosa para manejar operaciones asíncronas en Android. En este artículo, exploraremos cómo utilizarlas efectivamente en el desarrollo de aplicaciones Android.

¿Qué son las Coroutines?

Las coroutines son una forma de programación asíncrona que permite escribir código secuencial para operaciones asíncronas. En Android, son especialmente útiles para manejar operaciones de red, base de datos y otras tareas que no deben bloquear el hilo principal.

Conceptos básicos

1. Scope y Context

class MyViewModel : ViewModel() {
    // Scope específico para ViewModel
    private val viewModelScope = CoroutineScope(Dispatchers.Main + Job())
    
    fun fetchData() {
        viewModelScope.launch {
            // Código asíncrono
        }
    }
}

2. Dispatchers

class Repository {
    suspend fun fetchData() = withContext(Dispatchers.IO) {
        // Operaciones de red o base de datos
    }
    
    suspend fun updateUI() = withContext(Dispatchers.Main) {
        // Actualizaciones de UI
    }
}

Patrones comunes

1. Manejo de errores

class UserRepository {
    suspend fun getUser(id: String): Result<User> = try {
        withContext(Dispatchers.IO) {
            val user = api.getUser(id)
            Result.success(user)
        }
    } catch (e: Exception) {
        Result.failure(e)
    }
}

// Uso
viewModelScope.launch {
    userRepository.getUser("123")
        .onSuccess { user ->
            // Manejar éxito
        }
        .onFailure { error ->
            // Manejar error
        }
}

2. Operaciones paralelas

class DataManager {
    suspend fun loadData() = coroutineScope {
        val users = async { userRepository.getUsers() }
        val posts = async { postRepository.getPosts() }
        
        val result = Pair(users.await(), posts.await())
        result
    }
}

Integración con Android

1. ViewModel

class UserViewModel(
    private val repository: UserRepository
) : ViewModel() {
    private val _users = MutableStateFlow<List<User>>(emptyList())
    val users: StateFlow<List<User>> = _users.asStateFlow()
    
    fun loadUsers() {
        viewModelScope.launch {
            try {
                _users.value = repository.getUsers()
            } catch (e: Exception) {
                // Manejar error
            }
        }
    }
}

2. Room Database

@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    suspend fun getUsers(): List<User>
    
    @Insert
    suspend fun insertUser(user: User)
}

class UserRepository(
    private val userDao: UserDao
) {
    suspend fun getUsers() = withContext(Dispatchers.IO) {
        userDao.getUsers()
    }
}

Mejores prácticas

  1. Scopes apropiados
  • Usar viewModelScope en ViewModels
  • Usar lifecycleScope en Activities/Fragments
  • Crear scopes personalizados cuando sea necesario
  1. Manejo de errores
  • Implementar try-catch en operaciones críticas
  • Usar Result para encapsular resultados
  • Manejar errores de red específicamente
  1. Performance
  • Elegir el dispatcher correcto
  • Evitar operaciones bloqueantes
  • Usar withContext para cambiar de contexto

Testing

@Test
fun testUserLoading() = runTest {
    // Arrange
    val repository = mock<UserRepository>()
    val users = listOf(User("1", "Test"))
    whenever(repository.getUsers()).thenReturn(users)
    
    val viewModel = UserViewModel(repository)
    
    // Act
    viewModel.loadUsers()
    
    // Assert
    assertEquals(users, viewModel.users.value)
}

Ventajas de las Coroutines

  • Código más legible y mantenible
  • Mejor manejo de errores
  • Integración con otras APIs de Kotlin
  • Menos código boilerplate
  • Mejor control del ciclo de vida

Conclusión

Las coroutines son una herramienta esencial para el desarrollo moderno de Android. Su capacidad para manejar operaciones asíncronas de manera elegante y eficiente las convierte en la opción preferida para la programación asíncrona en Kotlin.