Room是Jetpack的成员之一,是一个数据持久化的组件,作用跟SQLite
类似,但提供了更易于使用、便捷开发的编程逻辑。
本文将通过以下codelabs学习和使用Room。
本文不实际探讨关于Room的深入原理。
整体背景
使用Room的整体结构参照下图:
从图中看到,应用通过Database获取到DAO示例;通过DAO与数据库进行交互,如CRUD。可以看出,对App来说,比较重要的应该就是这个DAO。
那Entities是什么呢?可以理解成是对象。一个对象对应着数据库中的一条数据。
使用步骤
总结下Room的使用步骤:
- 抽象对象,设计对应的Entity类。一般用
data class
。
- 定义DAO类,定义数据库与App交互的接口。一般用
interface
。
- 定义Database类,继承自
RoomDatabase
,主要用于提供DAO对象。一般用abstract class
。
这就是使用Room的最小步骤了。但在项目中使用,肯定是不满足仅仅如此的,通常我们还需要一个“最佳实践”。
最佳实践
使用Repository
中间层
在实际的项目中,通常会使用一个Repository
来作为App与Database中间的桥梁。
考虑以下场景:当需要访问xxx资源时,预期是如果数据被未拉取到本地,或者是远端存在更新,那么希望从远端获取数据并保存到本地。如果本地存在数据,且已是最新,则直接返回。
很容易想到的方法是在业务代码中直接实现,不过完全可以把这部分封装在获取数据的类中。这样业务方调用就很清晰明了了。
实例
这里保存书籍信息为例,实例介绍Room的使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
|
@Entity(tableName = "book") data class Book( @PrimaryKey @ColumnInfo(name = "name") val name: String, @ColumnInfo(name = "author") val author: String, @ColumnInfo(name = "pages") val pages: Int, @ColumnInfo(name = "price") val price: Double, )
@Dao interface BookDao { @Query("SELECT * FROM book") fun getAllBooks(): Flow<List<Book>>
@Insert(onConflict = OnConflictStrategy.IGNORE) suspend fun insertBook(vararg book: Book)
@Query("DELETE FROM book") suspend fun deleteAll() }
@Database(entities = [Book::class], version = 1, exportSchema = false) abstract class BookRoomDatabase : RoomDatabase() { abstract fun bookDao(): BookDao companion object { @Volatile private var INSTANCE: BookRoomDatabase? = null fun getDatabase(context: Context, scope: CoroutineScope): BookRoomDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext, BookRoomDatabase::class.java, "book_database" ).build() INSTANCE = instance instance } } } }
class BookRepository(private val bookDao: BookDao) { val allBooks: Flow<List<Book>> = bookDao.getAllBooks()
@WorkerThread suspend fun insert(book: Book) { bookDao.insertBook(book) }
companion object { private const val TAG = "BookRepository" } }
class MyApplication: Application() { val bookDb: BookRoomDatabase by lazy { BookRoomDatabase.getDatabase(this) } val bookRepository: BookRepository by lazy { BookRepository(bookDb.bookDao()) } }
class BookViewModel(private val repository: BookRepository): ViewModel() { val allBooks: LiveData<List<Book>> = repository.allBooks.asLiveData()
fun insert(book: Book) = viewModelScope.launch { repository.insert(book) }
companion object { private const val TAG = "BookViewModel" }
class BookViewModelFactory(private val repository: BookRepository): ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(BookViewModel::class.java)) { return BookViewModel(repository) as T } throw IllegalArgumentException("Unknown view model class") }
} } class MainActivity: AppCompatActivity() { private val mBookViewModel: BookViewModel by viewModels { BookViewModelFactory((application as MyApplication).bookRepository) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mBookViewModel.allBooks.observe(this, Observer { books -> books?.let { it.forEach { book -> Log.d(TAG, "$book") } } }) } }
|