{"id":1035,"date":"2025-10-14T16:22:29","date_gmt":"2025-10-14T21:22:29","guid":{"rendered":"https:\/\/80bits.blog\/?p=1035"},"modified":"2025-10-14T16:59:57","modified_gmt":"2025-10-14T21:59:57","slug":"flutter-obtener-ubicacion-cada-x-tiempo","status":"publish","type":"post","link":"https:\/\/80bits.blog\/index.php\/2025\/10\/14\/flutter-obtener-ubicacion-cada-x-tiempo\/","title":{"rendered":"Flutter. Obtener ubicaci\u00f3n cada X tiempo"},"content":{"rendered":"\n<p>Este fin de semana mientras intento aprender <a href=\"https:\/\/docs.flame-engine.org\/latest\/#\" target=\"_blank\" rel=\"noopener\" title=\"\">Flutter Flame<\/a> y ademas que una persona pregunto en un grupo de flutter sobre c\u00f3mo obtener la ubicaci\u00f3n cada cierto tiempo en flutter, me puse manos a la obra para crear un c\u00f3digo que hace exactamente eso, pero sin usar ning\u00fan paquete, simplemente usando channels y c\u00f3digo nativo, aunque solo para Android, ya que iOS tiene sus particularidades.<\/p>\n\n\n\n<p>Obviar\u00e9 todo el c\u00f3digo necesario para solicitar los permisos, ya que me centrar\u00e9 \u00fanicamente en la l\u00f3gica para obtener la ubicaci\u00f3n y enviarla mediante un channel hacia flutter.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Permisos necesarios<\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\" \/><br>&lt;uses-permission android:name=\"android.permission.FOREGROUND_SERVICE_LOCATION\" \/><br>&lt;uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" \/><br>&lt;uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"\/><br>&lt;uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\" \/><br>&lt;uses-permission android:name=\"android.permission.ACCESS_BACKGROUND_LOCATION\" \/><\/pre>\n\n\n\n<p>Estos son los permisos necesarios para que el c\u00f3digo funcione, estos deben ir en el archivo AndroidManifest.xml del proyecto.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">LocationService<\/h2>\n\n\n\n<p>&#8220;Lo primero que hay que hacer es crear un <strong>Foreground Service<\/strong>. En Android, para operaciones de larga duraci\u00f3n y continuas (como la obtenci\u00f3n constante de la ubicaci\u00f3n) que se ejecutan en segundo plano, es obligatorio usar un <strong>Foreground Service<\/strong>, el cual requiere de una notificaci\u00f3n visible para el usuario y as\u00ed garantizar que el sistema no lo detenga f\u00e1cilmente<\/p>\n\n\n\n<p>Para ello hay que extender una clase desde la clase <em>android.app.Service<\/em> de Android, despues hay dos clases que hay que usar:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>private lateinit var fusedLocationClient: FusedLocationProviderClient<br>private lateinit var locationCallback: LocationCallback<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><code>fusedLocationClient<\/code><\/strong>: el cliente que interact\u00faa con los servicios de ubicaci\u00f3n de Google.<\/li>\n\n\n\n<li><strong><code>locationCallback<\/code><\/strong>: recibe las actualizaciones de ubicaci\u00f3n en tiempo real.<\/li>\n<\/ul>\n\n\n\n<p>El c\u00f3digo quedara as\u00ed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>override fun onCreate() {\n    super.onCreate()\n    fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)\n\n    locationCallback = object : LocationCallback() {\n        override fun onLocationResult(result: LocationResult) {\n            result.locations.forEach { location ->\n                Log.d(\"LocationService\", \"Lat: ${location.latitude}, Lon: ${location.longitude}\")\n                LocationStreamHandler.emit(...)\n            }\n        }\n    }\n\n    startLocationUpdates()\n}\n<\/code><\/pre>\n\n\n\n<p>El c\u00f3digo para obtener las ubicaciones cada X tiempo llegara en el callback de <em>LocationCallback<\/em> dentro de este, lo que hice fue usar un StreamHandler, especificamente un m\u00e9todo dentro de mi clase <em>LocationStreamHandler<\/em> que hereda de <em>EventChannel.StreamHandler<\/em> esta clase es importante ya que es la que comunicara los datos mediante el m\u00e9todo emit hacia el c\u00f3digo de flutter.<\/p>\n\n\n\n<p>Tambien hay un metodo <em>startLocationUpdates<\/em>, su funcion es iniciar la petici\u00f3n de ubicaciones, de la siguiente manera:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>val request = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 1 * 60 * 1000L)\n            .setMinUpdateIntervalMillis(1 * 60 * 1000L)\n            .build()\n\n        fusedLocationClient.requestLocationUpdates(request, locationCallback, Looper.getMainLooper())<\/code><\/pre>\n\n\n\n<p>Para que todo esto funcione se necesita implementar <em>Looper.getMainLooper()<\/em>, esta funci\u00f3n en Android retorna el objeto <strong><code>Looper<\/code><\/strong> asociado al <strong>hilo principal (UI Thread)<\/strong> de la aplicaci\u00f3n.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><code>Looper<\/code>:<\/strong> Es un mecanismo que se encarga de <strong>extraer (dequeue)<\/strong> mensajes y eventos de una cola de mensajes (<code>MessageQueue<\/code>) y de <strong>enviarlos<\/strong> al correspondiente <strong><code>Handler<\/code><\/strong> para su procesamiento. En esencia, es lo que permite que un hilo (thread) se ejecute de manera c\u00edclica o en un &#8220;bucle&#8221;.<\/li>\n\n\n\n<li><strong><code>getMainLooper()<\/code>:<\/strong> Esta funci\u00f3n est\u00e1tica garantiza que obtienes el <code>Looper<\/code> de un hilo muy espec\u00edfico: el <strong>hilo principal<\/strong> o hilo de la interfaz de usuario (UI Thread).<\/li>\n<\/ul>\n\n\n\n<p>Por \u00faltimo, esta clase hay que agregarla en AndroidManifest.xml de la siguiente manera:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;service\n            android:name=\".LocationService\"\n            android:enabled=\"true\"\n            android:exported=\"false\"\n            android:foregroundServiceType=\"location\"\n\/><\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">LocationStreamHandler<\/h2>\n\n\n\n<p>la clase <em>LocationStreamHandler<\/em> es un singleton que hereda de <em>EventChannel.StreamHandler<\/em>. Esta clase nos servir\u00e1 para pasar los datos usando el m\u00e9todo <em>emit<\/em> hacia la parte flutter usando los channels.<\/p>\n\n\n\n<p>Esta clase sirve como un puente para el <em>Stream<\/em> de datos. Adem\u00e1s, la clase se asegura de que el env\u00edo de datos a Flutter se realice de forma segura, garantizando que la informaci\u00f3n se transmita al <strong>hilo principal (UI Thread)<\/strong> de la aplicaci\u00f3n Flutter.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>object LocationStreamHandler : EventChannel.StreamHandler {\n    private val main = Handler(Looper.getMainLooper())\n    @Volatile private var sink: EventChannel.EventSink? = null\n\n    override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { sink = events }\n    override fun onCancel(arguments: Any?) { sink = null }\n\n    fun emit(lat: Double, lon: Double, acc: Float?, bearing: Float?, speed: Float?, timeMillis: Long) {\n        val payload = mapOf(\n            \"latitude\" to lat,\n            \"longitude\" to lon,\n            \"accuracy\" to acc,\n            \"bearing\" to bearing,\n            \"speed\" to speed,\n            \"timestamp\" to timeMillis\n        )\n\n        main.post { sink?.success(payload) }\n    }\n\n    fun emitError(code: String, message: String) {\n        main.post { sink?.error(code, message, null) }\n    }\n}<\/code><\/pre>\n\n\n\n<p>Por ultimo, en main hay que &#8220;conectar&#8221; este StreamHandler de la siguiente manera:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>EventChannel(\n            flutterEngine.dartExecutor.binaryMessenger,\n            'com.g80bits\/location_updates'\n        ).setStreamHandler(LocationStreamHandler)\n<\/code><\/pre>\n\n\n\n<p>Una vez teniendo esto, en el lado flutter el c\u00f3digo simplemente debe implementar un Stream:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>EventChannel(\n      'com.g80bits\/location_updates',\n    ).receiveBroadcastStream().listen((event){\nprint(\"Location update: $event\");\n})<\/code><\/pre>\n\n\n\n<p>Con el c\u00f3digo anterior, se obtendr\u00e1 cada X tiempo la ubicaci\u00f3n desde Android<\/p>\n\n\n\n<p>Y eso ser\u00eda todo, dejare el c\u00f3digo completo as\u00ed se comprende mejor la l\u00f3gica.<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/genitalico\/location_service_native\" target=\"_blank\" rel=\"noopener\" title=\"\">C\u00f3digo en Github<\/a><\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Este fin de semana mientras intento aprender Flutter Flame y ademas que una persona pregunto en un grupo de flutter sobre c\u00f3mo obtener la ubicaci\u00f3n cada cierto tiempo en flutter, me puse manos a la obra para crear un c\u00f3digo que hace exactamente eso, pero sin usar ning\u00fan paquete, simplemente usando channels y c\u00f3digo nativo, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1058,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[10,21,23,22,20,65],"tags":[8,19,24],"class_list":["post-1035","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-android","category-dart","category-flutter","category-frameworks","category-lenguajes-de-programacion","category-sistemas-operativos","tag-android","tag-codigo","tag-flutter"],"jetpack_featured_media_url":"https:\/\/80bits.blog\/wp-content\/uploads\/2025\/10\/location_anime_run.jpg","jetpack-related-posts":[],"jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts\/1035","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/comments?post=1035"}],"version-history":[{"count":29,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts\/1035\/revisions"}],"predecessor-version":[{"id":1067,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts\/1035\/revisions\/1067"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/media\/1058"}],"wp:attachment":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/media?parent=1035"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/categories?post=1035"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/tags?post=1035"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}