Contenido del curso

Servicios de Localización

Callbacks de Android convertidos en Flows

Resumen

Obtener la ubicación de un usuario en Android no es solo pedirle al GPS que entregue coordenadas. Implica mapear modelos, validar permisos, vigilar el hardware y traducir callbacks del sistema operativo a flows de corrutinas. Aquí verás cómo construir esa capa de datos con una arquitectura limpia, lista para sobrevivir cambios futuros.

Por qué necesitas un mapper entre Location de Android y tu modelo de dominio

Android trae su propia clase Location cargada de propiedades que tu aplicación probablemente no necesita. Por eso el primer paso es crear una carpeta data con un LocationMapper que traduzca esa clase nativa a la tuya, definida en domain [00:14].

La solución es una extension function sobre android.location.Location llamada toLocation(), que devuelve tu modelo propio asignando latitud y longitud:

kotlin fun android.location.Location.toLocation(): Location { return Location( lat = latitude, long = longitude ) }

¿Para qué sirve un mapper en arquitectura limpia? Aísla tu dominio de las clases del framework. Si mañana migras de plataforma, solo cambias el mapper, no toda tu lógica de negocio.

Cómo definir un contrato con la interfaz LocationObserver

Dentro de domain/location agregas una interfaz llamada LocationObserver con una función observeLocation() que devuelva un Flow de tu modelo Location [01:05]. Esa interfaz es un contrato: cualquier clase que la implemente promete entregar ubicaciones siguiendo tu propio formato.

La ventaja es que tu capa de datos queda desacoplada. Si necesitas cambiar de proveedor de localización, escribes una nueva implementación y listo. El resto de la aplicación no se entera.

kotlin interface LocationObserver { fun observeLocation(interval: Long): Flow<Location> }

Revisa que importes tu propio Location y los Flow de corrutinas, no los de Java o Android.

Cómo implementar AndroidLocationObserver con callbackFlow

En la carpeta data creas la clase AndroidLocationObserver que implementa la interfaz [01:55]. Por inyección de dependencias recibes el Context de Android y construyes el cliente de Google Play Services:

kotlin class AndroidLocationObserver @Inject constructor( private val context: Context ) : LocationObserver { private val client = LocationServices.getFusedLocationProviderClient(context) }

Para devolver un Flow desde un sistema basado en callbacks, usas callbackFlow, un tipo de flow especial diseñado para traducir esos callbacks tradicionales de Android al mundo de las corrutinas [02:35].

Qué tres tareas debe resolver el observer

Dentro de callbackFlow cubres tres frentes:

  • Verificar que el GPS y la red estén activos en el dispositivo.
  • Validar que el usuario haya otorgado los permisos correctos.
  • Mapear el Location de Android al modelo del dominio.

Cómo verificar si el GPS y la red están activos

Obtienes el LocationManager desde el context y declaras dos banderas, isGpsEnabled e isNetworkEnabled, ambas en false. Luego entras a un loop que las actualiza en cada iteración consultando locationManager.isProviderEnabled para GPS_PROVIDER y NETWORK_PROVIDER [03:20].

Si ninguno está habilitado, el flow espera con un delay de tres segundos antes de volver a verificar. Tres segundos es razonable: el usuario no va a activar permisos en milisegundos, así que no tiene sentido saturar el bucle.

Cómo validar permisos de localización en Android

Android distingue entre dos permisos clave para ubicación:

  • ACCESS_FINE_LOCATION para localización exacta (GPS).
  • ACCESS_COARSE_LOCATION para localización aproximada (red).

Usas ActivityCompat.checkSelfPermission(context, ...) y comparas el resultado con PackageManager.PERMISSION_GRANTED. Si alguno falla, simplemente no emites valores, evitando una SecurityException del sistema operativo [05:00].

¿Por qué necesito declarar permisos en el manifiesto si ya los pido en código? Android exige que los permisos estén listados en el AndroidManifest.xml antes de compilar. El chequeo dinámico solo confirma si el usuario los aceptó en tiempo de ejecución.

En el manifiesto, antes de la etiqueta <application>, agregas:

xml <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

En cuanto los declaras, el error de compilación que pedía permisos requeridos desaparece.

Cómo emitir ubicaciones con trySend y LocationCallback

Con los permisos validados, pides la última ubicación al cliente con lastLocation.addOnSuccessListener. Si el resultado no es nulo, usas el operador let y llamas a trySend(location.toLocation()). Esa función trySend es la pieza clave que convierte un callback en una emisión del flow [06:30].

Pero lastLocation solo entrega un valor inicial. Para recibir actualizaciones continuas necesitas un LocationRequest y un LocationCallback:

kotlin val request = LocationRequest.Builder(interval) .setPriority(Priority.PRIORITY_HIGH_ACCURACY) .build()

val locationCallback = object : LocationCallback() { override fun onLocationResult(result: LocationResult) { result.locations.lastOrNull()?.let { location -> trySend(location.toLocation()) } } }

client.requestLocationUpdates(request, locationCallback, Looper.getMainLooper())

La prioridad PRIORITY_HIGH_ACCURACY indica al sistema que quieres precisión máxima, y el interval define cada cuánto pedir actualizaciones [07:50].

Por qué cerrar recursos con awaitClose

Cuando el consumidor del flow deja de observar, necesitas detener las actualizaciones para no consumir batería en vano. Lo haces con awaitClose, donde llamas a client.removeLocationUpdates(locationCallback) [09:10]. Sin este cierre, el cliente seguiría emitiendo aunque nadie escuche.

Usar sensores del sistema operativo es complejo: pedir permisos, vigilar hardware y traducir callbacks a flows requiere disciplina. ¿Ya tienes tu AndroidLocationObserver corriendo? Cuéntame en los comentarios si te apareció alguna SecurityException y cómo la resolviste.