jiichan.com

PROGRAMMING

Android Kotlin
javascript
PHP
Java

Room を使うための Application Database Dao Repository ViewModel

アプリを作っているとデータベースの利用は必須だと思います。
いつも Room で使用する色んなクラスの依存関係をメモっておくことにしました。

≪開発環境≫
windows11
andriod studio Ladybug

インスタンス生成の流れ

Room を使うための各クラスはそれぞれ依存関係にあるため、依存されているクラスを先にインスタンスする。

各ファイルインスタンスの場所インスタンスの順序
ApplicationAndroidManifest1
DatabaseApplication2
DaoApplication3
RepositoryApplication4
ViewModelActivity5

AndroidManifest

AndroidManifest の name に Application を登録し自動的にインスタンスされるようにする。


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
        
    <application
        android:name=".SampleApplication"
        android:allowBackup="true"
            :
            :

Application

Application 内で Database → Dao → Repository の順にインスタンスする。


// AndroidManifest.xml に登録必要
class SampleApplication : Application() {
    // データベースのインスタンスを作成 Database が全てのクラスから使えるようになる
    val database by lazy { SampleDatabase.getInstance(this) }
    // Daoをインスタンス化してリポジトリのインスタンス作成
    val repository by lazy { SampleRepository(database.sampleDao()) }
}

Database

Database クラスは abstract(抽象クラス)であり、Room が実装を自動的に行う


@Database(
    version = 1,
    entities = [Users::class, Groups::class],
    exportSchema = false,
//    autoMigrations = [
//        AutoMigration (from = 1, to = 2)
//    ]
)
abstract class SampleDatabase : RoomDatabase() {
    // この関数を実行することで Room が Dao を使えるように実装してくれる
    abstract fun SampleDao(): SampleDao

    /**
     * companionオブジェクトはクラスのインスタンス化無しで、
     * データベースを作成または取得するためのメソッドへのアクセスを可能にする。
     * このクラスの唯一の目的はデータベースを提供することだけなので、これをインスタンス化する理由はない。 */
    companion object {
        /**
         * データベースが作成された時点でINSTANCE変数にはデータベースの参照を持たせる。
         * これにより、繰り返し負荷のかかるデータベースへの接続を開く必要がなくなる。 */
        @Volatile
        private var INSTANCE: SampleDatabase? = null

        fun getInstance(context: Context): SampleDatabase {
            // synchronizedは一度に1つの実行スレッドだけがこのコードブロックに入ることができる
            // データベースが 1 回だけ初期化されるようにする
            synchronized(this) {
                var instance = INSTANCE
                // データベースが存在するときは、存在するデータベースを返し、存在しない場合は、データベースを作成する
                if (instance == null) {
                    instance = Room.databaseBuilder(
                        context.applicationContext,
                        SampleDatabase::class.java,
                        "Sample_database"     // database file name
                    )
                        .fallbackToDestructiveMigration()
                        .build()
                    INSTANCE = instance
                }
                return instance
            }
        }
    }
}

Dao

Dao のインスタンスは Application 内で Repository インスタンス時に済んでいる。


@Dao
interface SampleDao {
        :
        :
}

Repository

リポジトリは Dao のインスタンスが必要だが、どちらも Application 内で作成済み。


class SampleRepository(private val dao: SampleDao) {
        :
        :
}

ViewModel

ViewModel では companion object で viewModelFactory を定義し Activity でインスタンスできるようにする。


class MyViewModel(private val repo: SampleRepository) : ViewModel() {
        :
        :
     /** 引数付きの ViewModel を生成するための Factory
     * Activity にてこれを使用し Viewmodel のインスタンスを作成 ================================ */
    companion object {
        val factory = viewModelFactory {
            initializer {
                // [APPLICATION_KEY] は ViewModel から Application クラスのインスタンスを参照できる
                val application = (this[APPLICATION_KEY] as SampleApplication)
                // 引数をセット
                MyViewModel(application.repository)
            }
        }
    }   
}

Activity

ViewModel 内の viewModelFactory を使用し Activity でインスタンスする。


class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            SampleAppTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    // 引数あり(room使用のため)の ViewModel を準備
                    val owner = LocalViewModelStoreOwner.current
                    owner?.let {
                        val viewModel: MyViewModel = viewModel(factory = MyViewModel.factory)
                        MainContent(viewModel)
                    }
                }
            }
        }
    }
}

@Composable
fun MainContent(
    viewModel: MyViewModel
) {
        :
        :
}

まとめ

Room を使うための準備は gradle の設定や沢山のクラス作成など面倒ですが パターンをメモっておけば楽に進めれると実感しました。