iT邦幫忙

2022 iThome 鐵人賽

DAY 23
0
Mobile Development

Kotlin 全面啟動 系列 第 23

[Kotlin 全面啟動] SQLDelight II

  • 分享至 

  • xImage
  •  

有了昨天介紹 SQLDelight 的基礎之下,讓我們繼續來看看怎麼使用它。

如果你還沒看過上一篇的話,請點這裡:
https://ithelp.ithome.com.tw/articles/10304793

DAO

我們所定義的 Player.sq 除了會在 MyDBImpl 裡插入 create table 的 command 之外,也會在相對應的 package 下建立相對應的 object class 以及他的 query class,以我們的例子就是 HockeyPlayerPlayerQueries,不過點進去看以後應該會發現目前好像沒什麼特別的功能,如下:

// HockeyPlayer.kt
public data class HockeyPlayer(
  public val player_number: Long,
  public val full_name: String
) {
  public override fun toString(): String = """
  |HockeyPlayer [
  |  player_number: $player_number
  |  full_name: $full_name
  |]
  """.trimMargin()
}

// PlayerQueries.kt
public interface PlayerQueries : Transacter

MyDBImpl 裡也會有一小段相關程式碼如下:

private class PlayerQueriesImpl(
  private val database: MyDBImpl,
  private val driver: SqlDriver
) : TransacterImpl(driver), PlayerQueries

SQL command to function

接下來回到我們一開始的問題,我們要怎麼在 SQLDelight 定義 function,讓我們可以動態的呼叫相對應的 function 來操作我們的 table 呢?

要回答這個問題還是需要回頭看我們的 Player.sq,既然 SQLDelight 有辦法理解 sql command,最有可能的方式就是在這邊寫更多的 sql command 並給它個名字變成可呼叫的 function,而在 SQLDelight 裡定義一個 function 只要用冒號結尾,並接著 sql command 就可以了,範例如下:

selectAll:
SELECT *
FROM hockeyPlayer;

insert:
INSERT INTO hockeyPlayer(player_number, full_name)
VALUES (?, ?);

insertFullPlayerObject:
INSERT INTO hockeyPlayer(player_number, full_name)
VALUES ?;

把以上三段 sql 加到 Player.sq 並成功的 compile 之後,就會發現原本空的 PlayerQueries 現在變得跟 Room 所定義的 interface 有點像了,如下:

public interface PlayerQueries : Transacter {
  public fun <T : Any> selectAll(mapper: (player_number: Long, full_name: String) -> T): Query<T>

  public fun selectAll(): Query<HockeyPlayer>

  public fun insert(player_number: Long, full_name: String): Unit

  public fun insertFullPlayerObject(hockeyPlayer: HockeyPlayer): Unit
}

而具體的實作依然是放在 MyDBImpl 裡面,有興趣的大家可以自行看看~

現在的問題變成我們要怎麼使用呢?

Driver and DB

這時候就是我們的 driver 登場的時候了!相信大家還記得這些 driver 是 platform dependent 的,所以我們可以運用之前所介紹的 expect/actual 技巧,定義如下:

// in src/commonMain
expect class DriverFactory {
  expect fun createDriver(): SqlDriver
}

fun createDatabase(driverFactory): MyDB {
    val driver = driverFactory.createDriver()
    val database = MyDB(driver)

    return database
}

// in src/androidMain
actual class DriverFactory(private val context: Context) {
  actual fun createDriver(): SqlDriver {
    return AndroidSqliteDriver(MyDB.Schema, context, "test.db") 
  }
}

// in src/iosMain
actual class DriverFactory {
  actual fun createDriver(): SqlDriver {
    return NativeSqliteDriver(MyDB.Schema, "test.db")
  }
}

Sample

接下來在各 platform 裡建立 driverFactory 後,呼叫 createDatabase 就可以拿到 MyDB 的實體囉,而透過 database.playerQueries 就可以拿到我們的 query class,有這個物件就可以呼叫我們所定義好的 sql command 囉,範例如下:

// init here, can move to di
val driver = driverFactory.createDriver()
val database = MyDB(driver)

val playerQueries: PlayerQueries = database.playerQueries

println(playerQueries.selectAll().executeAsList())
// Prints [HockeyPlayer(15, "Ryan Getzlaf")]

playerQueries.insert(player_number = 10, full_name = "Corey Perry")
println(playerQueries.selectAll().executeAsList())
// Prints [HockeyPlayer(15, "Ryan Getzlaf"), HockeyPlayer(10, "Corey Perry")]

val player = HockeyPlayer(10, "Ronald McDonald")
playerQueries.insertFullPlayerObject(player)

心得

其實在 multi-platform 裡使用 sql 說起來有點小複雜,各 platform dependent 的程式以及 sql 跟 Kotlin 間的轉換,還有不少 interface 跟 implementation 的結構,但這個設計其實很漂亮,相信多試幾次後大家一定沒問題的!

Reference

https://github.com/cashapp/sqldelight/
https://cashapp.github.io/sqldelight/
https://cashapp.github.io/sqldelight/multiplatform_sqlite/


上一篇
[Kotlin 全面啟動] SQLDelight
下一篇
[Kotlin 全面啟動] KotlinPoet
系列文
Kotlin 全面啟動 30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言