読者です 読者をやめる 読者になる 読者になる

vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

Gsonとか(和歌山トイレマップで遊んでみる 10)

はじめに

ちょこちょこいじっていたつもりではありましたが、ブログとしては結構間が空いていましたね。

以前Spreadsheetに入力していた値をJsonファイルとして出力できるようにしましたが、今回はこれをAndroidアプリ側で取り込んでみることにします。

github.com

Gsonを使う

Jsonファイルを出力する時と同じく、Gsonを使ってファイルからデータを読み込みます。

インストール

app/build.gradle

~省略~
dependencies {
~省略~
    compile "com.google.code.gson:gson:2.7"
~省略~
}
~省略~

何の事はない、dependenciesにgsonの項目を追加しただけですね。
簡単にできるのは素晴らしい。

データを格納するクラスを作る

Jsonファイルを作った時と同じく、Jsonから取得したデータを格納するクラスを作成します。

ToiletInfoClass.java

import com.google.gson.annotations.SerializedName;
import java.util.ArrayList;

public class ToiletInfoClass {
    @SerializedName("toiletInfo")
    private ArrayList toiletInfoList;

    public ArrayList getToiletInfoList(){
        return toiletInfoList;
    }
    public void setToiletInfoList(ArrayList newValue){
        toiletInfoList = newValue;
    }
    public int getInfoCount(){
        if(toiletInfoList == null){
            return 0;
        }
        return toiletInfoList.size();
    }
    public class ToiletInfo{
        @SerializedName("toiletName")
        public String toiletName;

        @SerializedName("district")
        public String district;

        @SerializedName("municipality")
        public String municipality;

        @SerializedName("address")
        public String address;

        @SerializedName("latitude")
        public double latitude;

        @SerializedName("longitude")
        public double longitude;

        @SerializedName("availableTime")
        public String availableTime;

        @SerializedName("hasMultiPurposeToilet")
        public boolean hasMultiPurposeToilet;
    }
}

ここで以前と異なっているのは、Jsonファイルのデータが以下のように配列で格納されている、ということです。

toiletdata.json

  "toiletInfo": [{
        "toiletName":"友ヶ島野奈浦公衆トイレ"
        ,"district":"和歌山県"
        ,"municipality":"和歌山市"
        ,"address":"和歌山市加太笘ヶ沖島2673-3"
        ,"latitude":34.282891
        ,"longitude":135.008677
        ,"availableTime":"終日"
        ,"hasMultiPurposeToilet":true
    }
    ,{
        "toiletName":"友ヶ島南垂水公衆トイレ"
        ,"district":"和歌山県"
        ,"municipality":"和歌山市"
        ,"address":"和歌山市加太苫ケ沖島2673-1"
        ,"latitude":34.28255
        ,"longitude":135.013597
        ,"availableTime":"終日"
        ,"hasMultiPurposeToilet":false
    }
    ,{
    ~省略~

そのためToiletInfoClass自体は配列(ArrayList)のみを持ち、その中身は内部クラスであるToiletInfoが入っている、という状態にしました。
なお、@SerializedNameを使うことで「@SerializedName("toiletInfo")」のようにJsonファイルの項目名を指定できます。

データの読み込み

Jsonデータの読み込み

それではJsonをロードして先ほどのクラスに値をセットします。
Jsonの置き場所は以前のCSVと同じく、app\src\main\assetsに置いています。

ToiletInfoAccesser.java

~省略~
    AssetManager assetManager = currentActivity.getResources().getAssets();
    if(assetManager == null){
        // TODO: Assetの取得に失敗した時の処理.
    }
    else{
        try {
            // Jsonの読み込み.
            InputStream inputStream = assetManager.open("toiletdata.json");
            JsonReader jsonReader = new JsonReader(new InputStreamReader(inputStream));

            // JsonデータをToiletInfoClassとして取得.
            ToiletInfoClass jsonToiletInfoClass = new Gson().fromJson(jsonReader, ToiletInfoClass.class);

            // 取得したデータをDBに挿入.
            toiletInfoModel.insertInfo(sqlite, jsonToiletInfoClass);

            jsonReader.close();
            inputStream.close();

            // DBへのデータ挿入後、データを検索してマーカー設置.
            ToiletInfoClass toiletInfoClass = toiletInfoModel.search(sqlite);
            subscriber.onNext(toiletInfoClass.getToiletInfoList());
            subscriber.onCompleted();
        } catch (IOException ex) {
            subscriber.onError(ex);
        }
    }
~省略~

かなりシンプルですね。
以前CSVを読み込んでいた時と比較するとえらい違いが...。

データの挿入

なお、DBへのデータ挿入はこのような感じに。
以前は登録済みのデータはそのまま無視していたのですが、今後データを修正する必要があった場合などを考えて上書き処理をすることとしました。

ToiletInfoModel.java

~省略~
    public void  insertInfo(SQLiteDatabase db, ToiletInfoClass toiletInfoClass) {
        Cursor cursorMaxId = db.query("toiletinfo"
                , new String[]{"MAX(id)"}
                , null
                , null
                , null, null, null);
        cursorMaxId.moveToFirst();
        int maxId = cursorMaxId.getInt(cursorMaxId.getColumnIndex("MAX(id)"));
        cursorMaxId.close();

        for(ToiletInfoClass.ToiletInfo toiletInfo: toiletInfoClass.getToiletInfoList()){
            Cursor cursorExistence = db.query("toiletinfo"
                    , new String[]{"id"}
                    , "toiletname = ? AND address = ?"
                    , new String[]{toiletInfo.toiletName, toiletInfo.address}
                    , null, null, null);
            cursorExistence.moveToFirst();

            if(cursorExistence.getCount() > 0){
                // 既存データをアップデート.
                update(db, toiletInfo, cursorExistence.getInt(cursorExistence.getColumnIndex("id")));
            }
            else{
                // 新規追加.
                maxId++;
                insert(db, toiletInfo, maxId);
            }
            cursorExistence.close();
        }
    }
~省略~
    private void insert(SQLiteDatabase db, ToiletInfoClass.ToiletInfo toiletInfo, int newId){
        // Transactionの開始.
        db.beginTransaction();

        // Transactionの開始・終了は呼び出し元で実行.
        ContentValues contentValues = new ContentValues();

        contentValues.put("id", newId);
        contentValues.put("toiletname", toiletInfo.toiletName);
        contentValues.put("district", toiletInfo.district);
        contentValues.put("municipality", toiletInfo.municipality);
        contentValues.put("address", toiletInfo.address);
        contentValues.put("latitude", toiletInfo.latitude);
        contentValues.put("longitude", toiletInfo.longitude);
        contentValues.put("availabletime", toiletInfo.availableTime);
        contentValues.put("hasMultiPurposeToilet", toiletInfo.hasMultiPurposeToilet);
        db.insert("toiletinfo", null, contentValues);

        // CommitしてTransactionを終了.

        db.setTransactionSuccessful();
        db.endTransaction();
    }
    private void update(SQLiteDatabase db, ToiletInfoClass.ToiletInfo toiletInfo, int targetId){
        // Transactionの開始.
        db.beginTransaction();

        // Transactionの開始・終了は呼び出し元で実行.
        ContentValues contentValues = new ContentValues();

        contentValues.put("id", targetId);
        contentValues.put("toiletname", toiletInfo.toiletName);
        contentValues.put("district", toiletInfo.district);
        contentValues.put("municipality", toiletInfo.municipality);
        contentValues.put("address", toiletInfo.address);
        contentValues.put("latitude", toiletInfo.latitude);
        contentValues.put("longitude", toiletInfo.longitude);
        contentValues.put("availabletime", toiletInfo.availableTime);
        contentValues.put("hasMultiPurposeToilet", toiletInfo.hasMultiPurposeToilet);

        db.update("toiletinfo", contentValues, "id = ?", new String[]{String.valueOf(targetId)});
        // CommitしてTransactionを終了.

        db.setTransactionSuccessful();
        db.endTransaction();
    }
}

以前に引き続きSQLiteを使用していますが、せっかく勉強会に参加したこともありRealmも試してみたいところ。

おわりに

コードがシンプルになるとスッキリ感も加わって気持ちが良いものですね。
ただ、取り組んでいる内容はずっとリファクタリングだけなので、そろそろ機能追加をしたいところ。

とりあえずサジェスト機能を付けるかなぁ。

今はアプリ内に入れているJsonも、外部に出してインターネット経由でデータを更新するようにもしたいのですが。

参考

Gson