Help us understand the problem. What is going on with this article?

Realmの初期データをKotlinで作る

More than 1 year has passed since last update.

プロジェクトでカタログアプリのようなものを作る機会がありました。
そこでは、商品の切り替えはあまりなく、あっても1年に1度程度、アプリのアップデートと絡めてで問題ないということでした。
そのため、Realmのデータベースを別途作成、それをiOS/Androidの両方のアプリに組み込む形としました。

単純なCSVからテーブルを作るだけであれば、Realm Studioの機能でできるのですが、ある程度正規化したりしながら作る場合、プログラムを書く必要があります。

Swiftでの初期データ作成は記事があったのですが、個人的にはAndroidの方が得意だったので、Kotlinでやってみました。

(Swiftでの記事: https://qiita.com/negibouze/items/0fe2928ea2f00d8d3cd3

完成形

https://github.com/noboru-i/android-realm-creator

Androidエミュレータを起動し、DB作成用のアプリを実行、adbコマンドを利用してローカルにコピーする形です。
(本当は、Androidを利用せずにやりたかったのですが、単独のJava用SDKなどが見当たらなかったので、この形にしました。)

コマンドとしては、下記のとおりです。

./gradlew :realmcreator:installDebug
adb shell am start -n hm.orz.chaos114.android.realmcreator/hm.orz.chaos114.android.realmcreator.MainActivity
adb shell run-as hm.orz.chaos114.android.realmcreator cat files/output.realm > default.realm

取得できた default.realm を、利用したいアプリの app/src/main/assets/ に配置し、下記のように読み込めます。

val config = RealmConfiguration.Builder()
        .name(Realm.DEFAULT_REALM_NAME)
        .assetFile("default.realm")
        .readOnly()
        .build()
realm = Realm.getInstance(config)

今回のサンプル

この前の世界バレーの結果を、下記のような形式のCSVにしたものを使います。

POOL A 日本 オランダ アルゼンチン ドイツ カメルーン メキシコ
日本 -
オランダ 3-2 -
アルゼンチン 0-3 0-3 -
ドイツ 0-3 1-3 3-0 -
カメルーン 0-3 0-3 0-3 0-3 -
メキシコ 0-3 0-3 3-0 0-3 1-3 -
POOL B 中国 イタリア トルコ ブルガリア カナダ キューバ
中国 -
イタリア 3-1 -
トルコ 0-3 0-3 -
ブルガリア 1-3 0-3 0-3 -
カナダ 0-3 0-3 0-3 1-3 -
キューバ 0-3 0-3 1-3 0-3 1-3 -

これを、「国テーブル」と「対戦結果テーブル」に分割して登録してみました。

image.png

image.png

データベース作成の実装

まず、 realmcreator/src/main/assets/ にCSVファイルを配置します。

そしてコードとしては、

  • ActivityのonCreateで Realm オブジェクトを作成
  • 古いデータを削除
  • CSVデータを読みながら登録
  • writeCopyToを利用してサイズの縮小

といった流れです。

CSVの読み込みには、 Apache Commons CSVを利用しました。

package hm.orz.chaos114.android.realmcreator

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.text.TextUtils
import hm.orz.chaos114.android.realmcreator.entity.Country
import hm.orz.chaos114.android.realmcreator.entity.Match
import io.realm.Realm
import org.apache.commons.csv.CSVFormat
import org.apache.commons.csv.CSVRecord
import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader


class MainActivity : AppCompatActivity() {

    lateinit var realm: Realm

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Realm.init(this)
        realm = Realm.getDefaultInstance()

        val outputFile = File(filesDir, "output.realm")
        deleteOldData(outputFile)

        createCountries()
        createMatch()

        // reduce size
        realm.writeCopyTo(outputFile)
    }

    private fun deleteOldData(outputFile: File) {
        realm.beginTransaction()
        realm.deleteAll()
        realm.commitTransaction()
        outputFile.delete()
    }

    private fun createCountries() {
        val reader = BufferedReader(InputStreamReader(resources.assets.open("match.csv")))
        reader.use {
            val records = CSVFormat.EXCEL.parse(reader)

            var poolName: String? = null
            realm.beginTransaction()
            records.records.forEach loop@{ record ->
                if (record.get(0).isEmpty()) {
                    poolName = null
                    return@loop
                }
                if (poolName == null) {
                    poolName = record.get(0)
                    return@loop
                }

                val obj = Country(
                        name = record.get(0),
                        pool = poolName!!
                )
                realm.copyToRealm(obj)
            }
            realm.commitTransaction()
        }
    }

    private fun createMatch() {
        val reader = BufferedReader(InputStreamReader(resources.assets.open("match.csv")))
        reader.use {
            val records = CSVFormat.EXCEL.parse(reader)

            var header: CSVRecord? = null
            realm.beginTransaction()
            records.records.forEach loop@{ record ->
                if (record.get(0).isEmpty()) {
                    header = null
                    return@loop
                }
                if (header == null) {
                    header = record
                    return@loop
                }

                val country1: Country = getCountry(record.get(0))
                record.forEachIndexed colLoop@{ index, colmn ->
                    if (index == 0) {
                        return@colLoop
                    }
                    if (TextUtils.isEmpty(colmn) || colmn.equals("-")) {
                        return@colLoop
                    }
                    val obj = Match(
                            country1 = country1,
                            country2 = getCountry(header!!.get(index)),
                            result = colmn
                    )
                    realm.copyToRealm(obj)
                }
            }
            realm.commitTransaction()
        }
    }

    private fun getCountry(name: String): Country {
        return realm.where(Country::class.java).equalTo("name", name).findFirst()!!
    }
}

createCountriescreateMatch で同じようなループをしており、処理としては無駄が大きいのですが、それぞれのテーブル作成条件が変わったりといった仕様変更に耐えやすかと思い、冗長に書いています。

Realm#writeCopyTo については、これを行うことで出力されるファイルサイズが減るようです。
今回のサンプルでは、8kByteが4kByteといった差となりました。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away