当前位置: 首页 > 开发杂谈 >

Day 23: 不同的环境,不同的Driver,利用Driver 驾驭SQLDelight

Keyword:SQLDelight,Driver
到23日,引入SQLDelight,到在Android上呈现DB资料
KMMDay23


在各平台上的SQLDelight的实作方法不同,因此也需要不同的Driver,好在这个Driver我们不用自己写,在一开始引入SQLDelight时的Gradle,有几行就是分别写在各个平台专用的区块,其中就有官方帮我们预先写好的Driver.我们只需要在启动SQLDelight时提供所需要的Driver就可以了.那今天我们就来学习如何使用

使用DatabaseHelper封装

首先现在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) {
        this@transactionWithContext.transaction(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出马的时候了.

根据平台提供Driver

在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")}
}

大功告成!

在Android使用SQLDelight

回到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内的资料.

https://github.com/officeyuli/itHome2021/raw/main/day23/DB.png

明天会来串iOS的DB


相关文章:

  • 强敌!费波那契数的哥哥登场,Ruby 30 天刷题修行篇第五话
  • 为什么亚马逊排名靠前却没出单?
  • 外贸卖家怎样识别客户的价值
  • 中国卖家如何注册亚马逊物流服务?
  • 关於伪元素 ( Pseudo-elements )
  • Day05 - 纯 Html - 复杂型别 object + collection
  • 第55天~
  • 第六章 之二
  • 甘特图
  • 在亚马逊上斩获4000+好评、品类排名第一,这款宠物玩具现在居然这么火?
  • Day 16 「听从你的蜥蜴脑」单元测试、Code Smell 与重构 - If 篇
  • [27] 用 python 刷 Leetcode: 455
  • Flutter基础介绍与实作-Day18 FireBase
  • [Day18] Vite 出小蜜蜂~ 位置校正 Position Adjustment!
  • 亚马逊卖家备战年终购物季需要注意哪些方面
  • 菲律宾虚拟卡预付卡大全
  • 免费VPS/腾讯云国际版/免费送300美金/有效期6个月
  • 谷歌将在6月中旬启动网页体验(Page Experience)更新,Search Console 新推出网页体验报告
  • 寻找印度市场伙伴
  • 洛杉矶CN2服务器推荐:PCCW线路VPS,服务器服务商layerhost
  • 性价比最高的国外VPS服务器推荐:便宜好用的美国VPS大全
  • 海外适合游戏投放的渠道有哪些?
  • Monzo Bank教程:英国银行卡申请教程【教你国内注册申请欧洲银行卡】
  • Gutenberg 10.4 在自定义程序中引入了块小工具
  • Git是什么?
  • WordPress SEO怎么做?如何优化WordPress提升流量
  • 专业提供东南亚-越南线上支付通道
  • 最便宜的国外VPS推荐:5美金以下的VPS大全
  • PayPal解决找回密码时无法检验身份问题
  • 韩国Moack蘑菇CN2服务器评测和特价服务器,优惠码