Aitor Sánchez - Blog - Oct. 27, 2023, 1:35 p.m.
¿Pensando en incluir algún sistema de geolocalización en tu aplicación? O, quizás, ya lo has hecho, pero te falta conocer algún detalles cómo los permisos o encender o apagar el GPS ¿verdad
Mi nombre es Aitor Sánchez, soy desarrollador de apps desde 2014, y cuando termines con este artículo geolocalizarás a tu usuario cómo un explorador experto que sabe lo que hace en medio de un bosque oscuro, ruidoso y lúgubre a las 03:00.
Pero antes de continuar, esta es la Flutter Mafia. Es mi newsletter donde aprenderás desarrollo de apps móviles, aso y monetización junto con otros genietes que ya están dentro. Y si te suscribes te regalo mi ebook "Duplica los ingreso de tus apps en 5 minutos" No es broma, quizás te interese.
Y ahora, comenzamos. Let´s go!
Node 12.14.1 (cualquier versión 12.x funcionaría)
Ionic 5 (CLI 5.3.0)
Capacitor 2.0
Angular ~9.1.x
El primer, es confirmar que tenemos la última versión de Ionic CLI.
En caso de que no la tengas, ejecuta el siguiente comando:
...
$ npm install -g ionic@latest
...
Esto instalará, o actualizará, Ionic a la última versión disponible (dentro del paquete de Ionic ya está el CLI).
Una vez terminado de ejecutar el comando anterior, creamos un nuevo proyecto de app con el siguiente comando:
...
$ ionic start IonicGeo blank --type=angular --capacitor
...
Flags:
Probamos que todo funcione correctamente con el siguiente comando:
...
$ ionic serve
...
Si todo ha ido bien, se mostrará la aplicación. Si no existen errores, continuaremos.
Si te ha dado algún fallo este paso, dímelo en los comentarios y vemos a ver cómo podemos arreglarlo.
Para poder hacer este tutorial necesitaremos los siguientes plugins/paquetes.
Para esto, vamos a utilizar el siguiente comando:
...
$ npm install @agm/core
...
Recordemos que este plugin solo es necesario para Android.
...
$ npm install cordova-plugin-android-permissions
$ npm install @ionic-native/android-permissions
...
Este plugin permitirá al usuario encender/apagar el GPS sin necesidad de tener que salir de la aplicación.
¿Cómo lo hace el plugin? Mediante un modal que se lo solicitará cuando nosotros queramos que lo haga.
Para instalarlo:
...
$ npm install cordova-plugin-request-location-accuracy
$ npm install @ionic-native/location-accuracy
...
A diferencia de cuando utilizamos Cordova, que necesitamos instalar el Plugin aparte, Capacitor ya viene de serie con él. Así que no tendremos que hacer nada. Podríamos utilizarlo directamente así:
...
import { Plugins} from "@capacitor/core";
const { Geolocation} = Plugins;
...
Por fin, ya estamos en disposición de importar los plugins y las funcionalidad para comenzar a utilizar todo esto.
Recordemos que el modelo estándar de construcción de aplicaciones móviles con Angular define una estructura de componentes con los siguientes archivos:
En la aplicación de ejemplo que hemos creado, tendremos una página por defecto llamada "home" que tendrá ya disponibles los archivos que hemos mencionado.
El primero paso de la implementación de Google Maps, es importa el módulo "AgmCoreModule" en el archivo "app.module.ts" de la siguiente manera:
...
import { AgmCoreModule } from '@agm/core';
...
@NgModule({
...
imports: [
....
AgmCoreModule.forRoot({
apiKey: 'YOUR_GOOGLE_MAPS_API_KEY',
}),
],
....
})
...
Nota: Cómo es lógico, donde utlizamos "YOUR_GOOGLE_MAPS_API_KEY" tendrás que introducir tu KEY de la api de Google Maps.
Después de hacer esto, tenemos que importar "AgmCoreModule" en el archivo "home.module.ts". Al hacerlo nos permitirá utilizar el módulo desde nuestra página "Home". Lo haremos así:
import { AgmCoreModule } from '@agm/core';
...
@NgModule({
imports: [
...
AgmCoreModule,
...
],
})
Probemos todo antes de continuar. En "home.page.html" vamos a agregar el siguiente código HTML:
<agm-map [latitude]="lat" [longitude]="lng" [zoom]="15" [disableDefaultUI]="false">
<agm-marker [latitude]="lat" [longitude]="lng" [markerDraggable]=true>
</agm-marker>
</agm-map>
Y en tu archivo "home.page.ts" vamos a definir las dos variables que hemos utilizado en este código. La variable "lat" y la variable "lng".
Ahora, si ejecutas la aplicación tal cual, y has seguido todos los pasos al pie de la letra, tendrás algo así:
Vamos a crear una directorio dentro de "app" que se llamará services. Será el encargado de contener todos los servicios de nuestra aplicación.
Siguiendo el orden, y dentro de services, vamos a crear un archivo que se va a llamar "location.service.ts" en el que programaremos el servicio encargado de las cosas de la localización.
Pitaría así, más o menos:
import { LocationAccuracy } from '@ionic-native/location-accuracy';
import { Capacitor } from "@capacitor/core";
...
// Comprobar si la aplicación tienes permisos para acceder a la información del GPS.
async checkGPSPermission(): Promise<boolean> {
return await new Promise((resolve, reject) => {
if (Capacitor.isNative) {
AndroidPermissions.checkPermission(AndroidPermissions.PERMISSION.ACCESS_FINE_LOCATION).then(
result => {
if (result.hasPermission) {
// Tenemos el permiso concedido.
resolve(true);
} else {
//No tenemos permiso, solicitarlo mediante el modal.
resolve(false);
}
},
err => {alert(err);}
);}
else {resolve(true); }
})
}
async requestGPSPermission(): Promise<string> {
return await new Promise((resolve, reject) => {
LocationAccuracy.canRequest().then((canRequest: boolean) => {
if (canRequest) {
resolve('CAN_REQUEST');
} else {
// Mostramos el modal de solicitud de permisos.
AndroidPermissions.requestPermission(AndroidPermissions.PERMISSION.ACCESS_FINE_LOCATION)
.then(
(result) => {
if (result.hasPermission) {
// Llamamos la método para encender el GPS.
resolve('GOT_PERMISSION');
} else {
resolve('DENIED_PERMISSION');
}
},
error => {
// Mostramos un alert si el usuario ha hecho click en "no".
alert('requestPermission Error requesting location permissions ' + error);
});
}
});
})
}
Bien, podrás utilizar esta función nada más cargar la aplicación, o justo antes de cuando se vaya a utilizar. Eso ya depende más de ti que de mí. Pero te aconsejo que lo hagas solo cuando la vayas a utilizar.
En el mismo archivo "location.service.ts" que hemos creado antes, y a continuación de lo que hemos programado, vamos a colocar el siguiente código:
import { Injectable } from '@angular/core';
import { LocationAccuracy } from '@ionic-native/location-accuracy';
import { Capacitor } from "@capacitor/core";
@Injectable({
providedIn: 'root'
})
export class LocationService {
constructor() {
}
async askToTurnOnGPS(): Promise<boolean> {
return await new Promise((resolve, reject) => {
LocationAccuracy.canRequest().then((canRequest: boolean) => {
if (canRequest) {
// the accuracy option will be ignored by iOS
LocationAccuracy.request(LocationAccuracy.REQUEST_PRIORITY_HIGH_ACCURACY).then(
() => {
resolve(true);
},
error => {
resolve(false);
}
);
}
else {resolve(false);}
});
})
}
}
La elección de cómo colocarlo, el código del punto anterior y este, lo dejo a tu elección también. Lo importante es que esté. Y en el orden en el que está.
Porque si te fijas, el decorador "injectable" tiene que estar justo en las líneas anteriores a la creación de la clase "LocationService". Si no está así, no funcionará nada.
¿Qué hará la función askToTurnOnGPS? Pues cómo su nombre indica, le preguntará al sistema si podemos utilizar le GPS.
Ahora, deberás de utilizar la función "watchPosition" para trackear, de manera continua, la posición en la que se encuentra el dispositivo donde se está ejecutando la aplicación. Y, por extensión, el usuario.
Nota: Recuerda, esta funcionalidad consume bastante batería, recuerda limpiar el tracker llamando a "clearWatch" cuando vayas a dejar de utilizarlo en una aplicación real.
Perfecto, ahora combinaremos todo el código que hemos visto en la parte de arriba de este artículo para poner todo en marcha.
Comenzamos:
"home.page.html"
<ion-header>
<ion-toolbar color="primary">
<ion-title>
Geolocation
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<agm-map [latitude]="lat" [longitude]="lng" [zoom]="15" [disableDefaultUI]="false">
<agm-marker [latitude]="lat" [longitude]="lng" [markerDraggable]=true></agm-marker>
</agm-map>
<ion-row>
<ion-col>Latitude: {{lat}}</ion-col>
<ion-col>Longitude: {{lng}}</ion-col>
</ion-row>
</ion-content>
<ion-footer>
<ion-button expand="full" color="primary" (click)="getMyLocation()">Get My Location</ion-button>
</ion-footer>
"home.page.ts"
import { Component, NgZone } from '@angular/core';
import { Capacitor, Plugins } from "@capacitor/core";
import { LocationService } from '../location.service';
const { Geolocation, Toast } = Plugins;
@Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
})
export class HomePage {
lat: any;
lng: any;
watchId: any;
constructor(public ngZone: NgZone, private locationService: LocationService) {
this.lat = 12.93448;
this.lng = 77.6192;
}
async getMyLocation() {
const hasPermission = await this.locationService.checkGPSPermission();
if (hasPermission) {
if (Capacitor.isNative) {
const canUseGPS = await this.locationService.askToTurnOnGPS();
this.postGPSPermission(canUseGPS);
}
else { this.postGPSPermission(true); }
}
else {
const permission = await this.locationService.requestGPSPermission();
if (permission === 'CAN_REQUEST' || permission === 'GOT_PERMISSION') {
if (Capacitor.isNative) {
const canUseGPS = await this.locationService.askToTurnOnGPS();
this.postGPSPermission(canUseGPS);
}
else { this.postGPSPermission(true); }
}
else {
await Toast.show({
text: 'User denied location permission'
})
}
}
}
async postGPSPermission(canUseGPS: boolean) {
if (canUseGPS) { this.watchPosition(); }
else {
await Toast.show({
text: 'Please turn on GPS to get location'
})
}
}
async watchPosition() {
try {
this.watchId = Geolocation.watchPosition({}, (position, err) => {
this.ngZone.run(() => {
if (err) { console.log('err', err); return; }
this.lat = position.coords.latitude;
this.lng = position.coords.longitude
this.clearWatch();
})
})
}
catch (err) { console.log('err', err) }
}
clearWatch() {
if (this.watchId != null) {
Geolocation.clearWatch({ id: this.watchId });
}
}
}
"location.service.ts"
import { Injectable } from '@angular/core';
import { AndroidPermissions } from '@ionic-native/android-permissions';
import { LocationAccuracy } from '@ionic-native/location-accuracy';
import { Capacitor } from "@capacitor/core";
@Injectable({
providedIn: 'root'
})
export class LocationService {
constructor() { }
async askToTurnOnGPS(): Promise<boolean> {
return await new Promise((resolve, reject) => {
LocationAccuracy.canRequest().then((canRequest: boolean) => {
if (canRequest) {
// the accuracy option will be ignored by iOS
LocationAccuracy.request(LocationAccuracy.REQUEST_PRIORITY_HIGH_ACCURACY).then(
() => {
resolve(true);
},
error => { resolve(false); }
);
}
else { resolve(false); }
});
})
}
// Check if application having GPS access permission
async checkGPSPermission(): Promise<boolean> {
return await new Promise((resolve, reject) => {
if (Capacitor.isNative) {
AndroidPermissions.checkPermission(AndroidPermissions.PERMISSION.ACCESS_FINE_LOCATION).then(
result => {
if (result.hasPermission) {
// If having permission show 'Turn On GPS' dialogue
resolve(true);
} else {
// If not having permission ask for permission
resolve(false);
}
},
err => { alert(err); }
);
}
else { resolve(true); }
})
}
async requestGPSPermission(): Promise<string> {
return await new Promise((resolve, reject) => {
LocationAccuracy.canRequest().then((canRequest: boolean) => {
if (canRequest) {
resolve('CAN_REQUEST');
} else {
// Show 'GPS Permission Request' dialogue
AndroidPermissions.requestPermission(AndroidPermissions.PERMISSION.ACCESS_FINE_LOCATION)
.then(
(result) => {
if (result.hasPermission) {
// call method to turn on GPS
resolve('GOT_PERMISSION');
} else {
resolve('DENIED_PERMISSION');
}
},
error => {
// Show alert if user click on 'No Thanks'
alert('requestPermission Error requesting location permissions ' + error);
}
);
}
});
})
}
}
"home.page.scss"
#map {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
agm-map {
height: calc(100vh - 164px);
}
agm-map {
.gm-svpc {
display: none;
}
}
ion-row {
height: 64px;
align-items: center;
background: black;
color: white;
ion-col {
text-align: center;
}
}
Antes de que instalemos la aplicación en nuestro dispositivo, lo que tenemos que hacer es compilar los recursos. Para ello vamos a utilizar el siguiente comando:
...
$ ionic build
...
Agregamos la plataforma de Android a nuestro proyecto:
...
$ npx cap add android
...
En caso de que no usemos Android Studio cómo IDE, hacemos esto:
...
$ npx cap open android
...
Esto abrirá Android Studio y cargará este proyecto sobre él.
Selecciona el dispositivo donde quieres cargar la aplicación. Ya sea en un emulador, o en un dispositivo físico, y dale al play. Verás algo como esto:
Mira... tu logo es la parte de la ficha de Google Play que más inflye en tu tasa de click desde el listado a la ficha. Vamos, la puerta de entrada de tu app. Si tu mejoras el logo consigues más descargas, y por esta razón, ganas más pasta.
Pues bien, para optimizar, mejorar y evaluar aún más tus logos, pásate por este enlace. Es una herramienta que hemos hecho para hacer esto y para que puedas espiar a la competencia. No te espoileo más. Nos vemos dentro.
Y ahora si, esto ha sido todo por hoy en la guía más completa que encontrarás sobre Google Maps en Ionic de la red.
Solo queda despedirme. Así que nos vemos en el siguiente artículo. Hasta entonces ¡que vaya bien!