この記事はKotlin Advent Calendar 2020 の22日目の記事です。
Kotlin Advent Calendarに参加するのは今年で6年連続6回目になりました🎉🐦
毎年22日近辺を書いています。マイルストーンの時から書いている記事もあるので情報が古くなっているものもありますが、
過去にはこんな記事を書いていました。
2018年 22日: Kotlin Multiplatform環境でKotlin SerializationとAndroid ExtensionsのParcelize Annotationを使う
2019年 20日(代打): オレの考えた最強のKotlin Multiplatform Projectアーキテクチャ2020
2020年はMPP(KMP)にも大きな進展がありました!
Kotlin1.4がリリースされたり、
当初はKotlin Multiplatform Projectとして、Android, Native(iOS, Linux, wasm…etc), WEB(JS), Server全てを一気に対応する方針で進んできましたが、一旦は一番需要があるMobileに振り切って、
Kotlin Multiplatform Mobile
(通称KMM)という名前でalphaリリースが行われました🎉
もちろんJS等の対応をやめたということではなく、Mobileの枠を区切って力を入れることでMPPの利用者を増やすことを目的としているんだと思います(勝手な推測)
引き続きKotlin JS等には期待しています。
最近の私ですが、Kotlin Multiplatform Project(MPP)でガッツリ大規模サービスを開発していきたい気持ちもありつつ、
現所属の会社は少人数(フロント1or2人)で素早くプロトタイプを開発していくフェーズで
今年は主にFlutterで開発する機会が多くなってしまいました。
とはいえKotlinへの愛は絶えていないのでキャッチアップは細々と続けています。
FlutterはFlutterで結構楽しいです🎯
今回はMPPのガッツリネタというよりは、2020年12月15日にDeveloper PreveiwがリリースされたばかりのRealm Kotlinを試してみたので、使ってみた系の記事を書きたいと思います。
ドキュメントはまだDeveloper PreviewでAPI Designを定義している状態なので、今後APIが変更になる可能性が高いのはご注意ください。
Realm Kotlin
Realm’s Kotlin Native SDK is here! Now you can use @realm and tap into the full @kotlin ecosystem. Check out the repo here: https://t.co/Sr4iE95V26 #androiddev #kotlin #android pic.twitter.com/AbXflfWP2C
— Realm (@realm) December 15, 2020
Realm Kotlinのリポジトリはこちらです。
https://github.com/realm/realm-kotlin
API Design OverviewがGoogle Docsで用意されています。 提案モードが用意されているので今ならAPI設計に物申せます!
https://docs.google.com/document/d/1RSPNO95wZAAojYlFwshSpLiuEu9ZqXptO58RDoPHKNc/edit
こっちはモノレポにするかとかいろいろなメリット・デメリットとかも載ってるので、 それを見るのも面白いのでぜひ見てみてください。
https://docs.google.com/document/d/10adRFquingm_JgyjDhUzcYXIDJsDG2A1ldFw53GSVJQ/edit
使い方
Project作成
まずはIntelliJ(Andorid Studio)からプロジェクトを作成します。
KMMのリリースと同時にプラグインが追加されているので、追加しましょう。
これを使ってプロジェクトを作成すると、簡単にKMMのプロジェクトが設定できます。
Gradle設定
/build.gradle.kts
buildscript {
repositories {
gradlePluginPortal()
jcenter()
google()
mavenCentral()
maven(url = "http://oss.jfrog.org/artifactory/oss-snapshot-local") // 追加
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.21")
classpath("com.android.tools.build:gradle:4.1.0")
classpath("io.realm.kotlin:plugin-gradle:0.0.1-SNAPSHOT") // 追加
}
}
allprojects {
repositories {
google()
jcenter()
mavenCentral()
maven(url = "http://oss.jfrog.org/artifactory/oss-snapshot-local") // 追加
}
}
/shared/build.gradle.kts
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
kotlin("multiplatform")
id("com.android.library")
id("realm-kotlin") // 追加
}
kotlin {
android()
iosX64("ios") { // A1
binaries {
framework {
baseName = "shared"
}
}
}
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.realm.kotlin:library:0.0.1-SNAPSHOT") // B 追加
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val androidMain by getting {
dependencies {
implementation("com.google.android.material:material:1.2.1")
}
}
val androidTest by getting {
dependencies {
implementation(kotlin("test-junit"))
implementation("junit:junit:4.13")
}
}
val iosMain by getting
val iosTest by getting
}
}
android {
compileSdkVersion(29)
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdkVersion(24)
targetSdkVersion(29)
}
}
val packForXcode by tasks.creating(Sync::class) {
group = "build"
val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
val framework =
kotlin.targets.getByName<KotlinNativeTarget>("ios").binaries.getFramework(mode) // A2
inputs.property("mode", mode)
dependsOn(framework.linkTask)
val targetDir = File(buildDir, "xcode-frameworks")
from({ framework.outputDirectory })
into(targetDir)
}
tasks.getByName("build").dependsOn(packForXcode)
コメントしている箇所はデフォルトのKMMプロジェクトから変更している箇所になります。
- A1, A2
Realm Kotlinはまだbetaバージョンです。
そのため2020/12/21現在はまだ x86_64
のアーティファクトしか配布されていません。
なのでiOS側はarmを除く必要があります。
- B
SharedモジュールにRealm Kotlinの依存を追加します。 Kotlin1.4まではAndroid, iOS各プラットフォーム毎に依存を自分で追加しないと動かなかったのですが、 1.4からはcommonMain 1箇所のみの定義で良くなったのでとてもスッキリしました!
Shared Code
DBに使うモデルには、
RealmModelを継承したクラスに@RealmObject
annotationをつけます。
ここはdata classにしたかったのですが、普通のclassでないとコンパイルできませんでした🥺
@RealmObject
class Person : RealmModel {
var name: String = "hoge"
var age: Int = 46
}
次にSchema用にRealmModule
annotationをつけたクラスを用意します。
サンプルコードではannotationの引数にRealmModelを指定していますが、なくても動作はしました🤔
@RealmModule(Person::class)
class Entities
準備はこれだけで、あとはShared Module側で初期化処理をします。
とても簡単で、Realm.openにRalm configurationを渡すだけです
Realm configurationには他に名前やパスの設定ができます。
private val realm: Realm by lazy {
val configuration = RealmConfiguration.Builder()
.schema(Entities())
.build()
Realm.open(configuration)
}
insert
fun addPerson(name: String, age: Int): Person {
realm.beginTransaction()
val person = realm.create(Person::class).apply {
this.name = name
this.age = age
}
realm.commitTransaction()
return person
}
AndroidのRealmとは違って、KMMのRealmはclose処理がありません。
select
fun persons(): List<Person> {
return realm.objects(Person::class)
}
fun queryPerson(name: String): List<Person> {
return realm.objects(Person::class).query("name = $0", name)
}
delete
fun deletePerson(person: Person) {
realm.beginTransaction()
Realm.delete(person)
realm.commitTransaction()
}
fun deletePersons() {
realm.beginTransaction()
realm.objects(Person::class).delete()
realm.commitTransaction()
}
特定の行を削除する時だけRealm
オブジェクトからおこないます。
Platform Code
Shared moduleのRealmインスタンスはObject classで定義するなり、DIするなりしてSingletonにすると良いと思います。
Shared moduleにSingletonで定義されているとして、
あとはプラットフォーム側で
Database.addPerson("abc", 20)
val person = Database.queryPerson("abc")
Log.v("android", person.toString())
みたいにするだけです。 プラットフォーム側のコードはとてもシンプルに書けますね。
現状提供されている機能はこれだけです。
Coroutines support
DBからの値をリアクティブに返却する機能はもはや必須と言っても過言ではないでしょう。
KMMなのでKotlin CoroutinesのFlowで返して貰えると嬉しいです。
もちろんRealmも考えているみたいですが、まだ使うことはできません。
Docsがあるので今後実装されることを期待しましょう。
https://docs.google.com/document/d/1H9CVA928omjKIB19MqYUj7Pysy3Lb0a6WQcv9IAGPwg/edit
まとめ
先日リリースされたばかりのRealm Kotlinを早速使ってみました。
まだまだDeveler Previewの段階で基本的な機能(insert, select, delete)ぐらいしか実装されていませんが、SQLiteを使うまでもない場合には選択肢の1つとしてあると嬉しいですね。
今後の開発に期待しましょう!!
Have a nice Kotlin!
明日は優秀な同僚@oboenikui さんのmockkの話です。