Couchbase Lite機能解説：Androidアプリ開発におけるライブクエリ利用

# はじめに

以前にこちらの記事で、ライブクエリ、つまりデータベースの変更を監視するクエリについて、概要を記しました。

https://qiita.com/yoshiyuki_kono/items/8da15296e9a979fbff3d

今回は、以下の公式チュートリアルの内容をベースに、Androidアプリ開発におけるライブクエリの利用について、具体的なコードを交えて解説します。

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

Couchbase Liteについては、[Couchbase Mobileアプリケーション開発へのロードマップ](https://qiita.com/yoshiyuki_kono/items/6a5bbad1b02196a25417)に記事をまとめている他、（これらの記事を元に構成した）以下の電子書籍を無償で頒布しています。

https://techbookfest.org/product/5074954942939136?productVariantID=5042865933647872

また、Couchbase Serverの存在意義、機能詳細、利用方法等については、拙著[NoSQLドキュメント指向データベースCouchbase Serverファーストステップガイド(インプレスR&D刊)](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)や、[NoSQL/JSONデータベースCouchbase Server理解・活用へのロードマップ](https://qiita.com/yoshiyuki_kono/items/4c9cd1115efc5807b49f)にまとめてある記事をご参考ください。

## Androidアプリ開発におけるライブクエリ利用

### RecyclerView
RecyclerViewウィジェットは、Androidプラットフォームにおいて、動的なデータを効率的に表示するための選択肢です。

### 構成

Couchbase LiteをRecyclerViewのデータソースとして使用する際の一般的な構成を以下に示します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/b5c81474-f05b-4565-25ce-26f25a2a6fee.png)


以下、構成要素について、説明します。

- `RecyclerView`は`Activity`によってインスタンス化されます。`RecyclerView`は`Adapter`と関係を持っています。
- `Adapter`は、ViewHolderパターンを使用してデータ項目をビューにバインドする役割を果たします。
- Couchbase Liteは、ビューに表示されるデータのデータソースになります。
- `DatabaseManager`は、CouchbaseLiteの初期化と管理を担当します。
- ライブクエリを構成するリスナーを、アプリは、クエリの結果に影響を与えるデータベースへの変更を受け取るリスナーを登録します。

シーケンスは以下の通りです

- `Activity`は、Couchbase Liteライブクエリを使用してデータベース内のアイテムをクエリし、クエリデータの更新を処理するためにリスナーを登録します。
- データベースでクエリ結果に影響を与えるデータの変更が行われるたびに、リスナーコールバックを介して`Activity`に変更が通知されます。
- アダプターに変更が通知されます。
- RecyclerViewは、更新されたデータセットで更新されます。



## チュートリアル紹介


### インストール

GitHubからUniversityListerプロジェクトのマスターブランチのクローンを作成します。ターミナルで次のコマンドを入力します

```BASH
$ git clone https://github.com/couchbaselabs/UniversityLister-Android.git
```


### アプリ概要
「UniversityLister」アプリは、データソースとしての「University」のリストを`Recyclerview`を使って表示します。
「University」エントリがCouchbase Liteデータベースに追加されるたびに、RecyclerViewが自動的に更新され、新しく追加されたアイテムが表示されます。



## コードによる解説

チュートリアルで利用されているコードについて、開設します。ここでのコードの抜粋は、ライブクエリの解説としてみた場合、冗長な部分もありますが、JSONドキュメントとオブジェクトとの変換についてなど、Couchbase Liteを用いたアプリ開発の例として、参考としてもらえれば幸いです。

### 初期化
ListActivity.javaファイルを開き、`onCreate`メソッドを確認します。ここですべての初期化が行われます。

```JAVA
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Couchbase Liteデータベースマネージャーの初期化
    dbMgr = new DatabaseManager(this); 

    // 典型的なコンテンツレイアウトの初期化
    setContentView(R.layout.activity_list); 

    // ツールバー
    Toolbar toolbar = (Toolbar) findViewById(R.id.university_toolbar);
    setSupportActionBar(toolbar);

    // RecyclerViewの設定
    RecyclerView recyclerView = (RecyclerView)findViewById(R.id.rvUniversities);
    recyclerView.setAdapter(adapter); 
    recyclerView.setLayoutManager(new LinearLayoutManager(this));

    // ローカルサンプルファイルからデータを非同期にロードするためのAsynchronously Load the data from local sample file
    DataFetcher fetcher = new DataFetcher(this,this); 
    fetcher.execute();
}
```

- `DatabaseManager`は、Couchbase Liteのインスタンスの作成/オープンを担当します。
- `DataFetcher`は`AsyncTask`であり、アプリにバンドルされているファイルからサンプルデータを読み込む役割を果たします。これは、データの外部ソースをシミュレートするものです。`AsyncTask`の`execute()`メソッドを呼び出します。これについては、次のセクションで詳しく説明します。

### サンプルデータ読み込み

DataFetcher.javaファイルを開き、`doInBackground()`メソッドを確認します。アプリの起動中、`DataFetcher`クラスはアプリにバンドルされているローカルファイルからサンプルのデータをロードします。データのロードは、`AsyncTask`を使用してバックグラウンドスレッドで実行されます。

```JAVA
@Override
protected List<University> doInBackground(Void... voids) {
    // サンプルファイルパス（コンテンツはJSON形式）
    String fileName = "university_sample.txt";
    StringBuilder stringBuilder = new StringBuilder();
    List<University> universities = null;
    try {
        // assetsフォルダーのローカルサンプルデータファイルからデータをロード
        InputStream inputStream = mContext.getAssets().open(fileName);
 
        // Jacksonライブラリを使って、JSONからUniversity POJOのリストにマップ
        ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 
        universities = Arrays.asList(mapper.readValue(inputStream, University[].class));
        return universities;
    } catch (IOException  e ) {
        e.printStackTrace();
        return null;
    }
}

@Override
protected void onPostExecute(List<University> result) {
    // IDataFetchResponseデレゲート(ここではListActivity)へデータロード完了を通知
    mDelegate.postResult(result);

}
```


この時点では、サンプルデータ(`sampleData`)はCouchbase Liteデータベースに保存されていません。このサンプルデータがどのように使用されるかについては、チュートリアルの後半で説明します。

### ライブクエリ

ListActivity.javaファイルを開き、`QueryForListOfUniversities()`メソッドを確認します。このアクティビティでは、Couchbase Liteデータベースから大学のリストを取得するための「ライブクエリ」を設定します。

```JAVA
private void QueryForListOfUniversities() {
    try {
        // データベースから全てのドキュメントをフェッチするクエリの構築
        query = QueryBuilder.
                select(SelectResult.all()).
                from(DataSource.database(dbMgr.database)); 

        // ライブクエリリスナーを追加
        query.addChangeListener(new QueryChangeListener() { 
                @Override
                    public void changed(QueryChange change) {
                        ResultSet resultRows = change.getResults();
                        Result row;
                        List<University> universities = new ArrayList<University>();
                        // リスナーコールバック内における、結果セットの反復処理
                        while ((row = resultRows.next()) != null) { 
                            // Jacksonライブラリの`ObjectMapper`を使用
                            ObjectMapper objectMapper = new ObjectMapper();
                            // 宣言されていないプロパティを無視する
                            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);


                            // エントリに対応する`ReadOnlyDictionary`オブジェクトを取得
                            Dictionary valueMap = row.getDictionary(dbMgr.database.getName()); 

                            // `ReadOnlyDictionary`タイプから`University`POJOに変換
                            University university = objectMapper.convertValue(valueMap.toMap(),University.class);
                            universities.add(university); 
                        }

                        // Update the adapter with the newly added University documents
                        adapter.setUniversities(universities); 

                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                // 変更されたドキュメントでアダプターへ通知
                                adapter.notifyDataSetChanged(); 
                            }
                        });

                    }
                }
        );
        // リスナーの登録されたクエリの実行
        query.execute(); 
    }
    catch (IllegalArgumentException e) {

    } catch (CouchbaseLiteException e) {
        e.printStackTrace();
    }
}
```



### データベースの更新


ListActivity.javaファイルを開き、`fetchUniversityAndAddToDatabase()`メソッドを確認します。この`fetchUniversityAndAddToDatabase()`メソッドでは、サンプルデータのデータ項目をCouchbase Liteに挿入します。

ここではライブクエリに関わる通知などの処理と直接関係していないことにご留意ください。

```JAVA
private void fetchUniversityAndAddToDatabase() {
    Random r = new Random();
    int index = r.nextInt(sampleData.size()-1);
    try {
        // ランダムに選択されたインデックスでUniversityオブジェクトを選択
        University university = sampleData.get(index); 

        // オブジェクトとドキュメントの変換を実施するマッパー
        ObjectMapper objectMapper = new ObjectMapper(); 

        // 宣言されていないプロパティを無視する
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        HashMap<String,Object> universityMap = objectMapper.convertValue(university,HashMap.class);
        MutableDocument doc = new MutableDocument(universityMap);

        // ドキュメントをデータベースに保存する
        dbMgr.database.save(doc); 
    }
    catch ( CouchbaseLiteException | NullPointerException e) {
        e.printStackTrace();
    }
}
```

## 最後に

Couchbase LiteデータベースをAndroidアプリ内の`RecyclerViews`の埋め込みデータソースとして使用する方法の例を紹介しました。