Jetpack系列(五) — Room

Jetpack系列(五) — Room

Room简单介绍

初步印象

Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。

基本概念

@Database标记数据库,继承RoomDatabase,执行数据库创建相关的操作

@Entity标记表,将类标记为实体。这个类在数据库中将有一个映射SQLite表

@Dao标记数据操作的类

Room基本使用

实现本地存储

  1. 创建表,用@Entity标记

    • 可以使用 @Embedded注释表示要分解为表格中的子字段的对象
    // Word.kt
    @Entity(tableName = "word_table")
    data class Word(
        @PrimaryKey  @ColumnInfo(name = "word") val word: String
    )
    复制代码
  2. 创建数据库操作类,用@Dao标记

    • @Insert@Delete@Update等操作均可使用@Query代替
    • Room 查询操作可以自动返回Flow
    @Dao
    interface WordDao {
    
        @Query("SELECT * FROM word_table ORDER BY word ASC")
        fun getAlphabetizedWords(): Flow<List<Word>>
    
        // 主键冲突
        @Insert(onConflict = OnConflictStrategy.IGNORE)
        suspend fun insert(word: Word)
    
        @Query("DELETE FROM word_table")
        suspend fun deleteAll()
    }
    复制代码
  3. 创建Room Database,继承RoomDatabase, 使用@Database标记

    @Database(
        entities = [Word::class],   // 表
        version = 1,
        exportSchema = false
    )
    abstract class AppDataBase : RoomDatabase() {
    
        abstract fun wordDao(): WordDao
    
        companion object {
    
            @Volatile
            private var INSTANCE: AppDataBase? = null
    
            fun getDataBase(
                context: Context,
                scope: CoroutineScope
            ): AppDataBase {
    
                return INSTANCE ?: synchronized(this) {
                    val instance = Room.databaseBuilder(
                        context.applicationContext,
                        AppDataBase::class.java,
                        "my_database"
                    ).build()
                    INSTANCE = instance
                    instance
                }
            }
        }
    }
    复制代码
  4. Repository中引入Dao

    class WordRepository(
        private val wordDao: WordDao
    ) {
    
        val allWords: Flow<List<Word>> = wordDao.getAlphabetizedWords()
    
        @Suppress("RedundantSuspendModifier")
        @WorkerThread
        suspend fun insert(word: Word) {
            wordDao.insert(word)
        }
    }
    复制代码
  5. ViewModel当中引入Repository

    class MainViewModel(
        private val repository: WordRepository
    ) : ViewModel() {
     
        val allWords: LiveData<List<Word>> = repository.allWords.asLiveData()
    
        fun insert(word: Word) = viewModelScope.launch {
            repository.insert(word)
        }
    }
    
    // 用于实例化MainViewModel
    class MainViewModelFactory(private val repository: WordRepository) : ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
                @Suppress("UNCHECKED_CAST")
                return MainViewModel(repository) as T
            }
            throw IllegalArgumentException("Unknown ViewModel class")
        }
    
    }
    复制代码
  6. View 层修改和观察数据

    // HomeFragment.kt 观察数据
    class HomeFragment : Fragment() {
    
        private lateinit var binding: FragmentHomeBinding
        private lateinit var adapter: WordListAdapter
     
        private val mainViewModel: MainViewModel by activityViewModels {
            MainViewModelFactory((requireActivity().application as BaseApplication).repository)
        }
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View {
            binding = FragmentHomeBinding.inflate(inflater, container, false)
            return binding.root
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            initView()
            initListener()
            subscribeUI()
        }
    
        private fun initView() {
            adapter = WordListAdapter()
            binding.recyclerview.adapter = adapter
        }
    
        private fun initListener() {
            ...
        }
    
        private fun subscribeUI() {
            observe(mainViewModel.allWords, ::allWordsUpdate) 
        }
     
    
        private fun allWordsUpdate(words: List<Word>) {
            words.let {
                adapter.submitList(words)
            }
        }
    }
    
    // FlowStepFragment.kt 新增数据
    class FlowStepFragment : Fragment() {
    
        private lateinit var binding: ViewBinding  
        private val viewModel: MainViewModel by activityViewModels {
            MainViewModelFactory((requireActivity().application as BaseApplication).repository)
        }
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View {
            ...
            binding = when (flowStepNumber) {
                2 -> FragmentFlowStepTwoBinding.inflate(inflater, container, false)
                else -> FragmentFlowStepOneBinding.inflate(inflater, container, false)
            }
            return binding.root
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
    
            val mainActivity = activity as MainActivity
            mainActivity.dismissBottom()
    
            initView()
            initListener(view)
            subscribeUI()
        }
    
        private fun initView() {
    
        }
    
        private fun initListener(view: View) {
            view.findViewById<Button>(R.id.button)?.setOnClickListener(
                Navigation.createNavigateOnClickListener(R.id.next_action)
            )
            view.findViewById<Button>(R.id.button_add).setOnClickListener {
                val word = view.findViewById<EditText>(R.id.et_word).text.toString()
                if (word.isEmpty()) {
                    return@setOnClickListener
                }
                // 插入数据库
                viewModel.insert(Word(word))
                // 返回
                Navigation.findNavController(it).navigateUp()
            }
        }
    
        private fun subscribeUI() {
    
        }
    }
    复制代码
  7. Application 中间实例化database

    • WordRepository构造函数需要传入WordDao,WordDao是由AppDataBase 创建,案例当中直接在Application 创建

    • HomeFragmentFlowStepFragment 当中就可以从Application 拿到WordRepository的实例

    class BaseApplication : Application() {
        private val database by lazy { AppDataBase.getDataBase(thise) }
    
        val repository by lazy { WordRepository(database.wordDao()) }
    }
    复制代码

数据库迁移

  1. RoomDatabase 当中有addMigrations

    • 下述代码就是当手机原本是版本1新代码是版本2的时候,更新之后会创建一个表
    // 代码来自 《第一行代码》
    private val MIGRATION_1_2 = object : Migration(1, 2) {
        override fun migrate(database: SupportSQLiteDatabase) {
            database.execSQL("create table Book (id integer primary key autoincrement not null, name text not null, pages integer not null)")
        }
    }
    
    @Synchronized
    fun getDatabase(context: Context): AppDatabase {
        instance?.let {
            return it
        }
        return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database")
        .addMigrations(MIGRATION_1_2, MIGRATION_2_3)
        .build().apply {
            instance = this
        }
    }
    复制代码

相关知识点

知识点一: 查看数据文件

  • Android Studio 插件 Database Navigator
  • tab标签DB Browser

image-20210512165913551.png

知识点二: 类型转换器

  • 使用@TypeConverter注解, 可以实现在自定义类与 Room 可以保留的已知类型之间来回转换

    // 官网例子
    class Converters {
        @TypeConverter
        fun fromTimestamp(value: Long?): Date? {
            return value?.let { Date(it) }
        }
    
        @TypeConverter
        fun dateToTimestamp(date: Date?): Long? {
            return date?.time?.toLong()
        }
    }
    复制代码
  • dataBase类上加注解@Database

    @Database(entities = arrayOf(User::class), version = 1)
    @TypeConverters(Converters::class)
    abstract class AppDatabase : RoomDatabase() {
        abstract fun userDao(): UserDao
    }
    复制代码

知识点三: 预填充数据库

  • 使用 createFromAsset()createFromFile()预填充数据库

  • 结合fallbackToDestructiveMigration()增加基础数据数据

    @Database(
        entities = [Word::class],
        version = 1,
        exportSchema = false
    )
    abstract class AppDataBase : RoomDatabase() {
    
        abstract fun wordDao(): WordDao
    
        companion object {
    
            @Volatile
            private var INSTANCE: AppDataBase? = null
    
            fun getDataBase(
                context: Context,
                scope: CoroutineScope
            ): AppDataBase {
    
                return INSTANCE ?: synchronized(this) {
                    val instance = Room.databaseBuilder(
                        context.applicationContext,
                        AppDataBase::class.java,
                        "my_database"
                    )
                        .fallbackToDestructiveMigration()
                        .addCallback(WordDatabaseCallback(scope))
                        .build()
                    INSTANCE = instance
                    instance
                }
            }
    
            class WordDatabaseCallback(
                private val scope: CoroutineScope
            ) : RoomDatabase.Callback() {
                override fun onCreate(db: SupportSQLiteDatabase) {
                    super.onCreate(db)
                    INSTANCE?.let {database ->
                        scope.launch(Dispatchers.IO) {
                            populateDatabase(database.wordDao())
                        }
                    }
                }
            }
    
            private suspend fun populateDatabase(wordDao: WordDao) {
                wordDao.deleteAll()
                var word = Word("Hello")
                wordDao.insert(word)
                word = Word("World!")
                wordDao.insert(word)
            }
        }
    }
    复制代码

相关链接

Jetpack系列(一) — Navigation

Jetpack系列(二) — Lifecycle

Jetpack系列(三) — LiveData

Jetpack系列(四) — ViewModel

参考资料

官网

codelabs

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享