July 7, 2022
  • All
  • Capacitor
  • plugins

Take Control of Your Capacitor App State

Simon Grimm

This is a guest post from Simon Grimm, Ionic Developer Expert and educator at the Ionic Academy. Simon runs a YouTube channel where he helps web developers build awesome mobile apps based on their existing skills!

What happens when your app goes to the background? How do you react when your app is restored? These questions and more can be answered by adding the Capacitor App plugin to your app, which brings a handful of utility functions and listeners to your web native app!

In this tutorial we will explore all features of the plugin to control our state, catch events like the Android back button or restored app state, and finally even add a simple handler for a custom URL scheme to open our app!

To follow along simply start a new Ionic app since it already comes with Capacitor and install the App plugin afterwards:

ionic start capacitorState blank --type=angular
cd ./capacitorState
npm install @capacitor/app

However, you can follow along with any web project and framework where you added Capacitor as well!

Getting your App information

Let’s begin with some basic functionality, which is loading your apps information. This can be helpful for debugging or presenting your apps version and build number, and the getInfo() function returns an object with that information.

Additionally we can also test two of the functions to close and minimize our app but be aware that those only work on Android!

Bring up the src/app/home/home.page.ts now and change it to:

import { Component } from '@angular/core';
import { App } from '@capacitor/app';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {
  appInfo = null;

  constructor() {}

  async getInfo() {
    this.appInfo = await App.getInfo();
  }

  // Android only!
  minimize() {
    App.minimizeApp();
  }

  // Android only!
  close() {
    App.exitApp();
  }
}

To quickly test our functions let’s change the src/app/home/home.page.html to this:

<ion-header>
  <ion-toolbar>
    <ion-title> Capacitor State </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-card>
    <ion-card-content> {{ appInfo | json }} </ion-card-content>
  </ion-card>
  <ion-button expand="full" (click)="getInfo()">Get info</ion-button>
  <ion-button expand="full" (click)="minimize()">Minimize</ion-button>
  <ion-button expand="full" (click)="close()">Close</ion-button>
</ion-content>

Now you can build your Capacitor app and run it on a connected device or simulator and you should be able to retrieve the general information and close the app on Android!

Capacitor app info

Helpful but not overly exciting. Let’s move on.

Listening to your App State and Back Button

If you play sound or a video in your app you might want to pause it before the app goes to the background. Likewise you might want to automatically restart your background music when the app becomes active again.

Additionally you might want to have more control of the back button of Android devices so you can display an alert before the app is actually closed.

For these things we can use the addListener() function and listen to two different events in our src/app/app.component.ts:

import { Component } from '@angular/core';
import { App } from '@capacitor/app';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.scss'],
})
export class AppComponent {
  constructor() {
    this.setupListener();
  }

  async setupListener() {
    App.addListener('appStateChange', ({ isActive }) => {
      if (!isActive) {
        // App went to background
        // Save anything you fear might be lost
      } else {
        // App went to foreground
        // restart things like sound playing
      }
    });

    App.addListener('backButton', (data) => {
      console.log('back button click:', JSON.stringify(data));
      if (data.canGoBack) {
        window.history.back();
      } else {
        // Maybe show alert before closing app?
        App.exitApp();
      }
    });
  }
}

These events are really powerful and help to build even better apps with more control about what happens when users open or close the app.

There are two more events, and we’ll inspect them in detail next!

Restoring Activity Results

On older Android devices the following can happen: You start a plugin (like the camera), and because of memory limitations your apps activity is killed.

When you are done capturing an image the app starts again, but it’s a fresh app and not in the state where you left it.

To handle this scenario you can use the appRestoredResult event and grab any information that was passed to the startup of your application.

For this example I’ve installed the Capacitor Camera Plugin and implemented the standard function to take a picture. On top of that I’ve now also added the listener to our app state inside the src/app/home/home.page.ts like this:

import { Component } from '@angular/core';
import { App } from '@capacitor/app';
import { Camera, CameraResultType } from '@capacitor/camera';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {
  appInfo = null;
  image = null;

  constructor() {
    App.addListener('appRestoredResult', (data) => {
      console.log('My state:', JSON.stringify(data));
      if (
        data.pluginId == 'Camera' &&
        data.methodName == 'getPhoto' &&
        data.success
      ) {
        this.image = data.data.webPath;
      }
    });
  }

  async takePicture() {
    const image = await Camera.getPhoto({
      quality: 90,
      resultType: CameraResultType.Uri,
    });
    this.image = image.webPath;
  }
}

Since this event could be triggered by any other plugin or outside event, we need to be careful and check exactly if the plugin we expect and the method we want to catch are part of the data we received!

If your device doesn’t close the activity on Android there’s a developer setting that you can enable to test this behaviour:

Open the developer settings on your Android device and enable “Don’t keep activities”. By doing this, your app will be killed whenever it enters the background (e.g. when the camera activity starts).

Catching the Launch URL

The last feature of the App plugin is the ability to catch the appUrlOpen event, something you need to handle if you plan to use a custom URL scheme or even Deeplinks with Capacitor.

Setting up deeplinks (also know now as Universal Links) is a complicated process that involves uploading files to your server, but implementing a custom URL scheme so your app can be opened from the outside is way faster.

All we need to do in terms of real app functionality is catch the event and then handle it however we want to. It usually contains a URL, so you could split that URL and grab an ID, then use that information to trigger your router and move to the correct page of your app.

Let’s begin with the code inside the src/app/app.component.ts like this:

import { Component } from '@angular/core';
import { App, URLOpenListenerEvent } from '@capacitor/app';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.scss'],
})
export class AppComponent {
  constructor() {
    this.setupListener();
  }

  async setupListener() {
    App.addListener('appUrlOpen', (data: URLOpenListenerEvent) => {
      console.log('App opened with URL:', JSON.stringify(data));
      const openUrl = data.url;
      alert(openUrl);
      // Use URL for routing to the right page!
    });

    // Alternative easy way to access the value once
    const { url } = await App.getLaunchUrl();
  }
}

Now the hard part is adding the right information to our native platforms.

Android Custom URL Scheme

For Android we need to add another intent-filter within the activity tag of our android/app/src/main/AndroidManifest.xml:

 <activity…>
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="@string/custom_url_scheme" />
    </intent-filter>
</activity>

This is now looking for a custom_url_scheme which we need to add (or usually change since it exists) inside of our android/app/src/main/res/values/strings.xml:

<?xml version='1.0' encoding='utf-8' ?>
<resources>
    <string name="app_name">capacitorState</string>
    <string name="title_activity_main">capacitorState</string>
    <string name="package_name">io.ionic.starter</string>
    <string name="custom_url_scheme">simonsapp</string>
</resources>

In my case I used the name “simonsapp“, which allows me to open the app by using something like “simonsapp://app/product/42”.

Additionally I still have the initial io.ionic.starter package name.

This means after deploying the app to my Android device, I can use the Android Debug Bridge to open the app with a specific URL:

adb shell am start -a android.intent.action.VIEW -d "simonsapp://app/news/123" io.ionic.starter

Note that only because there’s an URL in the event, it won’t automatically use that URL to navigate in your app.

That part is completely up to you, but to the end user it will feel like they directly jumped into a page of your app!

iOS Custom URL Scheme

For iOS we also need one tiny change, which is adding an object like this to our ios/App/App/Info.plist:

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLName</key>
    <string>com.ionic.academy</string>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>simonsapp</string>
    </array>
  </dict>
</array>

We can see simonsapp in there again, and on iOS we could simply open Safari and use simonsapp://app/news/123 as the URL and it will open our app!

Capacitor open App from URL

This might already be enough in many cases where you simply want to allow another installed app to directly jump into your app.

Conclusion

The Capacitor App plugin looks like a small fish among other plugins, but having control over you app state and opening information can dramatically improve user experience and fix edge cases like on old Android devices.

If you want to get even more support for your apps, check out the Ionic Academy in which I helped thousands of Ionic developers to build amazing Ionic apps and support them through our private community.

And if you enjoy video tutorials, don’t forget to head over to my YouTube channel and join the SIMONICS community!


Simon Grimm