Android官方推出了一个ORM框架,并将它加入了Jetpack当中,这就是Room
。本文实现的Demo使用到了room+ViewModel+liveData+viewBinding
本文实现的效果图
Room整体架构
它主要由Entity、Dao和Database这3部分组
成,每个部分都有明确的职责,详细说明如下。
Entity
用于定义封装实际数据的实体类,每个实体类都会在数据库中有一张对应的表,并
且表中的列是根据实体类中的字段自动生成的。
Dao
Dao是数据访问对象的意思,通常会在这里对数据库的各项操作进行封装,在实际编
程的时候,逻辑层就不需要和底层数据库打交道了,直接和Dao层进行交互即可。
Database
Dao是数据访问对象的意思,通常会在这里对数据库的各项操作进行封装,在实际编
程的时候,逻辑层就不需要和底层数据库打交道了,直接和Dao层进行交互即可。
实现代码
添加依赖
本文用到的添加依赖,需要在app/bulid.gradle中添加
plugins {
id 'kotlin-kapt' //kotlin-kapt插件 room编译注解库 kapt只能在kotlin中使用
}
//ViewBinding
buildFeatures {
viewBinding true
}
dependencies {
//lifecycle
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
//侧滑删除
implementation 'com.github.mcxtzhang:SwipeDelMenuLayout:V1.3.0'
//BaseRecyclerViewAdapterHelper能够简化适配器代码
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4'
//room
implementation 'androidx.room:room-runtime:2.1.0'
kapt 'androidx.room:room-compiler:2.1.0'
}
复制代码
Book 实体类
这里在Book类名上使用@Entity注解将它声明为实体类, 并设置tab名
,然后在类中添加id字段,@PrimaryKey设置为主键,并通过autoGenerate =true设置为自增,另外还设置了name,price字段
@Entity(tableName = "tb_book")
class Book(var name:String,var price:Int) {
@PrimaryKey(autoGenerate = true)
var id:Long=0
}
复制代码
BookDao
它属于room中最关键的方法,所有访问数据库都在这里封装,就是使用注解,编写增删改查的sql语句
@Dao
interface BookDao {
/**
* 添加
*/
@Insert
fun insertBook(book: Book):Long
/**
* 根据id修改名称
*/
@Query("UPDATE tb_book SET name=:name WHERE id=:id")
fun updateBookPrice(name:String,id:Long)
/**
* 查询所有
*/
@Query("SELECT * FROM tb_book")
fun loadAllBook():LiveData<List<Book>>
/**
* 根据id删除book
*/
@Query("DELETE FROM tb_book WHERE id=:id")
fun deleteBookById(id:Long):Int
}
复制代码
AppDatabase
这部分内容的写法是非常固定的,只需要定义好
3个部分的内容:数据库的版本号、包含哪些实体类,以及提供Dao层的访问实例。
@Database(version = 1,entities = [Book::class],exportSchema = false)
abstract class AppDatabase : RoomDatabase(){
abstract fun bookDao(): BookDao
companion object{
private var instance:AppDatabase?=null
@Synchronized
fun getDatabase(context: Context):AppDatabase{
instance?.let {
return it
}
return Room.databaseBuilder(context.applicationContext,
AppDatabase::class.java,"app_db")
.build().apply {
instance=this
}
}
}
}
复制代码
Repository
object Repository {
val bookDao:BookDao=AppDatabase.getDatabase(MyApp.context).bookDao()
//查询所有
fun loadAllBook():LiveData<List<Book>>{
return bookDao.loadAllBook()
}
//修改图书价格
fun updateBookPrice(name:String,id:Long){
bookDao.updateBookPrice(name,id)
}
//添加图书
fun insertBook(book: Book):Long{
return bookDao.insertBook(book)
}
//根据id删除
fun deleteBookById(id:Long):Int{
return bookDao.deleteBookById(id)
}
}
复制代码
MainViewModel
class MainViewModel :ViewModel() {
//查询所有的book
fun getAll():LiveData<List<Book>>{
return Repository.loadAllBook()
}
}
复制代码
MainActivity
这里要注意数据库操作是属于耗时的操作,Room默认是不允许放在主线程中进行数据库操作,所以我们把对数据库增删改的操作放到了子线程中。
class MainActivity : AppCompatActivity() {
private lateinit var binding:ActivityMainBinding
private val list=ArrayList<Book>()
private lateinit var adapter: BookAdapter
private val mainViewModel by lazy {
ViewModelProvider(this).get(MainViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding= ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
//初始化适配器
adapter=BookAdapter(R.layout.item_book,list)
binding.rvBook.adapter=adapter
//viewModel+liveData监听数据变化---数据库数据一旦发生变化 这里就会收到通知
mainViewModel.getAll().observe(this, Observer {
list.clear()
Log.d(TAG, "onCreate: ${it.size}")
list.addAll(it)
adapter.notifyDataSetChanged()
})
//注册点击事件
adapter.addChildClickViewIds(R.id.btnDelete,R.id.btnUpdate)
//item子控件点击事件
adapter.setOnItemChildClickListener(object :OnItemChildClickListener{
override fun onItemChildClick(
adapter: BaseQuickAdapter<*, *>,
view: View,
position: Int
) {
if(view.id==R.id.btnDelete){//删除按钮
thread {
Repository.deleteBookById(list[position].id)
}
}else if(view.id==R.id.btnUpdate){//修改按钮
thread {
Repository.updateBookPrice("我被修改了!!",list[position].id)
}
}
}
})
//添加
binding.btnAdd.setOnClickListener {
val name = binding.edtBook.text.toString().trim()
val price = binding.edtPrice.text.toString()
if(name==""||price==""){
Toast.makeText(this, "请填写完整", Toast.LENGTH_SHORT).show()
}else{
val book=Book(name,price.toInt())
thread {
val id=Repository.insertBook(book)
Log.d(TAG, "onCreate: ${id}")
}
}
}
}
companion object {
private const val TAG = "MainActivity"
}
}
复制代码
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<LinearLayout
android:id="@+id/ll_edt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<EditText
android:id="@+id/edt_book"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_weight="1"
android:hint="输入名称"/>
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:background="#000000"/>
<EditText
android:id="@+id/edt_price"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_weight="1"
android:hint="输入价格"/>
<Button
android:id="@+id/btn_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="添加"/>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_book"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
/>
</LinearLayout>
复制代码
BookAdapter
class BookAdapter(layoutId:Int,list:ArrayList<Book>):BaseQuickAdapter<Book,BaseViewHolder>(layoutId,list){
override fun convert(holder: BaseViewHolder, item: Book) {
holder.setText(R.id.tv_name,item.name.toString())
holder.setText(R.id.tv_price,item.price.toString())
}
}
复制代码
item_book.xml
这里的item父布局我们用到了SwipeMenuLayout,这里是使用一个支持侧滑的依赖库实现,前面添加依赖的操作已作说明。
<?xml version="1.0" encoding="utf-8"?>
<com.mcxtzhang.swipemenulib.SwipeMenuLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginBottom="5dp"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="59dp">
<TextView
android:id="@+id/tv_name"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="活着"/>
<TextView
android:id="@+id/tv_price"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="12"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#d0d0d0"/>
</LinearLayout>
<Button
android:id="@+id/btnUpdate"
android:layout_width="60dp"
android:layout_height="match_parent"
android:background="#BEBEBE"
android:text="修改"
android:textColor="@android:color/white"/>
<Button
android:id="@+id/btnDelete"
android:layout_width="60dp"
android:layout_height="match_parent"
android:background="#ff0000"
android:text="删除"
android:textColor="@android:color/white"/>
</com.mcxtzhang.swipemenulib.SwipeMenuLayout>
复制代码
MyApp
这里定义了全局Context。不要忘记在AndroidManifest.xml中注册添加MyApp哟
class MyApp :Application(){
companion object{
lateinit var context: Context
}
override fun onCreate() {
super.onCreate()
context=applicationContext
}
}
复制代码
注:部分内容参考自 郭神《第一行代码Android第三版》