Cordovaアプリケーション開発に、NoSQLドキュメント指向組み込みデータベースCouchbase Liteを使うには？（Androidネイティブプラグイン構築手順）

## はじめに

先に、下記の記事を（iOSネイティブプラグイン構築について）発表しました。

https://qiita.com/yoshiyuki_kono/items/9122beb6b86847a82788

CordovaとCouchbase Liteに関する基本については、そちらをご覧ください。

## チュートリアル紹介

Couchbase公式チュートリアルとして下記があります。

https://docs.couchbase.com/tutorials/hotel-lister/android.html

本稿では、CordovaアプリケーションでCouchbase Liteを使用する方法を解説します。

このチュートリアルでは、AndroidでCouchbase Lite2.xとインターフェイスするCordovaプラグインを構築する方法について説明しています。このチュートリアルでは、フロントエンドWebアプリレイヤーの構築にIonicを使用しています。

チュートリアルでは、Couchbase Liteデータベースに保存されているホテルに関する情報を表示および検索するサンプルアプリケーションが用いられています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/aa96f579-45fb-d874-c087-9df2901f299b.png)


## 前提条件

このチュートリアルは、次のコンポーネントとバージョンを前提としています。

- Android Studio 3.0
- Android SDK 19
- Couchbase Lite 2.1.0

上記からも分かるように、このチュートリアルは発表時点のテクノロジースタックを元にしています。

開発を始め際には、前提となるフレームワーク、IonicとCordovaの準備が必要になりますが、チュートリアルが提供するサンプルプロジェクトは、過去のバージョンが用いられています。

本稿では、チュートリアルのサンプルアプリそのものの構築からは離れて、Couchbase Liteプラグイン構築手順に焦点を当てて解説します。また、IonicおよびCordovaのインストール手順についても、割愛します。

### Couchbase Liteのセットアップ
Couchbase LiteをAndroid Studioプロジェクトにインポートするには、build.gradleに下記の内容を追加します。

```gradle:build.gradle
dependencies {
    implementation fileTree(dir: 'libs', include: '*.jar')
    // SUB-PROJECT DEPENDENCIES START
    implementation(project(path: ":CordovaLib"))
    compile "com.android.support:support-annotations:27.+"
    // SUB-PROJECT DEPENDENCIES END
    implementation 'com.couchbase.lite:couchbase-lite-android:2.1.0'
}
```

上記の内容は、チュートリアルで用いられているバージョンに関する記述です。

適宜、下記の記事もご参考ください。

https://qiita.com/yoshiyuki_kono/items/b8facccc9a2763ab1f76

https://qiita.com/yoshiyuki_kono/items/fef9051657a423f02fb7



## Androidネイティブプラグイン構築手順

Cordovaプラグインを使うと、JavaScriptからネイティブコードに、アクセスすることができます。プラグインは、通常Webベースのアプリでは利用できないデバイスおよびプラットフォーム機能へのアクセスを提供します。

ここtでは、Couchbase LiteのJava APIを使用してcordova-plugin-hotel-listerというプラグインを作成する例を紹介します。

### プラグイン定義ファイル

#### plugin.xml

次のように、plugin.xmlを作成します。

```XML:plugin.xml
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
        xmlns:android="http://schemas.android.com/apk/res/android"
        id="cordova-plugin-hotel-lister" version="0.0.1">
  <name>cordova-plugin-hotel-lister</name>
  <description>An Cordova plugin for the Hotel Lister application to implement data persistence using Couchbase Lite.</description>
  <license>Couchbase CE / https://www.couchbase.com/binaries/content/assets/website/legal/ce-license-agreement.pdf</license>

  <engines>
    <engine name="cordova" version=">=3.0.0"/>
  </engines>

  <js-module src="www/hotel-lister.js" name="HotelLister">
    <clobbers target="window.plugins.HotelLister" />
  </js-module>

  <platform name="android">
    <config-file target="res/xml/config.xml" parent="/*">
      <feature name="HotelLister">
        <param name="android-package" value="com.hotellister.HotelLister"/>
      </feature>
    </config-file>

    <source-file
            src="src/android/HotelLister.java"
            target-dir="src/com/hotellister/" />
    <source-file
            src="src/android/Utils.java"
            target-dir="src/com/hotellister/" />
  </platform>
</plugin>
```

- `id="cordova-plugin-hotel-lister"`属性は、Ionicプロジェクトでインポートするプラグインの名前です。
- XMLタグ`<js-module></js-module>`は、JavaScriptのインタフェースを宣言しているファイルの場所を示します。
- XMLタグ<platform name="ios"></platform>は、ネイティブコードを保持するためのJavaファイルの場所を宣言します。

#### package.json

次のように、cpackage.jsonを作成します。

```JAVASCRIPT:package.json
{
  "name": "cordova-plugin-hotel-lister",
  "version": "0.0.1",
  "description": "A Cordova plugin for the Hotel Lister app.",
  "cordova": {
    "id": "cordova-plugin-hotel-lister",
    "platforms": [
      "android",
      "ios"
    ]
  },
  "engines": [
    {
      "name": "cordova",
      "version": ">=3.0.0"
    }
  ],
  "author": "Couchbase <mobile@couchbase.com>",
  "license": "Couchbase CE / https://www.couchbase.com/binaries/content/assets/website/legal/ce-license-agreement.pdf"
}
```

### API定義ファイル
このファイルはプラグインについて説明しています。

Cordova/Ionic　Webアプリがプラグインと対話するためのJS APIを定義します。

このことで、プラグインがwindow要素にバインドされます。そうすることで、単純なJavaScript参照を介してアクセスが許可されます。

ここでは、以下のように、hotel-lister.jsという名称のファイルを作成します。

```JAVASCRIPT:hotel-lister.js
var exec = require('cordova/exec');

var hotelLister = {
  /* code will be added here later */
};

module.exports = hotelLister;
```

### ネイティブソース雛形作成

次のように、`HotelLister.java`を作成します。こ
のファイルは、`hotel-lister.js`で定義されているAPIに対応するネイティブのJava実装を提供します。

```JAVA:HotelLister.java
package com.hotellister;

import android.content.Context;

import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.json.JSONArray;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class HotelLister extends CordovaPlugin {

    @Override
    public void initialize(CordovaInterface cordova, CordovaWebView webView) {
        super.initialize(cordova, webView);
    }

    @Override
    public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) {
        return false;
    }

}
```

Cordovaプラグインは、JavaScript WebViewアプリで最初に呼び出されたときにインスタンス化されます。この`pluginInitialized()`メソッドにはスタートアップコードが含まれています。
この`exec`メソッドは、JavaSciptからJava側にリクエストをマーシャリングします。

詳細については、以下のプラグインライフサイクルのドキュメントを参照してください。

https://cordova.apache.org/docs/en/latest/guide/platforms/android/plugin.html#plugin-initialization-and-lifetime


### プラグインインストール

プラグインが期待どおりに機能する準備ができていることを確認するには、プラグインをHotelListerプロジェクトにインストールする必要があります。これを行うには、次のコマンドを実行します。

```BASH
cordova plugin add ../cordova-plugin-hotel-lister/
```

成功すると、出力に次のように表示されます。

```BASH
Adding cordova-plugin-hotel-lister to package.json
Saved plugin info for "cordova-plugin-hotel-lister" to config.xml
```



## 新しいネイティブソースの追加

データベース管理クラスを追加します。

以下のように、DatabaseManager.javaを作成します。

```JAVA:DatabaseManager.java
package com.hotellister;

import android.content.Context;

import com.couchbase.lite.CouchbaseLiteException;
import com.couchbase.lite.Database;
import com.couchbase.lite.DatabaseConfiguration;
import com.couchbase.lite.FullTextIndex;
import com.couchbase.lite.FullTextIndexItem;
import com.couchbase.lite.IndexBuilder;

public class DatabaseManager {

    private static String DB_NAME = "travel-sample";

    private static Database database;
    private static DatabaseManager instance = null;

    private DatabaseManager(Context context) {
        if (!Database.exists("travel-sample", context.getFilesDir())) {
            String assetFile = String.format("%s.cblite2.zip", DB_NAME);
            Utils.installPrebuiltDatabase(context, assetFile);
        }
        DatabaseConfiguration configuration = new DatabaseConfiguration(context);
        try {
            database = new Database(DB_NAME, configuration);
        } catch (CouchbaseLiteException e) {
            e.printStackTrace();
        }
    }

    public static DatabaseManager getSharedInstance(Context context) {
        if (instance == null) {
            instance = new DatabaseManager(context);
        }
        return instance;
    }

    public static Database getDatabase() {
        if (instance == null) {
            try {
                throw new Exception("Must call getSharedInstance first");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return database;
    }

}
```

このコードでは、まず「travel-sample」という名前のデータベースがデフォルトのCouchbase Liteディレクトリに存在するかどうかを確認します。存在しない場合は、アプリにバンドルされていたtravel-sample.cblite2データベースファイルをデフォルトのCouchbase Liteディレクトリにコピーされます。

このサンプルでは、事前にホテルのデータが登録されたCouchbase Liteデータベースを使っています。そのため、プロジェクトに、事前に構築されたデータベースファイルが追加されていることが前提となります。


#### plugin.xmlの`<platform/>`要素編集

新しいJavaファイルを追加するときは、プラグインの構成ファイルに参照を追加する必要があります。
以下の要素を、plugin.xmlの`<platform name="ios"></platform>`要素に挿入します。

```XML:plugin.xml
<source-file
        src="src/android/DatabaseManager.java"
        target-dir="src/com/hotellister/" />
```

### ネイティブソース実装追加



次のインポートステートメントをHotelLister.javaの先頭に追加します。

```JAVA:HotelLister.java
import com.couchbase.lite.CouchbaseLiteException;
import com.couchbase.lite.DataSource;
import com.couchbase.lite.Database;
import com.couchbase.lite.Expression;
import com.couchbase.lite.Meta;
import com.couchbase.lite.Query;
import com.couchbase.lite.QueryBuilder;
import com.couchbase.lite.Result;
import com.couchbase.lite.ResultSet;
import com.couchbase.lite.SelectResult;
```

HotelLister.javaに次のインスタンス変数を追加します。

```JAVA:HotelLister.java
private Database database;
```

次に、HotelLister.javaの`initialize`メソッドを次のように更新して、`database`インスタンスプロパティを設定します。

```JAVA:HotelLister.java
@Override
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
    super.initialize(cordova, webView);
    Context context = cordova.getContext();
    DatabaseManager.getSharedInstance(context);
    this.database = DatabaseManager.getDatabase();
}
```

さらに、データベースからホテルを一覧表示する機能を追加します。

`queryHotels`メソッドの実装をHotelLister.javaに追加します。

```JAVA:HotelLister.java
private List<Result> queryHotels() {
    String DOC_TYPE = "hotel";

    Query query = QueryBuilder
        .select(
            SelectResult.expression(Meta.id),
            SelectResult.property("address"),
            SelectResult.property("phone"),
            SelectResult.property("name")
        )
        .from(DataSource.database(database))
        .where(
            Expression.property("type")
                .equalTo(Expression.string(DOC_TYPE))
        );

    try {
        ResultSet resultSet = query.execute();
        return resultSet.allResults();
    } catch (CouchbaseLiteException e) {
        e.printStackTrace();
        return null;
    }
}
```


これで、HotelLister.javaの`execute`メソッドからこのメソッドを呼び出すことができます。

JavaScriptの`exec`関数を使用してプラグインにディスパッチされたものはすべて、プラグインクラスの`execute`メソッドに渡されます。

```JAVA:HotelLister.java
@Override
public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) {
    if ("queryHotels".equals(action)) {
        List<Result> resultList = queryHotels();
        ArrayList<Map<String,Object>> arrayList = new ArrayList<>();
        assert resultList != null;
        for (Result result : resultList) {
            Map<String,Object> map = result.toMap();
            arrayList.add(map);
        }
        JSONArray jsonArray = new JSONArray(arrayList);
        callbackContext.success(jsonArray);
        return true;
    }
    return false;
}
```

次に、プラグインのJavaScriptインターフェースを更新します。hotel-lister.jsに次のコードを追加します。

```JAVASCRIPT:hotel-lister.js
queryHotels: function(successCallback, errorCallback) {
  exec(successCallback, errorCallback, 'HotelLister', 'queryHotels');
},
```

これで、Ionicプロジェクトで`queryHotels`メソッドを呼び出すことができます。home.tsに以下を追加します。

```JAVASCRIPT:home.ts
this.platform.ready().then(() => {
  window.plugins.HotelLister.queryHotels((hotels) => {
    console.log(hotels);
    this.hotels = hotels;
  });
});
```

変更をテストするには、Cordovaプラグインをcordova-plugin-hotel-listerからHotelListerに再インストールする必要があります。start-projectフォルダーのルートから次のコマンドを実行します


### Cordovaプラグイン再インストール

変更を反映するには、Cordovaプラグインを再インストールする必要があります。

ビルドして実行します。


## 最後に

本稿では、Cordovaアプリケーション開発のために、Android用のCouchbase Liteネイティブプラグインを構築する手順について解説しました。


Couchbase Liteについての記事を以下の投稿で整理していますので、ご関心に応じて、参照してみてください。

https://qiita.com/yoshiyuki_kono/items/6a5bbad1b02196a25417

Couchbase Liteは、Couchbase Serverとの自動双方向同期が可能です。

本稿で扱っていない、Couchbase Serverについては、日本語で読むことができるまとまった情報として、次の拙著を紹介させていただきます。

https://www.amazon.co.jp/-/en/%E6%B2%B3%E9%87%8E-%E6%B3%B0%E5%B9%B8/dp/429560030X/ref=tmm_pap_swatch_0?_encoding=UTF8&qid=1636588872&sr=1-1