因为以前的一个项目用的是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群友的一些解惑,感觉谷歌文档真的坑!
后续还有数据库升级,实在不想踩坑了,先写业务了