room 数据库 使用详解以及遇到的一些问题

因为以前的一个项目用的是sqllite,所以最近在重构,本来准备用greendao 但是green不支持kotlin的data class 所以考虑用了官方的room架构

谷歌文档
[使用 Room 将数据保存到本地数据库 | Android 开发者 | Android Developers (google.cn)
]
首先添加依赖

    def room_version = "2.3.0"


    implementation "androidx.room:room-ktx:$room_version"
    testImplementation "androidx.room:room-testing:$room_version"
    kapt "androidx.room:room-compiler:$room_version"
    // To use Kotlin Symbolic Processing (KSP)
    implementation "androidx.room:room-runtime:$room_version"

然后定义数据类

@Entity
    data class User(
    @PrimaryKey val uid: Int,
    @ColumnInfo(name = "first_name") val firstName: String?,
    @ColumnInfo(name = "last_name") val lastName: String?
    )

定义dao 里面是增删改查

@Dao
    interface UserDao {
        @Query("SELECT * FROM user")
        fun getAll(): List

        @Query("SELECT * FROM user WHERE uid IN (:userIds)")
        fun loadAllByIds(userIds: IntArray): List

        @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
               "last_name LIKE :last LIMIT 1")
        fun findByName(first: String, last: String): User

        @Insert
        fun insertAll( users: List)

        @Delete
        fun delete(user: User)
    }

定义database

/**
 * entities 每个@entity 都要在这边添加
 *  version 数据库版本
 */
@Database(entities = arrayOf(User::class), version = 1)
    abstract class AppDatabase : RoomDatabase() {
        abstract fun userDao(): UserDao
    }

activity

   val db = Room.databaseBuilder(
            applicationContext,
            AppDatabase::class.java, "database-name"
        ).build()

     val user1 = User(1, firstName = "hou", lastName = "zhanlei")
            val user2 = User(2, firstName = "hou1", lastName = "zhanlei2")
            val list = arrayListOf(user1,user2)
            object : Thread() {
                override fun run() {
                    db.userDao().insertAll(list)
                }
            }.start()

这样基本的就可以了,调用数据库之前记得请求权限,但是你以为这样就可以在项目里使用了? to yong to simple,bug才刚刚开始

下面只罗列我遇到的问题
问题1 本地建表默认字段是不能为null ,也就是你数据类里的字段,如果你数据类某个字段为null就会报错,两个解决办法:
1 可以在字段上添加注解 @Nullable
2 字段上可以为null val firstName: String?
3 自定义数据解析器 把后台返回的null转为""

package com.mot.st.network

import com.google.gson.Gson
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import java.io.IOException
import java.lang.reflect.Type


/**
 * 自定义TypeAdapter ,null对象将被解析成空字符串
 */
class NullStringToEmptyAdapterFactory : TypeAdapterFactory {
    override fun  create(gson: Gson?, type: TypeToken): TypeAdapter? {
        val rawType = type.getRawType() as Class
        return if (rawType != String::class.java) {
            null
        } else StringNullAdapter() as TypeAdapter
    }
}

 class StringNullAdapter : TypeAdapter() {
    @Throws(IOException::class)
    override fun read(reader: JsonReader): String? {
        if (reader.peek() == JsonToken.NULL) {
            reader.nextNull()
            return ""
        }
        return reader.nextString()
    }

    @Throws(IOException::class)
    override fun write(writer: JsonWriter, value: String?) {
        if (value == null) {
            writer.nullValue()
            return
        }
        writer.value(value)
    }
}

//class NullListToEmptyAdapterFactory : TypeAdapterFactory {
//    override fun  create(gson: Gson?, type: TypeToken): TypeAdapter? {
//        val rawType = type.getRawType() as Class
//        return if (rawType != List::class.java) {
//            null
//        } else ListNullAdapter() as TypeAdapter
//    }
//}
//
//class ListNullAdapter : TypeAdapter?>() {
//    @Throws(IOException::class)
//    override fun read(reader: JsonReader): List? {
//        if (reader.peek() == JsonToken.NULL) {
//            reader.nextNull()
//            return emptyList()
//        }
//    }
//
//    @Throws(IOException::class)
//    override fun write(writer: JsonWriter, value: List?) {
//        if (value == null) {
//            writer.nullValue()
//            return
//        }
//        writer.value("")
//    }
//}

然后在retofitbuild里面添加自定义gson解析器

        val gson =GsonBuilder()
            .serializeNulls()
            .registerTypeAdapterFactory(NullStringToEmptyAdapterFactory()).create()

        retrofit = Retrofit.Builder()
            .client(getOkHttpClient())
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create(gson))
            .build()

第二个问题 数据类里包含list
例如

    val selectableStockList: List?= emptyList(),

解决办法定义 TypeConverters 解决办法如下

//room conventer
class SkuConverters {
    @TypeConverter
    fun stringToArticles(json: String): List {
        if (json.isBlank()) {
            return emptyList()
        }
        val type = object : TypeToken>() {}.type
        return GsonUtils.fromJson(json, type)
    }

    //注意加上? 可空判断 后台空数组会会返回null
    @TypeConverter
    fun articlesToString(data: List?): String {
        if (data.isNullOrEmpty())
            return ""
        else
            return GsonUtils.toJson(data)
    }

}

这里注意那个注释 因为你不加?
在一个注释文件里 会报kotlin check param not null

        _tmp_1 = __selectableStockListConverters.articlesToString(value.getSelectableStockList());

@Entity
@TypeConverters(
    SkuConverters::class,
    SelectableStockListConverters::class,
    SkuPackageDetailConverters::class
)
data class StockDocDetailResponseData(

    val selectableStockList: List?= emptyList(),
    val skuList: List?= emptyList(),

data class StockDocSku(
 
    val skuPackageDetailList: List?= emptyList(),

)

)

所有的TypeConverters都要在@Entity这里加

到此我的bug改的差不多了 下面再上一个单例的数据库单例 是mvvmmlin git开源库里的一个东西

@Database(entities = [StockDocDetailResponseData::class, StDcDataCheck::class], version = 1)
abstract class MyDatabase : RoomDatabase() {

    abstract fun stockInDataDao(): StockInDataDao
    abstract fun StockInInventoryDao(): StockInInventoryDao


    companion object {
        fun getInstance() = SingletonHolder.INSTANCE
    }

    private object SingletonHolder {
        val INSTANCE = Room.databaseBuilder(
            Utils.getApp(),
            MyDatabase::class.java,
            "com.mos.st.db"
        )
            //   .addMigrations(MIGRATION.MIGRATION_1_2)
            .build()
    }
}

object MIGRATION {
    val MIGRATION_1_2 = object : Migration(1, 2) {
        override fun migrate(database: SupportSQLiteDatabase) {
            // 没有更改表结构,空实现
            database.execSQL("create table banner(id INTEGER NOT NULL primary key,`desc` TEXT NOT NULL,imagePath TEXT NOT NULL,isVisible INTEGER NOT NULL,`order` INTEGER NOT NULL, title TEXT NOT NULL,type INTEGER NOT NULL,url TEXT NOT NULL)")
        }
    }
}

再此感觉Android 是开源的 能百度到大量知识 谢谢mmvmlin的优秀开源架构[项目地址:MVVMLin],也感谢MVVMHabit-Family群友的一些解惑,感觉谷歌文档真的坑!

后续还有数据库升级,实在不想踩坑了,先写业务了

你可能感兴趣的:(room 数据库 使用详解以及遇到的一些问题)