Roomのセットアップ
依存関係の追加
build.gradle.kts
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
// 以下を追加
alias(libs.plugins.ksp)
alias(libs.plugins.room)
}
kotlin {
sourceSets {
androidMain.dependencies {
// 以下を追加
implementation(libs.androidx.room.runtime.android)
implementation(libs.koin.android)
}
commonMain {
// 以下を追加
kotlin.srcDir("build/generated/ksp/metadata")
dependencies {
// 以下を追加
implementation(libs.androidx.room.runtime)
implementation(libs.sqlite.bundled)
api(libs.koin.core)
implementation(libs.koin.compose)
implementation(libs.koin.compose.viewmodel)
}
}
}
}
dependencies {
// 以下を追加
// Android
add("kspAndroid", libs.androidx.room.compiler)
// iOS
add("kspIosSimulatorArm64", libs.androidx.room.compiler)
add("kspIosX64", libs.androidx.room.compiler)
add("kspIosArm64", libs.androidx.room.compiler)
debugImplementation(compose.uiTooling)
}
// 以下を追加
room {
schemaDirectory("$projectDir/schemas")
}
テーブル・DAOの作成
TaskEntity.kt
package com.app.app.data.local.entity
import androidx.room.Entity
import androidx.room.PrimaryKey
/**
* タスクテーブルを表すEntityクラス
*/
@Entity(tableName = "task")
data class TaskEntity(
@PrimaryKey(autoGenerate = true) val id: Long?,
val title: String,
)
TaskDao.kt
package com.app.app.data.local.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import com.app.app.data.local.entity.TaskEntity
import kotlinx.coroutines.flow.Flow
/**
* DBへのアクセスを提供するインターフェース
* Roomが実装し、ローカルのSQLiteとやり取りを行う
*/
@Dao
interface TaskDao {
@Insert
suspend fun insert(task: TaskEntity)
@Query("SELECT count(*) FROM task")
suspend fun count(): Int
@Query("SELECT * FROM task")
fun selectAll(): Flow<List<TaskEntity>>
}
DBの作成・取得
AppDatabase.kt
package com.app.app.data.local
import androidx.room.*
import com.app.app.data.local.dao.TaskDao
import com.app.app.data.local.entity.TaskEntity
/**
* DBのファイル名を定義
*/
internal const val DB_FILE_NAME = "app.db"
/**
* データベースを表すクラス
*/
@Database(entities = [TaskEntity::class], version = 1)
@ConstructedBy(AppDatabasebaseConstructor::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun getTaskDao(): TaskDao
}
/**
* データベースのインスタンスを取得する
*/
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
expect object AppDatabasebaseConstructor : RoomDatabaseConstructor<AppDatabase> {
override fun initialize(): AppDatabase
}
DIモジュールの定義
Koin.kt
package com.app.app.di
import org.koin.core.context.startKoin
import org.koin.core.module.Module
import org.koin.dsl.KoinAppDeclaration
/**
* 各プラットフォームで異なるモジュールを提供するための関数
*/
expect fun platformModule(): Module
/**
* DIフレームワークの初期化を行う
*/
fun initKoin(appDeclaration: KoinAppDeclaration = {}) =
startKoin {
appDeclaration()
modules(
platformModule(),
)
}
各プラットフォームごとのDB作成・取得
Android
AppDatabase.android.kt
package com.app.app.data.local
import android.content.Context
import androidx.room.Room
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
/**
* Android向けのデータベースインスタンスを取得する関数
*/
fun getDatabase(context: Context): AppDatabase {
val dbFile = context.getDatabasePath(DB_FILE_NAME)
return Room.databaseBuilder<AppDatabase>(
context = context.applicationContext,
name = dbFile.absolutePath,
)
.setDriver(BundledSQLiteDriver())
.fallbackToDestructiveMigration(true)
.build()
}
iOS
AppDatabase.ios.kt
package com.app.app.data.local
import androidx.room.Room
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import platform.Foundation.*
/**
* iOS向けのデータベースインスタンスを取得する関数
*/
@OptIn(ExperimentalForeignApi::class)
fun getDatabase(): AppDatabase {
val documentDirectory =
NSFileManager.defaultManager.URLForDirectory(
directory = NSDocumentDirectory,
inDomain = NSUserDomainMask,
appropriateForURL = null,
create = false,
error = null,
)
val dbFilePath = "${requireNotNull(documentDirectory?.path)}/$DB_FILE_NAME"
return Room.databaseBuilder<AppDatabase>(name = dbFilePath)
.setDriver(BundledSQLiteDriver())
.setQueryCoroutineContext(Dispatchers.IO)
.build()
}
各プラットフォームごとのDI
Android
Koin.android.kt
package com.app.app.di
import com.app.app.data.local.AppDatabase
import com.app.app.data.local.getDatabase
import org.koin.core.module.Module
import org.koin.dsl.module
/**
* Android用のプラットフォームモジュール
*/
actual fun platformModule(): Module =
module {
single<AppDatabase> { getDatabase((get())) }
}
MainActivity.kt
package com.app.app
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import com.app.app.di.initKoin
import org.koin.android.ext.koin.androidContext
/**
* Androidアプリのエントリーポイント関数
*/
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Koinの初期化
initKoin(appDeclaration = { androidContext(this@MainActivity) })
setContent {
App()
}
}
}
iOS
Koin.ios.kt
package com.app.app.di
import com.app.app.data.local.AppDatabase
import com.app.app.data.local.getDatabase
import org.koin.core.module.Module
import org.koin.dsl.module
/**
* iOS用のプラットフォームモジュール
*/
actual fun platformModule(): Module =
module {
singleDatabase> { getDatabase() }
}
MainViewController.kt
package com.app.app
import androidx.compose.ui.window.ComposeUIViewController
import com.app.app.di.initKoin
import platform.UIKit.UIViewController
/**
* iOSアプリのエントリーポイント関数
*/
fun mainViewController(): UIViewController {
// Koinの初期化
initKoin()
return ComposeUIViewController { App() }
}