Keyword:SQLDelight,Driver
到23日,引入SQLDelight,到在Android上呈现DB资料
KMMDay23
在各平台上的SQLDelight的实作方法不同,因此也需要不同的Driver,好在这个Driver我们不用自己写,在一开始引入SQLDelight时的Gradle,有几行就是分别写在各个平台专用的区块,其中就有官方帮我们预先写好的Driver.我们只需要在启动SQLDelight时提供所需要的Driver就可以了.那今天我们就来学习如何使用
首先现在commonMain底下建立一层封装,DatabaseHelper来帮助我们使用DB,在这里需要一个SQLDriver和一条在背景执行IO操作的Coroutine,然後使用SQLDriver建立DB
//这边是Kotlin喔
class DatabaseHelper(
sqlDriver: SqlDriver,//各平台自己提供自己的Driver
private val backgroundDispatcher: CoroutineDispatcher//背景执行用的Coroutine
) {
private val cafeDB: CafeDB = CafeDB(sqlDriver)//建立DB就是这麽简单
}
当然有DB也有Coroutine就能够直接进行资料库的操作了,不过我们可以帮SQLDelight的Transacter多做一个扩展,让他使用起来更方便.同样的在commonMain下建立一个CoroutinesExtensions.kt来撰写我们的扩展
//这是Kotlin喔
import com.squareup.sqldelight.Transacter
import com.squareup.sqldelight.TransactionWithoutReturn
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext
suspend fun Transacter.transactionWithContext(
coroutineContext: CoroutineContext,//执行用的Coroutine
noEnclosing: Boolean = false,//闭包设定
body: TransactionWithoutReturn.() -> Unit//执行後行为
) {
withContext(coroutineContext) {
[email protected](noEnclosing) {
body()
}
}
}
我们昨天写了两个SQL,分别是插入资料的insertCafe以及读取全部资料的getAll,在DatabaseHelper内写上这两个方法
//这是Kotlin喔
class DatabaseHelper(
sqlDriver: SqlDriver,
private val backgroundDispatcher: CoroutineDispatcher
) {
private val cafeDB: CafeDB = CafeDB(sqlDriver)
fun selectAllItems(): Flow<List<CAFE>> = //使用Flow,当DB变化时Flow也会提供新资料
cafeDB.cafeQueries
.getAll()
.asFlow()
.mapToList()
.flowOn(backgroundDispatcher)
suspend fun insertCafeList(cafeList: List<CafeResponseItem>) {//插入资料
cafeDB.transactionWithContext(backgroundDispatcher) {
cafeList.forEach { cafe ->
cafeDB.cafeQueries.insertCafe(cafe.id, cafe.name,cafe.address)
}
}
}
}
然後我们有了DB操作的工具,在DataRepository内加入DatabseHelper并使用
class DataRepository :KoinComponent {
companion object {
val tag = DataRepository::class.simpleName
}
private val ktorApi: CafeApi by inject()
private val dbHelper: DatabaseHelper by inject()//我们还没写注入所以这边会有问题
suspend fun fetchCafesFromNetwork(cityName: String) =ktorApi.fetchCafeFromApi(cityName)
//以下两个方法都利用DbHelper实作
fun getCafeFromDb(): Flow<List<CAFE>> = dbHelper.selectAllItems()
suspend fun insertCafeToDB(cafeResponse: List<CafeResponseItem>) {
dbHelper.insertCafeList(cafeResponse)
}
}
再来,由於我们希望DatabaseHelper由Koin帮我们提供,所以来去修改commonMain底下的Koin.kt档案,让Koin知道如何帮我们产生DatabaseHelper,先在coreModule内加上DatabaseHelper的实作方法
//这是Kotlin喔
private val coreModule = module{
...
single {
DatabaseHelper(
get(),//根据建构子,这个应该是SqlDriver,使用get()让Koin去寻找实作方法
Dispatchers.Default//没有特别挑的Coroutine
)
}
...
}
现在问题变成了,如何提供SQLDriver给Koin了,就像前面说的,双平台的Driver不同,所以这时候就是expect/actual出马的时候了.
在commonMain的最下面,建立一个新的module,叫platformModule.这个Koin Module专门存放各平台不同的实作.所以是expect的.
expect val platformModule: Module
有了expect就要去各平台实作actual了,同样要注意package路径要相同,不然KMM没办法对应起来.
Android的实作如下,使用AndroidSqliteDriver
actual val platformModule: Module = module {
single<SqlDriver>{
AndroidSqliteDriver(CafeDB.Schema,get(),"CafeDb")//叫做AndroidSqliteDriver
}
}
然後iOS叫做NativeSqliteDriver,因为iOS是利用Native Kotlin完成的
actual val platformModule = module {
single<SqlDriver> {NativeSqliteDriver(CafeDB.Schema,"CafeDB")}
}
大功告成!
回到androidApp内,我们来修改MainViewModel,让Ktor网路请求的结果不直接显示在画面上,而是改为进入DB.
//这是Kotlin
class MainViewModel : ViewModel() , KoinComponent{
private val dataRepository: DataRepository by inject()
private val cafeList = MutableLiveData<List<CAFE>>()
val cafeListLiveData: LiveData<List<CAFE>> = Transformations.map(cafeList) { it }
fun fetchCafeList(cityName :String = "taipei"){
viewModelScope.launch {
val response = async { dataRepository.fetchCafesFromNetwork(cityName)}
val result = response.await()
//cafeList.value = result 不再直接显示
dataRepository.insertCafeToDB(result)//改为存进DB
}
}
fun fetchCafeFromDB(){
viewModelScope.launch {
dataRepository.getCafeFromDb().collect {//读取DB的资料
cafeList.value = it//资料更新到LiveData,也能使用Flow的AsLiveData
}
}
}
}
由於我们画面的资料从网路请求的CafeResponseItem换成DB的物件CAFE,所以Activity和Adatper的内容也需要一并修改.
//这边是Kotlin
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModel()
private val adapter : CafeAdapter by lazy { CafeAdapter() }
private lateinit var cafeRecyclerView : RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.fetchCafeList("taipei")//进行网路请求
viewModel.fetchCafeFromDB()//捞取DB资料
viewModel.cafeListLiveData.observe(this, Observer {
adapter.cafeList = it//这边的cafeList已经变成DB的CAFE了
adapter.notifyDataSetChanged()
})
cafeRecyclerView = findViewById(R.id.rv_cafeList)
cafeRecyclerView.adapter = adapter
cafeRecyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
cafeRecyclerView.addItemDecoration(
DividerItemDecoration(this,
DividerItemDecoration.VERTICAL)
)
}
}
//这边是Kotlin
class CafeAdapter : RecyclerView.Adapter<CafeViewHolder>() {
// var cafeList = listOF<CafeResponseItem>()网路请求物件改为DB物件
var cafeList = listOf<CAFE>()
...
}
然後执行可以看到由DB提供的资讯.
在模拟器执行的期间,打开Android Studio下方的 App Inspection,可以看到存在DB内的资料.
明天会来串iOS的DB
<<: 【Day13-计数】如何快速统计资料出现频率?——使用Counter或groupby快速计算元素出现个数
思考了目标,我们可以开始了解网站分析工具。网站工具其实有好多种,大家最常见的GA、还有其他可能像Ho...
昨天,我们已建立完决策树,那今天,我打算带入资料去看他分类结果: 建立决策树的使用模型: #用树来预...
阵列的基本介绍 简单来说是存放一组资料集 阵列 会使用 [ ] 前後包住资料集 下面的范例意思是 d...
https://wolkesau.medium.com/c-语言-f42ebe8eda8c C# 入...
What's Cypress Cypress 是 Vue.js 官方推荐的一个 E2E Testin...