Android Jetpackアーキテクチャコンポーネント(七)Room使用編


前言の前のいくつかの編はLifecycle、LiveData、ViewModelを説明して、前のいくつかの編の敷居があって、私達が今日説明するRoomを引き出すことができて、Roomは1つのデータベースのアクセスコンポーネントで、SqLiteデータベースに対して友好的なパッケージをして、私達にコードする時、論理の部分を重視するだけでいいことができて、データベースはRoomに渡して流暢なアクセスに行きます.
Room使用手順>Githubプロジェクトアドレス
  • 1依存
    build.gradle {
    apply plugin: 'kotlin-kapt'
    
    dependencies {
    	kapt "androidx.room:room-compiler:$rootProject.roomVersion"
    	implementation "androidx.room:room-runtime:$rootProject.roomVersion"
    	}
    }
    
  • を追加
  • 2 Entityエンティティークラス
    @Entity(tableName = "apps")
    data class AppEntity(
            @ColumnInfo(name = "packageName") @PrimaryKey val packageName: String,
            @ColumnInfo(name = "app_id") val id: Int,
            @ColumnInfo(name = "versionCode") val versionCode: String,
            @ColumnInfo(name = "versionLabel") val versionLabel: String,
            @ColumnInfo(name = "versionName") val versionName: String,
            @ColumnInfo(name = "description") val description: String,
            @ColumnInfo(name = "icon") val icon: String)
     
     
     @Entity(tableName = "comments",
            foreignKeys = [
                ForeignKey(entity = AppEntity::class,
                        parentColumns = ["packageName"],
                        childColumns = ["packageName"],
                        onDelete = ForeignKey.CASCADE)
            ],
        	indices = [Index("packageName")])
    class CommentEntity(@PrimaryKey(autoGenerate = true) val id: Int = 0,
                    val packageName: String,
                    val comment: String = "this is comment for $packageName")
    
    を作成
  • エンティティクラス注釈Entityを使用してマークします.多くのプロパティがあります.
  • 1 tableName:データベース内のテーブル名を設定します.この値を設定しない場合、デフォルトはクラスの名前です.
  • 2 indices:インデックスを設定します.インデックスは、データベース・テーブルのデータ・アクセス速度を向上させるために使用されます.単一のカラム・インデックスと組合せインデックスがあります.
  • 3 inheritSuperIndices:親のインデックスが現在のクラスに自動的に継承されるかどうか.
  • 4 primaryKeys:プライマリ・キーを設定します.このプライマリ・キーの値がこのオブジェクトを一意に決定できる場合は、プライマリ・キーを1つだけ設定できます.フィールド値がオブジェクトを一意に決定できない場合は、primaryKeysで配列を設定できる複合プライマリ・キーが必要です.さらに、各Entityはプライマリ・キーを設定する必要があります.親と子がプライマリ・キーを設定している場合、子のプライマリ・キーは親のプライマリ・キーを上書きします.
  • 5 foreignKeys:外部キー、すなわちFOREGN KEYコンストレイントを設定します.Sqliteデータベースはリレーショナル・データベースに属するため、テーブル間にリレーショナルが存在するため、このプロパティ値は2つのフォーム間の関係を関連付けるために使用されます.以上のようにCommentEntityには、AppEntityのpackageNameという外部キーが設定されています.
  • 6 ignoredColumns:無視されたフィールド.

  • クラスでは、ColumnInfo注記も使用されます.属性値nameは、データベースに格納されているテーブル内のフィールドの値をマークするために使用されます.設定しない場合は、宣言されたフィールドの値がデフォルトです.
  • には、さらにEmbeddedがあり、ネストされたオブジェクトを表しています.クラスAを別のクラスBに入れることができ、BでAに注釈Embeddedを使用するだけで、BはAのすべての属性値を正常に使用することができます.

  • 3宣言Daoオブジェクト
    @Dao
    interface AppsDao {
    
        @Query("SELECT * FROM apps")
        fun loadApps(): LiveData<List<AppEntity>>
    
        @Query("SELECT * FROM apps WHERE packageName = :packageName")
        fun loadApp(packageName: String): LiveData<AppEntity>
    
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        fun insertAll(apps: List<AppEntity>)
    
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        fun insert(app: AppEntity)
    
        @Delete
        fun delete(app: AppEntity)
    
        @Update
        fun update(app: AppEntity)
    }
    
  • というDaoオブジェクトの宣言はinterfaceで修飾しなければならない.また、4種類の添削添削を提供した注釈を見ると、クエリの注釈だけは少量のSQL文を入力する必要があり、インタフェースの戻り値を定義することはLiveDataなどの観察可能なデータでもあり、操作が非常に便利である.
  • コードを同期すると、generatedxxx_Impl.javaオブジェクトが生成され、宣言したインタフェースメソッドが実装され、自分で処理する必要はありません.

  • 4宣言Databaseオブジェクト
    @Database(entities = [AppEntity::class, CommentEntity::class], version = 1, exportSchema = false)
    abstract class AppDatabase : RoomDatabase() {
    
        abstract fun appsDao(): AppsDao
    
        abstract fun commentsDao(): CommentsDao
    
        companion object {
            private const val DATABASE_NAME = "forward-db"
            private val executors: ExecutorService = Executors.newSingleThreadExecutor()
            @Volatile
            private var instance: AppDatabase? = null
    
            fun getInstance(context: Context): AppDatabase {
                return instance ?: synchronized(this) {
                    instance ?: buildDatabase(context.applicationContext).also {
                        instance = it
                    }
                }
            }
    
            private fun buildDatabase(context: Context): AppDatabase {
                return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
                        .addCallback(object : Callback() {
                            override fun onCreate(db: SupportSQLiteDatabase) {
                                executors.execute {
                                    Thread.sleep(3000)
                                    val request: OneTimeWorkRequest = OneTimeWorkRequestBuilder<AppsWorker>().build()
                                    WorkManager.getInstance(context).enqueue(request)
                                }
                            }
                        })
                        .build()
            }
        }
    }
    
  • は、Database注釈を使用して、私たちが宣言したすべてのEntityオブジェクト、バージョン番号version、およびSchemaなどの属性値をエクスポートするかどうかを注釈します.
  • このクラスはRoomDatabaseを継承し、一般的にこのクラスは単例の形式で使用される.さらに、 を使用してオブジェクトを作成し、データベースに格納されたonCreateメソッドのうち、WorkManagerを使用して、以下に示すように、データの取得をどこかに配置することができます.

  • 5取得データ
    class AppsWorker(context: Context, workerParameters: WorkerParameters)
        : CoroutineWorker(context, workerParameters) {
    
        private val TAG by lazy {
            AppsWorker::class.java.simpleName
        }
    
        override suspend fun doWork(): Result = coroutineScope {
            try {
                applicationContext.assets.open("apps.json").use {
                    JsonReader(it.reader()).use { reader ->
                        val appsType = object : TypeToken<List<AppEntity>>() {}.type
                        val appsList: List<AppEntity> = Gson().fromJson(reader, appsType)
                        val comments = DataGenerator.getComments(appsList)
                        val appsDao = RepositoryProvider.providerAppsRepository(applicationContext)
                        val commentDao = RepositoryProvider.providerCommentsRepository(applicationContext)
                        appsDao.insertAll(appsList)
                        commentDao.insertAll(comments)
                    }
                    Result.success()
                }
            } catch (e: Exception) {
                Result.failure()
            }
        }
    
        private fun insertData(database: AppDatabase, apps: List<AppEntity>, comments: List<CommentEntity>) {
            database.runInTransaction {
                database.appsDao().insertAll(apps)
                database.commentsDao().insertAll(comments)
            }
        }
    }
    
  • WorkManagerの使用はこのセクションの重点ではありません.使用は簡単ですが、ソース分析は複雑です.後で単独で説明します.

  • 6最終使用
       viewModel.apps.observe(viewLifecycleOwner, Observer {
            if (it.isNullOrEmpty()) {
                binding.loading = true
            } else {
                binding.loading = false
                adapter.setList(it)
            }
            binding.executePendingBindings()
        })
    
  • 上記のコードを呼び出すと、私たちのデータとライフサイクルが結合され、データが変化すると、UIを更新するコードがすぐにコールバックされ、私たちの目的を達成します.
  • ここではViewModelのコードを省略しています.ここでViewModelのコードを貼ると、FactoryRepositoryに関するコードがたくさん貼られなければなりません.具体的なソースコードはGithubプロジェクトのアドレスに行って見ることができます.