Capacitor

<< Componentes (Parte 2) Router de Ionic >>

Capacitor es una plataforma para crear aplicaciones hibridas multiplataforma. Una aplicación híbrida implica que se crea una aplicación nativa pero el código principal (web) se ejecuta en un entorno de navegador integrado. Capacitor, mediante un sistema de plugins, proporciona una API de funciones JavaScript que ejecutan funciones nativas del dispositivo (cámara, portapapeles, cuadros de diálogo nativos, navegación GPS, sistema de archivos, SQLite, sensores, brujula, vibración, servicios de Google, Facebook, etc.).

Capacitor se instala por defecto en todos los proyectos Ionic a partir de la versión 6. Sin embargo, se puede añadir capacitor a cualquier aplicación Angular con el comando ng add @capacitor/angular, e incluso a cualquier aplicación JavaScript/TypeScript (npm i @capacitor/core @capacitor/cli).

Configurar Capacitor

Lo primero que tenemos que hacer es configurar la parte de Capacitor en el archivo capacitor.config.ts, ya que si no, nos tocará editar estos datos directamente en el proyecto nativo de Android o iOS. Aquí configuramos el nombre de la aplicación y el appID. Es importante también, que si vamos a conectarnos a servicios web que no se ejecutan bajo https (por ejemplo en local), activemos la opción de Android allowMixedContent.

import { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'es.fullstackpro.ionic.capacitor',
  appName: 'Ionic Capacitor',
  webDir: 'www/browser',
  android: {
    allowMixedContent: true
  }
};

export default config;

Antes de añadir y generar proyectos para las diferentes plataformas, debemos al menos compilar la aplicación una vez para generar el directorio www:

ionic build

Crear proyecto iOS

Solo podemos crear y compilar la versión de la aplicación para iOS utilizando un dispositivo Mac con Xcode instalado

npm i @capacitor/ios

npx cap add ios

Crear proyecto Android

Para abrir el proyecto de Android necesitaremos instalar Android Studio.

npm i @capacitor/android

npx cap add android

Abrir proyecto Android

Una vez generado el proyecto, podemos abrir Android Studio (que debemos instalar previamente). La primera vez que abrimos Android Studio, nos instalará el SDK de Android más reciente junto a un emulador, por si queremos probar la app sin un teléfono físico. En el caso de utilizar el emulador, es recomendable tener activadas Las extensiones de virtualización del procesador( en la BIOS/UEFI) y habilitar un hipervisor.

Una vez abierto Android Studio, podemos abrir el proyecto de Android generado. Este estará dentro de la carpeta android del proyecto.

Habilitar tráfico http (clearText)

Si nuestra aplicación se conecta a servicios web por http en lugar de usar https (por ejemplo, durante el desarrollo), hay que activar la opción correpondiente en el proyecto Android, ya que desde hace tiempo está deshabilitado por defecto el tráfico no seguro. Después, para compilar la app en producción se aconsejaría quitar esta opción.

Para ello editamos el archivo app/manifests/AndroidManifest.xml y añadimos el siguiente atributo a la etiqueta application:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        ...
        android:usesCleartextTraffic="true">
        <!-- ... -->
    </application>
    <!-- ... -->
</manifest>

Configurar dispositivo físico

Para instalar (y ejecutar) aplicaciones desde tu ordenador en tu dispositivo Android conectado, deberás activar las "Opciones de desarrollador". Por lo general, esto se hace tocando varias veces sobre tu versión de Android.

Después de habilitar las Opciones de desarrollo, podrás acceder a ellas volviendo a las opciones principales de configuración y buscándolas desde allí.

Dentro de la sección de Opciones de desarrollo, deberás habilitar la opción de depuración USB y la posibilidad de instalar aplicaciones desde USB, y si aparece, también la posibilidad de conceder permisos via USB.

Si Android Studio no reconoce tu teléfono, prueba a seleccionar transferencia de archivos en la pantalla del teléfono al conectarlo por USB.

Una vez se haya reconocido el dispositivo, podrás generar la APK e instalarla en el teléfono con el botón que hay a la derecha o con Shift+F10. Debes prestar atención a la pantalla del teléfono ya que pedirá permiso para instalar la aplicación la primera vez.

Plugins de Capacitor

Capacitor tiene un sistema de plugins que instalaremos de forma individual según necesidades. Es importante con cada cambio de código que queramos trasladar al proyecto nativo de Android e iOS, ejecutemos lo siguiente:

ionic build && npx cap sync

Debemos tener en cuenta que los plugins de Capacitor suelen devolver resultados a partir de operaciones asíncronas, generalmente promesas. Esto implica que si modificamos datos de la interfaz en una aplicación zoneless hay que usar señales o el servicio ChangeDetectorRef generalmente para que Angular detecte esos cambios.

SplashScreen

Cuando tu aplicación se inicia, muestra una imagen de pantalla de presentación (splash screen), la cual es gestionada por el complemento de la Pantalla de Inicio. Por defecto, la Pantalla de Inicio está configurada para ocultarse automáticamente después de cierto tiempo (3 segundos). Sin embargo, tu aplicación debería arrancar mucho más rápido que esto.

Para ocultar la pantalla de bienvenida desde código puedes utilizar el plugin de Capacitor SplashScreen. De esta manera, cuando la aplicación (AppComponent) se carga, podemos ocultar esta pantalla.

npm i @capacitor/splash-screen

//...
import { SplashScreen } from '@capacitor/splash-screen';
import { ..., Platform } from '@ionic/angular/standalone';
//...
export class AppComponent {
  //...
  #platform = inject(Platform);

  constructor() {
    //...
    this.initializeApp();
  }

  async initializeApp() {
    if (this.#platform.is('mobile')) {
      await this.#platform.ready();
      SplashScreen.hide();
    }
  }
}

Se pueden cambiar los iconos y la pantalla de bienvenida utilizando la herramienta @capacitor/assets.

Importante: Si estás usando Android 12 con un lanzador de aplicaciones como Nova, MIUI (Xiaomi), Realme launcher, OPPO launcher, etc. es posible que no se muestre la pantalla de presentación. Esto es un bug de Android que debería estar resuelto en la versión 13.

Status Bar

El plugin StatusBar permite cambiar el estilo de la barra superior del teléfono, por ejemplo, adaptándolo al color principal de tu aplicación. Además del color, tenemos 2 estilos para el contraste del texto e iconos de la barra superior:

  • Light → Texto oscuro (úsalo con color de fondo blanco o muy claro).
  • Dark → Texto blanco (colores de fondo más oscuros).

npm i @capacitor/status-bar (viene instalado por defecto)

const config: CapacitorConfig = {
  //...
  plugins: {
    StatusBar: {
      overlaysWebView: false,
      style: "DARK",
      backgroundColor: "#0054e9",
    },
  },
};

Vibración

El plugin Haptics permite interactuar con el usuario mediante la vibración del dispositivo o diferentes valores de "impacto" ante la interacción táctil.

npm i @capacitor/haptics (viene instalado por defecto)

<!-- ... -->
<ion-content class="ion-padding" [fullscreen]="true">
  <ion-button (click)="vibrate()">Vibrate</ion-button>
  <ion-button (click)="vibrateHard()">Hard impact</ion-button>
  <ion-button (click)="vibrateLong()">Long vibration</ion-button>
</ion-content>

Cámara

El plugin Camera permite acceder a la cámara del dispositivo para tomar una foto o a la galería de imágenes, en función del método utilizado.

npm i @capacitor/camera

Para que funcione el acceso a la cámara (y galería de fotos), debemos añadir los siguientes permisos al final del archivo AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- ... -->
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>

Una vez instalado y configurados los permisos del plugin, ya podemos obtener una foto o imagen de la galería con el método getPhoto. Tenemos otros métodos como pickImages que permiten al usuario seleccionar varias fotos de la galería. También tiene métodos para comprobar los permisos, y volver a pedirlos si fuera necesario.

Se pueden consultar las opciones que se le pueden pasar en la documentación oficial. Algunas interesantes son resultType (con el valor CameraResultType.DataUrl la devuelve en base64), source (especificamos cámara o galería), quality (hasta 100), width (ancho máximo, respeta relación de aspecto), height, saveToGallery (guarda la foto en la galería), o direction (abre cámara trasera o frontal por defecto).

<!-- ... -->
<ion-content class="ion-padding" [fullscreen]="true">
  <ion-button (click)="takePhoto()">Take photo</ion-button>
  <ion-button (click)="pickFromGallery()" color="success">Pick from gallery</ion-button>
  @if (image()) {
    <ion-img [src]="image()"></ion-img>
  }
</ion-content>

Podemos utilizar la cámara en una aplicación web sin necesidad de ejecutar una aplicación Android o iOS, es decir, probándola desde el navegador. Sin embargo, para que esto funcione necesitamos instalar un módulo de Ionic para integración de ciertos componentes como la cámara en aplicaciones PWA:

npm i @ionic/pwa-elements

Posteriormente llamaremos a la función defineCustomElements en el archivo main.ts.

//...
import { defineCustomElements } from '@ionic/pwa-elements/loader';

defineCustomElements(window);

bootstrapApplication(/*...*/);

Clipboard (Portapapeles)

El plugin Clipboard permite copiar contenido hacia o pegar contenido desde el portapapeles del sistema.

npm i @capacitor/clipboard

<!-- ... -->
<ion-content class="ion-padding" [fullscreen]="true">
  <p>To test the clipboard better, copy text from another app and paste it here. Or copy content from here and paste it elsewhere.</p>
  <ion-item>
    <ion-input [(ngModel)]="text"></ion-input>
  </ion-item>
  <ion-button color="primary" (click)="copy()">Copy</ion-button>
  <ion-button color="secondary" (click)="paste()">Paste</ion-button>
</ion-content>

Device

El plugin Device permite a acceder a información general del dispositivo como el fabricante, sistema operativo y versión, nivel de batería, etc.

npm i @capacitor/device

<!-- ... -->
<ion-content class="ion-padding" [fullscreen]="true">
  <p>{{info() | json}}</p>
  <p>{{battery() | json}}</p>
</ion-content>

Filesystem

El plugin Filesystem permite acceder al sistema de ficheros del dispositivo móvil. Esto quiere decir crear, borrar y leer ficheros o directorios. En versiones recientes de Android, la aplicación no podrá acceder a los ficheros creados por otras aplicaciones, y viceversa. El directorio recomendado para crear los archivos de usuario es Documents.

npm i @capacitor/filesystem

A partir de la versión 10 de Android se requieren los siguientes permisos

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- ... -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>

Tenemos varios métodos para leer directorios (readdir), leer archivos (readFile), crear archivos (writeFile), etc. Dichos métodos devuelven siempre una promesa con el resultado.

<!-- ... -->
<ion-content [fullscreen]="true">
  <form>
    <ion-list>
      <ion-item>
        <ion-input label="Filename" labelPlacement="floating" [(ngModel)]="filename"></ion-input>
      </ion-item>
      <ion-item>
        <ion-textarea label="Content" labelPlacement="floating" [(ngModel)]="filetext"></ion-textarea>
      </ion-item>
    </ion-list>
    <ion-button color="primary" (click)="createFile()">Create</ion-button>
  </form>

  <ion-list>
    <ion-list-header>
      <ion-label>List of files</ion-label>
    </ion-list-header>
    @for (f of files(); track f.name) {
      <ion-item button (click)="readFile(f)">
        <ion-label>{{f.name}}</ion-label>
        <ion-button slot="end" color="danger" (click)="deleteFile(f)">X</ion-button>
      </ion-item>
    }
  </ion-list>
</ion-content>

Geolocation

El plugin Geolocation permite obtener tu ubicacion utilizando el GPS si está disponible. Si no, utilizará otro método menos preciso como la triangulación de Wifi.

npm i @capacitor/geolocation

Debes tener los siguientes permisos para que funcione la geolocalización. La última línea es opcional, e indica que la aplicación requiere un dispositivo con GPS.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- ... -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-feature android:name="android.hardware.location.gps" android:required="false" />
</manifest>

Para obtener la geolocalización llamamos al método getCurrentPosition que devuelve el resultado en una promesa. Con las coordenadas obtenidas, podríamos mostrar un mapa. Puedes ver como crear un mapa en Angular en la sección Openlayers del Curso de Angular con dicha librería.

<!-- ... -->
<ion-content [fullscreen]="true">
  @if(coords()) {
    <ol-map [coordinates]="coords()">
        <ol-marker [coordinates]="coords()"></ol-marker>
    </ol-map>
  }
</ion-content>

El plugin de navegación GPS ha cambiado: https://github.com/AshleyMedway/capacitor-start-navigation

Local Notifications

Las Notificaciones Locales son útiles para crear recordatorios para el usuario que aparezcan en la barra de notificaciones. Al contrario que las notificaciones Push (que envía el servidor), estas notificaciones solo pueden ser creadas por la aplicación cliente cuando está ejecutándose.

npm i @capacitor/local-notifications

Con este plugin puedes hacer cosas como programar una notificación puntual, o que se repita e intervalos de tiempo, agruparlas, asignarles iconos, etc., así como reaccionar (evento) cuando el usuario activa una notificación que tu aplicación ha creado.

<ion-header [translucent]="true">
  <ion-toolbar color="primary">
    <ion-buttons slot="start">
      <ion-menu-button></ion-menu-button>
    </ion-buttons>
    <ion-title>Local Notifications</ion-title>
  </ion-toolbar>
</ion-header>


<ion-content class="ion-padding" [fullscreen]="true">
  <ion-list>
    <ion-item>
      <ion-input [(ngModel)]="message" labelPlacement="floating" label="Message to remember"></ion-input>
    </ion-item>
  </ion-list>
  <ion-row>
    <ion-col>
      <ion-button expand="block" color="secondary" [disabled]="scheduled()" (click)="createNotification()">Notify (10sec)</ion-button>
    </ion-col>
    <ion-col>
      <ion-button expand="block" color="danger" [disabled]="!scheduled()" (click)="cancelNotification()">Cancel</ion-button>
    </ion-col>
  </ion-row>
  @if (triggered()) {
    <p>The notification has been triggered!</p>
  }
</ion-content>

Native dialogs

Si queremos mostrar diáologos del sistema (alert, confirm, prompt) nativos se puede utilizar el plugin Dialog. No podremos controlar el aspecto ni el contenido de dichas ventanas modales por lo que raramente nos interesará usarlo.

npm i @capacitor/dialog

<ion-header [translucent]="true">
  <ion-toolbar color="primary">
    <ion-buttons slot="start">
      <ion-menu-button></ion-menu-button>
    </ion-buttons>
    <ion-title>Dialogs</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content class="ion-padding" [fullscreen]="true">
  <p><ion-button (click)="showAlert()">Show alert</ion-button></p>
  <p><ion-button (click)="showConfirm()">Show confirm</ion-button></p>
  <p>{{confirm() ? 'Good choice!' : 'Bad choice...'}}</p>
  <p><ion-button (click)="showPrompt()">Show prompt</ion-button></p>
  <p>{{name()}}</p>
</ion-content>

Native Toast

El plugin Toast permite mostrar notificaciones de tipo toast nativas. Lo único que podemos configurar es el texto a mostrar y la duración ('short': 2s, 'long': 3,5s).

npm i @capacitor/toast

<!-- ... -->
<ion-content class="ion-padding" [fullscreen]="true">
  <ion-button (click)="showToast()">Show Toast</ion-button>
</ion-content>

Native Action Sheet

Para mostrar un menú de tipo Action Sheet (menú con botones que aparece en la parte inferior) nativo tenemos el plugin Action Sheet. Le pasaremos un array de botones con un texto y nos devolverá el índice del botón que se haya pulsado. Lo máximo que podemos configurar el el rol Destructive y Cancel (que tienen efecto estético en iOS).

npm i @capacitor/action-sheet

<!-- ... -->
<ion-content class="ion-padding" [fullscreen]="true">
  <p><ion-button (click)="showActions()">Show Action Sheet</ion-button></p>
  @if (option() >= 0) {
    <p>Option selected: {{option()}}</p>
  }
</ion-content>

Motion (acelerómetro, brújula)

El plugin Motion permite monitorizar el movimiento del dispositivo (acelerómetros x,y,z) así como la orientación (brújula).

npm i @capacitor/motion

<!-- ... -->
<ion-content class="ion-padding" [fullscreen]="true">
  <p><ion-button (click)="startMotion()" [disabled]="started">Enable motion</ion-button></p>
  <h2>Acceleration</h2>
  <p>X: {{acceleration().x | number:'1.2-2'}}</p>
  <p>Y: {{acceleration().y | number:'1.2-2'}}</p>
  <p>Z: {{acceleration().z | number:'1.2-2'}}</p>

  <h2>Orientation</h2>
  <p>Alpha: {{orientation().alpha | number:'1.2-2'}}</p>
  <p>Beta: {{orientation().beta | number:'1.2-2'}}</p>
  <p>Gamma: {{orientation().gamma | number:'1.2-2'}}</p>
</ion-content>

Network (estado de la red)

El plugin Network permite obtener información sobre la conexión a internet (incluyendo el tipo de red al que está conectado el dispositivo). Además, podemos reaccionar a cambios en la conexión mediante eventos.

npm i @capacitor/network

<!-- ... -->
<ion-content class="ion-padding" [fullscreen]="true">
  @if (connected()) {
  <h3>Connected to: {{type()}}</h3>
  } @else {
    <h3 color="danger">Not connected!</h3>
  }
</ion-content>

Share (compartir contenido)

El plugin Share permite abrir el cuadro de diálogo del sistema para compartir contenido (por redes sociales, email, etc.).

npm i @capacitor/share

Se puede compartir un texto (text), un título (title), un enlace (url), y archivos (files).

<!-- ... -->
<ion-content class="ion-padding" [fullscreen]="true">
  <ion-list>
    <ion-item>
      <ion-input type="text" placeholder="Message" [(ngModel)]="message">
      </ion-input>
    </ion-item>
  </ion-list>

  <ion-button block color="secondary" (click)="share()">
    <ion-icon slot="start" name="share"></ion-icon>
    Share
  </ion-button>
</ion-content>

Preferences (almacenamiento clave-valor)

El plugin Preferences almacena información de forma persistente (hasta que el usuario borre los datos de la aplicación o la desinstale) en parejas de clave valor. Es muy similar al funcionamiento de la API LocalStorage del navegador, sin embargo, en apps móviles, el sistema operativo puede borrar periódicamente la información almacenada ahí, por lo que se recomienda utilizar la API nativa Preferences en su lugar.

npm i @capacitor/preferences

Como curiosidad, en iOS este plugin utiliza UserDefaults, mientras que en Android usa SharedPreferences para almacenar la información. Si queremos almacenar estructuras complejas de datos en local, seŕia mejor utilizar otro plugin como el de SQLite.

<!-- ... -->
<ion-content class="ion-padding" [fullscreen]="true">
  <p>
    Your data will persist when you click "Save". You can return to this page
    later to see it.
  </p>
  <ion-list>
    <ion-item>
      <ion-input type="text" [(ngModel)]="name" labelPlacement="floating" label="Name:"> </ion-input>
    </ion-item>
  </ion-list>

  <ion-button block color="primary" (click)="save()"> Save </ion-button>
</ion-content>

App

El plugin App permite gestionar varias cosas relacionadas con el estado de la app y eventos generales de la misma. Algunos ejemplos son:

  • Forzar el cierre de la aplicación (exitApp). También minimizar la aplicación (minimizeApp)
  • Obtener información de la app (getInfo)
  • Obtener el enlace de aplicación utilizado para lanzar la app (getLaunchUrl)
  • Evento appStateChange → Cuando la aplicación pasa a segundo plano o vuelve a primer plano. Alternativamente puedes escuchar los eventos pause (pasa a segundo plano) o resume (pasa a primer plano).
  • Evento backButton → Cuando el usuario presiona el botón de volver atrás. Puedes usarlo para preguntar al usuario si quiere cerrar la aplicación y salir.

npm i @capacitor/app

<!-- ... -->
<ion-content class="ion-padding" [fullscreen]="true">
  <p>You can exit the app from this page using the Android back button on your phone</p>
  <p>{{appInfo() | json}}</p>
</ion-content>

El plugin StartNavigation no está mantenido correctamente, por lo que en su lugar utilizaremos un plugin de Cordova llamado Launch Navigator. Por suerte, Capacitor es capaz de reconocer y utilizar plugins de Cordova sin demasiados problemas. 

npm i uk.co.workingedge.phonegap.plugin.launchnavigator

npm install @awesome-cordova-plugins/launch-navigator

<!-- ... -->
<ion-content [fullscreen]="true">
  @if(coords()) {
    <ol-map [coordinates]="coords()">
      <ol-marker [coordinates]="coords()"></ol-marker>
    </ol-map>
  }
</ion-content>

Google Login

Para la autenticación nativa con Google vamos a usar el plugin Capacitor Social Login.

Lo primero que tendremos que hacer será crear un proyecto en la consola de desarrollador de Google y crear credenciales web si no las tenemos ya creadas. Los pasos se pueden seguir en el apartado de Login de Google en la sección Integrar librerías del curso de Angular.

Para utilizar el login de Google nativo del dispositivo necesitaremos crear credenciales para Android. Se pedirán los siguientes datos:

  • Nombre de la aplicación → Puedes consultarla en el archivo capacitor.config.ts (appName), o en el proyecto Android (app/res/values/strings.xml)
  • Nombre del paquete → Id de la aplicación. Puedes consultarlo en el mismo lugar que el nombre.
  • Huella digital SHA-1 → Es un resumen hash del certificado utilizado para firmar la aplicación. En modo prueba (en local) existe un almacen de claves pregenerado con el certificado. Para publicar la aplicación habría que crear un almacén de claves y un certificado nuevo que compartirían los desarrolladores de la misma.

Para obtener la huella del certificado de pruebas (debug) ejecutamos lo siguiente:

  • Windows → keytool -list -v -alias androiddebugkey -keystore %USERPROFILE%\.android\debug.keystore
  • Linux → keytool -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore

La contraseña es: android

Instalamos el plugin:

npm install @capgo/capacitor-social-login

En el proyecto de Android (desde Android Studio mejor), editamos la clase MainActivity.java que está situada dentro de app/java/paqueteprincipal.

package es.fullstack.ionic.capacitor; // Esta línea se queda como estaba (con tu nombre de paquete)

import android.content.Intent;
import android.util.Log;

import com.getcapacitor.BridgeActivity;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginHandle;

import ee.forgr.capacitor.social.login.GoogleProvider;
import ee.forgr.capacitor.social.login.ModifiedMainActivityForSocialLoginPlugin;
import ee.forgr.capacitor.social.login.SocialLoginPlugin;

public class MainActivity extends BridgeActivity  implements ModifiedMainActivityForSocialLoginPlugin {
  @Override
  public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode >= GoogleProvider.REQUEST_AUTHORIZE_GOOGLE_MIN && requestCode < GoogleProvider.REQUEST_AUTHORIZE_GOOGLE_MAX) {
      PluginHandle pluginHandle = getBridge().getPlugin("SocialLogin");
      if (pluginHandle == null) {
        Log.i("Google Activity Result", "SocialLogin login handle is null");
        return;
      }
      Plugin plugin = pluginHandle.getInstance();
      if (!(plugin instanceof SocialLoginPlugin)) {
        Log.i("Google Activity Result", "SocialLogin plugin instance is not SocialLoginPlugin");
        return;
      }
      ((SocialLoginPlugin) plugin).handleGoogleLoginIntent(requestCode, data);
    }
  }

  // This function will never be called, leave it empty
  @Override
  public void IHaveModifiedTheMainActivityForTheUseWithSocialLoginPlugin() {}
}

Después debemos inicializar el plugin. Para eso llamamos al método initialize antes de poder hacer login y solo una vez. Es recomendable hacerlo en app.component.ts:

//...
import { GoogleAuth } from '@codetrix-studio/capacitor-google-auth';
//...
export class AppComponent {
  //...
  constructor() {
    //...
    this.initializeApp();
  }

  async initializeApp() {
    if (this.#platform.is('mobile')) {
      await this.#platform.ready();
      //...
      await SocialLogin.initialize({
        google: {
          webClientId: 'id cliente WEB de Google', // the web client id for Android and Web
        },
      });
    }
  }
}

Ya podemos crear un botón (como queramos) y lanzar el login de Google. Te devolverá un objeto con varias propiedades. El token que debes enviar a tu servidor para que te registre o valide el login está en la propiedad idToken de la respuesta.

<!-- ... -->
<ion-content class="ion-padding" [fullscreen]="true">
  @if (user()) {
    <ion-list>
      <ion-item text-wrap>
        <ion-avatar slot="start">
          <ion-img [src]="user()!.imageUrl"></ion-img>
        </ion-avatar>
        <ion-label>
          <h2>{{user()!.name}}</h2>
          <p>{{user()!.email}}</p>
        </ion-label>
      </ion-item>
    </ion-list>
  }

  <ion-button expand="block" color="danger" (click)="login()">
    <ion-icon name="logo-google" slot="start"></ion-icon>
    <ion-label>Login with Google</ion-label>
  </ion-button>

  {{user | json}}
</ion-content>

Facebook Login

Para el login de Facebook, podemos utilizar el mismo plugin que hemos visto anteriormente: Capacitor Social Login.

Para configurar este plugin debemos tener un proyecto ya creado en la Consola de desarrolladores de Facebook. Los pasos se pueden seguir en el apartado de Login de Facebook en la sección Integrar librerías del curso de Angular.

Una vez estamos en el proyecto de Facebook, en la sección Información Básica, y abajo del todo pulsaremos sobre Añadir plataforma. Seleccionaremos Android y en tienda de aplicaciones Google Play (por ejemplo. No tiene efecto hasta que la publiquemos).

Una vez creada la plataforma, selecciona Inicio rápido para configurarla.

Al igual que con Google, proporciona el nobmre del paquete (id de aplicación) que puedes localizar en capacitor.config.ts. La clase de la actividad predeterminada será el nombre del paquete seguido de MainActivity.

Posteriormente hay que generar el hash del certificado de la aplicación (como con Google) pero en base64. Si no está el comando openssl en Windows, puedes descargar OpenSSL para Windows. Los comandos serían los siguientes:

  • Windows → keytool -exportcert -alias androiddebugkey -keystore %HOMEPATH%\.android\debug.keystore | openssl sha1 -binary | openssl base64
  • Linux/Mac → keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore | openssl sha1 -binary | openssl base64

En el siguiente paso, pegamos la última línea generada por el comando, que será el hash en base64:

Instalamos el plugin:

npm i @capacitor-community/facebook-login

A continuación debemos inicializar el plugin. Lo haremos en el componente principal app.component.ts, después de que se inicialice la aplicación.

export class AppComponent {
  //...
  constructor() {
    //...
    this.initializeApp();
  }

  async initializeApp() {
    if (this.#platform.is('mobile')) {
      //...
      await SocialLogin.initialize({
        //...
        facebook: {
          appId: '1100988247798042',
          clientToken: '220465b47c8d891de3c0fbcf25e5c1fe',
        },
      });
    }
  }
}

El identificador de acceso de cliente puede encontrarse en Opciones avanzadasSeguridad.

<!-- ... -->
<ion-content class="ion-padding"  [fullscreen]="true">
  @if (accessToken()) {
    <h3>Access token:</h3>
    <p>{{accessToken()}}</p>
  }
  <ion-row>
    <ion-col>
      <ion-button expand="block" color="primary" (click)="login()"
		    [disabled]="accessToken() !== ''">Login with Facebook</ion-button>
    </ion-col>
    <ion-col>
      <ion-button expand="block" color="danger" fill="outline" (click)="logout()"
		    [disabled]="accessToken() === ''">Logout</ion-button>
    </ion-col>
  </ion-row>
</ion-content>

Barcode Scanner

El plugin Barcode Scanner está creado por Capawesome, que tiene otros plugins interesantes para Capacitor en su repositorio de Github. Con este plugin podemos escanear códigos de barras y QR, tanto desde de la cámara como desde fotos ya almacenadas.

npm install @capacitor-mlkit/barcode-scanning

Añade los siguientes permisos a AndroidManifest.xml (solo los que no estuviera ya):

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application ...>
      <meta-data android:name="com.google.mlkit.vision.DEPENDENCIES" android:value="barcode_ui"/>
      <!-- ... -->
    </application>
    <!-- ... -->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.FLASHLIGHT"/>
    <uses-feature android:name="android.hardware.camera" android:required="false" />
</manifest>

Para escanear códigos de barras tenemos 2 opciones:

  • Con el método startScan() que abre la cámara sin salir de la aplicación y permite interactuar con ella (pararla, encender la linterna, etc). Esto es más interactivo y rápido, pero requiere añadir CSS adicional para ocultar el elemento body y que se vea la cámara.
  • Con el método scan() que abre la cámara del sistema en modo escaner de códigos de barras y QR. En Android esto requiere tener instalado Google Play Services. Se puede consultar si el módulo está instalado en el teléfono con el método isGoogleBarcodeScannerModuleAvailable() e instalarlo con el método installGoogleBarcodeScannerModule().

Por simplicidad, vamos a optar por el segundo método:

<!-- ... -->
<ion-content class="ion-padding" [fullscreen]="true">
  <ion-button (click)="scan()" [disabled]="!installed()">Scan</ion-button>
  <p>{{data() | json}}</p>
</ion-content>

SQLite

El plugin de SQLite permite utilizar una base de datos ligera SQLite para almacenar datos de la aplicación. Es una base de datos SQL ligera y limitada que no necesita ningún servicio ejecutándose y guarda los datos en archivos. Soporta ciertas características avanzadas como claves ajenas, índices, triggers y vistas.

npm i @capacitor-community/sqlite

<!-- ... -->
<ion-content class="ion-padding" [fullscreen]="true">
  @if (open()) {
  <p>Connected to SQLite!</p>
  }
  <form #personForm="ngForm" (ngSubmit)="add(personForm)">
    <ion-list>
      <ion-item>
        <ion-input type="text" label="Name" name="name" [(ngModel)]="person.name" required>
        </ion-input>
      </ion-item>
      <ion-item>
        <ion-input type="number" label="Age" name="age" [(ngModel)]="person.age" required>
        </ion-input>
      </ion-item>
    </ion-list>
    <div>
      <ion-button type="submit" expand="block" [disabled]="personForm.invalid"
        >Add</ion-button
      >
    </div>
  </form>

  <ion-list>
    @for (person of people(); track person.id; let i = $index) {
    <ion-item>
      {{person.name}} ({{person.age}})
      <ion-button fill="clear" slot="end" (click)="remove(person, i)">
        <ion-icon slot="icon-only" name="trash" color="danger"></ion-icon>
      </ion-button>
    </ion-item>
    } @empty {
      <ion-item>No people in database...</ion-item>
    }
  </ion-list>

  <p>{{people() | json}}</p>
</ion-content>

Flashlight

El plugin Flash está mantenido por la empresa CapGo que tiene varios plugins interesantes en sus repositorios. Este plugin permite encender y apagar el flash de la cámara a modo de linterna.

Para utilizar el flash debemos añadir los permisos tanto para la cámara como para el flash. El atributo android:required a true implicaría que la aplicación solo se podría instalar en dispositivos que tengan esa característica hardware disponible (cámara, flash, etc.).

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- ... -->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.FLASHLIGHT"/>
    <uses-feature android:name="android.hardware.camera" android:required="false" />
    <uses-feature android:name="android.hardware.camera.flash" android:required="false" />
</manifest>

npm install @capgo/capacitor-flash

<!-- ... -->
<ion-content class="ion-padding" [fullscreen]="true">
  <ion-button
    expand="block"
    [color]="on() ? 'danger' : 'success'"
    (click)="toggleFlash()"
  >
    <ion-icon name="flashlight" slot="start"></ion-icon>
    Turn {{on() ? 'off' : 'on'}}
  </ion-button>
</ion-content>

Depurar la aplicación en el teléfono

Se puede depurar la aplicación desde el navegador o desde VS Code o desde el navegador. Por ejemplo desde Chrome con la aplicación funcionando en el teléfono y este conectado al PC, escribiríamos en la barra de direcciones: chrome://inspect

<< Componentes (Parte 2) Router de Ionic >>