中安拓也のブログ

プログラミングについて書くブログ。 Twitterやってます @l08084

Angular + Firebase Realtime Databaseでメンテナンスと強制バージョンアップのポップアップを表示する

はじめに

今回は、Angular(Ionic)で作ったアプリからFirebase Realtime Databaseを参照して、メンテナンスと強制バージョンアップを知らせるポップアップを表示する機能を作成します。

Firebaseが提供しているデータベースについて

まず、Firebaseが提供している二つのデータベースの違いについて説明します。

  • Cloud Firestore
    • データをドキュメントのコレクションとして扱う。多彩なクエリを使えるため検索に強く、Realtime Databaseよりも複雑で階層的なデータを扱いやすい
  • Realtime Database
    • データを単一のJSONツリーとして扱う。Cloud Firestoreよりもレイテンシーが低いため、データの同期が早い

Realtime Databaseではなく、基本的に後続サービスのCloud Firestoreを使ってください、と書かれている記事が多かったんですが、今回はデータのread/writeのスピードがより早いRealtime Databaseを採用します。

環境

TypeScriptベースのフレームワークであるAngularと、iOS/AndroidのハイブリッドモバイルアプリケーションのフレームワークであるIonicを使用しています。

  • "@angular/fire": "^6.1.4"
  • "firebase": "^8.3.1"
  • "semver": "^7.3.5"

ionic infoコマンドの実行結果

$ ionic info

Ionic:

   Ionic CLI                     : 6.11.8 (/usr/local/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 5.6.0
   @angular-devkit/build-angular : 0.1101.4
   @angular-devkit/schematics    : 11.1.4
   @angular/cli                  : 11.1.4
   @ionic/angular-toolkit        : 3.1.0

Cordova:

   Cordova CLI       : 8.0.0
   Cordova Platforms : none
   Cordova Plugins   : no whitelisted plugins (1 plugins total)

Utility:

   cordova-res : not installed
   native-run  : not installed

System:

   ios-deploy : 1.9.2
   ios-sim    : 6.1.2
   NodeJS     : v12.13.1 (/usr/local/bin/node)
   npm        : 6.14.12
   OS         : macOS Catalina
   Xcode      : Xcode 12.0.1 Build version 12A7300

環境構築

環境構築をしていきます。Firebaseのプロジェクトを作成した後、今回はWebアプリを作成するため、プラットフォームのウェブのアプリを追加します。

f:id:l08084:20210327175441p:plain
Firebaseのプロジェクトを作成する

続いて、プラットフォーム「ウェブアプリ」のSDKスニペットのfirebaseConfigの内容をAngular(Ionic)プロジェクトのsrc/environments/environment.tsに転記します。

f:id:l08084:20210327180409p:plain
編集後のenvironment.ts

AngularからFirebaseに接続する用途のライブラリである、AngularFireをnpmインストールします。

今回作成しているアプリはAngular CLIから作ったプロジェクトではなく、AngularベースのIonicプロジェクトであるため、公式が推奨しているng addコマンドではなく、下記のコマンドを使用します。

npm i firebase @angular/fire

app.module.tsimports: []AngularFireModule.initializeApp(environment.firebaseConfig)を追加します。

app.module.ts

import { AngularFireModule } from '@angular/fire';
import { environment } from 'src/environments/environment';
@NgModule({
  // ...省略
  imports: [
    // add this
    AngularFireModule.initializeApp(environment.firebaseConfig),
  ],
})
export class AppModule {}

強制バージョンアップメッセージの表示判定に使用する、セマンティック バージョニングを比較することができるライブラリ、node-semverもインストールします。

npm i semver

補足

今回はブラウザ上でしかアプリを動かさないので使えませんが、モバイルアプリ(iOS/Android)としても動かすときは、アプリバージョンを取得するライブラリであるcordova-plugin-app-versionを使ったりします。

ionic cordova plugin add cordova-plugin-app-version
npm i @ionic-native/app-version
npm i @ionic-native/core

Realtime Databaseのデータ構造を構築する

Realtime Databaseのデータ構造をJSONファイルとして作成します。

今回は、メンテナンスと強制バージョンアップのポップアップを表示したいので、下記のような構造のJSONファイルにしました。

{
  "maintenance": {
    "maintenanceFlg": false,
    "message": "メンテナンスのため一時サービスを停止しております。<br/>しばらくお待ちください。",
    "title": "メンテナンス"
  },
  "version": {
    "message": "最新版のアプリがリリースされました。<br/>バージョンアップをお願いいたします。",
    "minimumVersion": "0.0.3",
    "title": "バージョンアップ"
  }
}

上記で作成したJSONファイルをRealtime Databaseにインポートします。

f:id:l08084:20210329162221p:plain
「JSONをインポート」を選択する

JSONファイルをインポートすると、下記画像のように、Realtime Database上にデータ構造が作成されることを確認できます。

f:id:l08084:20210329162340p:plain
JSONインポート後のRealtime Database

メンテナンスと強制バージョンアップのポップアップを表示する

作成したRealtime Databaseのデータ構造を使って、ポップアップを表示するサービスクラスVersionCheckServiceを作成していきます。

src/app/services/version-check.service.ts

import { Injectable } from '@angular/core';
import { AngularFireDatabase } from '@angular/fire/database';
import { Observable } from 'rxjs';
import { Maintenance } from '../model/maintenance.model';
import { Version } from '../model/version.model';
import * as semver from 'semver';
import { AlertController } from '@ionic/angular';
@Injectable({
  providedIn: 'root',
})
export class VersionCheckService {
  // このアプリのバージョン
  private readonly appVersion = '1.0.0';

  private maintenance$: Observable<Maintenance>;
  private version$: Observable<Version>;
  private maintenanceAlert: HTMLIonAlertElement;
  private versionUpAlert: HTMLIonAlertElement;

  constructor(
    private db: AngularFireDatabase,
    private alertController: AlertController
  ) {}

  /**
   * 初期設定
   *
   * @memberof VersionCheckService
   */
  public initSetting(): void {
    // Realtime Databaseからデータを取得
    this.maintenance$ = this.db
      .object<Maintenance>('maintenance')
      .valueChanges();
    this.version$ = this.db.object<Version>('version').valueChanges();

    this.maintenance$.subscribe(
      async (maintenance: Maintenance) =>
        await this.checkMaintenance(maintenance)
    );
    this.version$.subscribe(
      async (version: Version) =>
        await this.checkVersion(this.appVersion, version)
    );
  }

  /**
   * メンテナンスポップアップを表示する。
   *
   * @private
   * @param {Maintenance} maintenance
   * @returns {Promise<void>}
   * @memberof VersionCheckService
   */
  private async checkMaintenance(maintenance: Maintenance): Promise<void> {
    if (!maintenance) {
      return;
    }

    if (!maintenance.maintenanceFlg) {
      // メンテナンスフラグがOFFだったら処理を中断する
      if (this.maintenanceAlert) {
        // メンテナンスメッセージが開かれている場合は閉じる
        await this.maintenanceAlert.dismiss();
        this.maintenanceAlert = undefined;
      }
      return;
    }

    // メンテナンスメッセージを表示する
    this.maintenanceAlert = await this.alertController.create({
      header: maintenance.title,
      message: maintenance.message,
      backdropDismiss: false, // 背景をクリックしても閉じない
    });
    await this.maintenanceAlert.present();
  }

  /**
   * 強制バージョンアップメッセージを表示する。
   *
   * @private
   * @param {string} appVersion
   * @param {Version} version
   * @returns
   * @memberof VersionCheckService
   */
  private async checkVersion(appVersion: string, version: Version) {
    if (!version || !version.minimumVersion) {
      return;
    }

    if (semver.gte(appVersion, version.minimumVersion)) {
      // 最低バージョンよりもアプリのバージョンが高かったら処理を中断する
      if (this.versionUpAlert) {
        // 強制バージョンアップメッセージが開かれている場合は閉じる
        await this.versionUpAlert.dismiss();
        this.versionUpAlert = undefined;
      }
      return;
    }

    // 強制バージョンアップメッセージを表示する
    this.versionUpAlert = await this.alertController.create({
      header: version.title,
      message: version.message,
      backdropDismiss: false, // 背景をクリックしても閉じない
    });
    await this.versionUpAlert.present();
  }
}

メンテナンスメッセージ用のクラスを作成します。

src/app/model/maintenance.model.ts

export interface Maintenance {
  maintenanceFlg: boolean;
  message: string;
  title: string;
}

強制バージョンアップメッセージ用のクラスを作成します。

src/app/model/version.model.ts

export interface Version {
  message: string;
  minimumVersion: string;
  title: string;
}

最後にAppComponentからVersionCheckServiceからinitSetting()を呼び出してあげれば実装は終了です。

src/app/app.component.ts

// ...省略
export class AppComponent implements OnInit {
  constructor(private versionCheckService: VersionCheckService) {}

  public ngOnInit(): void {
    this.versionCheckService.initSetting();
  }
}

動作確認

上記で作成したコードの動作確認をします。

メンテナンスメッセージの表示

Realtime Database上のmaintenanceFlgtrueに書き換えると、アプリ上でメンテナンスメッセージが表示されます。

f:id:l08084:20210329162506p:plain
maintenanceFlgをtrueに書き換える

f:id:l08084:20210329160227p:plain
アプリ上でメンテナンスメッセージが表示される

強制バージョンアップメッセージの表示

アプリのバージョン(1.0.0)よりも大きいバージョンを、Realtime Database上のminimumVersionに上書くと、アプリ上で強制バージョンアップメッセージが表示されます。

f:id:l08084:20210329160822p:plain
minimumVersionの値を書き換える

f:id:l08084:20210329160902p:plain
強制バージョンアップメッセージが表示される

参考サイト

データベースを選択: Cloud Firestore または Realtime Database  |  Firebase

GitHub - angular/angularfire: The official Angular library for Firebase.

App Version - Ionic Documentation

GitHub - npm/node-semver: The semver parser for node (the one npm uses)

GitHub - ionic-team/ionic-native: Native features for mobile apps built with Cordova/PhoneGap and open web technologies. Complete with TypeScript support. The successor to ngCordova. Pairs exquisitely with a nice bottle of Ionic Framework.

The target entry-point -has missing dependencies: - Ionic Native - Ionic Forum

TypeScript eqの例、semver.eq TypeScriptの例 - HotExamples

ion-alert: Ionic Framework API Docs

セマンティック バージョニング 2.0.0 | Semantic Versioning