プロジェクトでカタログアプリのようなものを作る機会がありました。
そこでは、商品の切り替えはあまりなく、あっても1年に1度程度、アプリのアップデートと絡めてで問題ないということでした。
そのため、Realmのデータベースを別途作成、それをiOS/Androidの両方のアプリに組み込む形としました。
単純なCSVからテーブルを作るだけであれば、Realm Studioの機能でできるのですが、ある程度正規化したりしながら作る場合、プログラムを書く必要があります。
Swiftでの初期データ作成は記事があったのですが、個人的にはAndroidの方が得意だったので、Kotlinでやってみました。
(Swiftでの記事: https://qiita.com/negibouze/items/0fe2928ea2f00d8d3cd3 )
完成形
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 | - |
これを、「国テーブル」と「対戦結果テーブル」に分割して登録してみました。
データベース作成の実装
まず、 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()!!
}
}
createCountries
と createMatch
で同じようなループをしており、処理としては無駄が大きいのですが、それぞれのテーブル作成条件が変わったりといった仕様変更に耐えやすかと思い、冗長に書いています。
Realm#writeCopyTo
については、これを行うことで出力されるファイルサイズが減るようです。
今回のサンプルでは、8kByteが4kByteといった差となりました。