Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

A Multiplatform Delight (KotlinConf 2018)

A Multiplatform Delight (KotlinConf 2018)

SQL Delight, a type-safe database API, recently completed migration from being a Java-generating, Android-specific tool to a Kotlin-generating, multiplatform one. Migrating an API from Java to Kotlin has obvious benefits, but adding multiplatform support for iOS introduces a dynamic which complicates the API, code generation, and runtime.

This talk will cover the challenges of platform-agnostic API design, type-safe multiplatform Kotlin code generation, and the integration of platform-specific runtimes such that the library not only runs efficiently on each platform but also integrates well with the other languages each might be using.

Presented with Alec Strong.

Video: https://youtu.be/WkIry790PHI

Jake Wharton

October 04, 2018
Tweet

More Decks by Jake Wharton

Other Decks in Programming

Transcript

  1. @Entity data class Player(A val name: String, val ranking: Int,

    val country: String )A @Entity data class Match(A val player1: Player, val player2: Player, val winner: Player )A
  2. Source Code Annotation
 Processor Reflection @Entity data class Player(A val

    name: String, val ranking: Int, val country: String )A @Entity data class Match(A val player1: Player, val player2: Player, val winner: Player )A
  3. Source Code Annotation
 Processor Reflection SQLite @Entity data class Player(A

    val name: String, val ranking: Int, val country: String )A @Entity data class Match(A val player1: Player, val player2: Player, val winner: Player )A
  4. CREATE TABLE player (A name TEXT NOT NULL, country TEXT

    NOT NULL, ranking INTEGER NOT NULL ); CREATE TABLE match (A player1 INTEGER NOT NULL REFERENCES player, player2 INTEGER NOT NULL REFERENCES player, winner INTEGER NOT NULL REFERENCES player );
  5. CREATE TABLE player (A id INTEGER NOT NULL PRIMARY KEY

    AUTOINCREMENT, name TEXT NOT NULL, country TEXT NOT NULL, ranking INTEGER NOT NULL ); CREATE TABLE match (A player1 INTEGER NOT NULL REFERENCES player, player2 INTEGER NOT NULL REFERENCES player, winner INTEGER NOT NULL REFERENCES player );B
  6. CREATE TABLE player (A id INTEGER NOT NULL PRIMARY KEY

    AUTOINCREMENT, name TEXT NOT NULL, country TEXT NOT NULL, ranking INTEGER NOT NULL ); CREATE TABLE match (A player1 INTEGER NOT NULL REFERENCES player, player2 INTEGER NOT NULL REFERENCES player, winner INTEGER NOT NULL REFERENCES player, PRIMARY KEY (player1, player2) ) WITHOUT ROWID;B
  7. SQLite Source Code Annotation
 Processor Reflection @Entity data class Player(

    val name: String, val ranking: Int, val country: String ) @Entity data class Match( val player1: Player, val player2: Player, val winner: Player ) CREATE TABLE player (A id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, country TEXT NOT NULL, ranking INTEGER NOT NULL ); CREATE TABLE match (A player1 INTEGER NOT NULL REFERENCES player, player2 INTEGER NOT NULL REFERENCES player, winner INTEGER NOT NULL REFERENCES player, PRIMARY KEY (player1, player2) ) WITHOUT ROWID;
  8. @Entity data class Player(A val name: String, val ranking: Int,

    val country: String )A @Entity data class Match(A val player1: Player, val player2: Player, val winner: Player )A
  9. @Entity data class Player(A @PrimaryKey @Autoincrement val id: Int, val

    name: String, val ranking: Int, val country: String )A @Entity data class Match(A val player1: Player, val player2: Player, val winner: Player )A
  10. @Entity data class Player(A @PrimaryKey @Autoincrement val id: Int, val

    name: String, val ranking: Int, val country: String )A @Entity(primaryKeys = ['player1', ‘player2']) data class Match(A val player1: Player, val player2: Player, val winner: Player )A
  11. @Entity data class Player(A @PrimaryKey @Autoincrement val id: Int, val

    name: String, val ranking: Int, val country: String )A @Entity(primaryKeys = ['player1', ‘player2']) data class Match(A val player1: Player, val player2: Player, val winner: Player )A A @PrimaryKey @Autoincrement val id: Int, @Entity(primaryKeys = ['player1', ‘player2'])
  12. ALTER TABLE player ADD COLUMN id INTEGER NOT NULL PRIMARY

    KEY AUTOINCREMENT; CREATE TABLE new_match ( player1 INTEGER NOT NULL REFERENCES player, player2 INTEGER NOT NULL REFERENCES player, winner INTEGER NOT NULL REFERENCES player, PRIMARY KEY (player1, player2) ) WITHOUT ROWID; -- migrate match to new_match
  13. CREATE TABLE player (A id INTEGER NOT NULL PRIMARY KEY

    AUTOINCREMENT, name TEXT NOT NULL, country TEXT NOT NULL, ranking INTEGER NOT NULL );
  14. CREATE TABLE player (A id INTEGER NOT NULL PRIMARY KEY

    AUTOINCREMENT, name TEXT NOT NULL, country TEXT NOT NULL, ranking INTEGER NOT NULL ); withRanking: SELECT * FROM player WHERE ranking = ?;
  15. SQLite Compiler Generated Code CREATE TABLE player (A id INTEGER

    NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, country TEXT NOT NULL, ranking INTEGER NOT NULL ); withRanking: SELECT * FROM player WHERE ranking = ?;
  16. interface PlayerModel {A long id(); @NonNull String name(); @NonNull String

    country(); long ranking(); final class Factory<T extends PlayerModel> {A public Factory(Creator<T> creator); public SqlDelightQuery withRanking(long ranking); public RowMapper<T> withRankingMapper(); }A }A
  17. interface PlayerModel {A … final class Factory<T extends PlayerModel> {A

    public Factory(Creator<T> creator); … }B }A @AutoValue public abstract class MyPlayerModel implements PlayerModel {A public static Factory<MyPlayerModel> FACTORY = new Factory(AutoValue_MyPlayerModel::new) }A
  18. SqlDelightStatement query = MyPlayerModel.FACTORY.withRanking(10); Observable<List<MyPlayerModel>> rank10 = db.createQuery(query.tables, query.statement, query.args)

    .mapToList(MyPlayerModel.FACTORY::withRankingMapper); interface PlayerModel {A … final class Factory<T extends PlayerModel> {A public Factory(Creator<T> creator); public SqlDelightQuery withRanking(long ranking); public RowMapper<T> withRankingMapper(); }B }A
  19. SQLite Compiler Generated Code CREATE TABLE player ( id INTEGER

    NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, country TEXT NOT NULL, ranking INTEGER NOT NULL ); withRanking: SELECT * FROM player WHERE ranking = ?; insertPlayer: INSERT INTO player (name, country, ranking) VALUES (?, ?, ?);
  20. CREATE TABLE player ( id INTEGER NOT NULL PRIMARY KEY

    AUTOINCREMENT, name TEXT NOT NULL, country TEXT NOT NULL, ranking INTEGER NOT NULL ); withRanking: SELECT * FROM player WHERE ranking = ?; insertPlayer: INSERT INTO player (name, country, ranking) VALUES (?, ?, ?);
  21. SQLite Compiler Generated Code CREATE TABLE player ( id INTEGER

    NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, country TEXT NOT NULL, ranking INTEGER NOT NULL ); withRanking: SELECT * FROM player WHERE ranking = ?; insertPlayer: INSERT INTO player (name, country, ranking) VALUES (?, ?, ?);
  22. interface Player {A val id: Long val name: String val

    country: String val ranking: Long data class Impl(A override val id: Long, override val name: String, override val country: String, override val ranking: Long ) : Player }A
  23. class PlayerQueries : Transacter {A fun <T : Any> withRanking(

    ranking: Long, mapper: ( id: Long, name: String, country: String, ranking: Long ) -> T ): Query<T> fun withRanking(ranking: Long): Query<Player> fun insertPlayer( name: String, country: String, ranking: Long ): Long }A
  24. headToHead: SELECT player1.name AS player1Name, player2.name AS player2Name, count(nullif(match.winner =

    player1.id, 0)) AS player1Wins, count(nullif(match.winner = player2.id, 0)) AS player2Wins FROM match JOIN player AS player1 ON (player1.name = :firstPlayerName AND (player1.id = match.player1 OR player1.id = match.player2) ) JOIN player AS player2 ON (player2.name = :secondPlayerName AND (player2.id = match.player1 OR player2.id = match.player2) ) ;
  25. class PlayerQueries : Transacter {A fun <T : Any> headToHead(

    firstPlayerName: String, secondPlayerName: String, mapper: ( player1Name: String, player2Name: String, player1Wins: Long, player2Wins: Long ) -> T ): Query<T> fun headToHead( firstPlayerName: String, secondPlayerName: String ): Query<com.sample.tennis.db.HeadToHead> }A
  26. class PlayerQueries : Transacter {A fun <T : Any> withRanking(

    ranking: Long, mapper: ( id: Long, name: String, country: String, ranking: Long ) -> T ): Query<T> fun withRanking(ranking: Long): Query<Player> fun insertPlayer( name: String, country: String, ranking: Long ): Long }A
  27. class PlayerQueries : Transacter {A fun withRanking(ranking: Long): Query<Player> }A

    val player: Query<Player> = playerQueries.withRanking(10)
  28. Query< > class PlayerQueries : Transacter {A fun withRanking(ranking: Long):

    Query<Player> }A val player: Player = playerQueries.withRanking(10).executeAsOne()
  29. class PlayerQueries : Transacter {A fun withRanking(ranking: Long): Query<Player> }A

    val player: Observable<Query<Player>> = playerQueries.withRanking(10) .asObservable() .ex
  30. class PlayerQueries : Transacter {A fun withRanking(ranking: Long): Query<Player> }A

    val player: Observable<List<Player>> = playerQueries.withRanking(10) .asObservable() .mapToList()
  31. class PlayerQueries : Transacter {A fun <T : Any> withRanking(ranking:

    Long, mapper: ( id: Long, name: String, country: String, ranking: Long ) -> T): Query<T> }A val player = playerQueries.withRanking(10, ::MyPlayer) P l a y e r
  32. class PlayerQueries : Transacter {A }A val id = playerQueries.insertPlayer(

    name = "Roger Federer", country = "Switzerland", ranking = 2 ) fun insertPlayer( name: String, country: String, ranking: Long ): Long
  33. Generated Code SQLite Compiler CREATE TABLE player (A id INTEGER

    NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, country TEXT NOT NULL, ranking INTEGER NOT NULL ); withRanking: SELECT * FROM player WHERE ranking = ?; class PlayerQueries : Transacter {A fun insertPlayer( name: String, country: String, ranking: Long ): Long }A
  34. CREATE TABLE player ( id INTEGER NOT NULL PRIMARY KEY

    AUTOINCREMENT, name TEXT NOT NULL, country NOT NULL, ranking INTEGER NOT NULL ); TEXT
  35. CREATE TABLE player ( id INTEGER NOT NULL PRIMARY KEY

    AUTOINCREMENT, name TEXT NOT NULL, country NOT NULL, ranking INTEGER NOT NULL ); import com.tennis.db.CountryCode; TEXT AS CountryCode
  36. import com.tennis.db.CountryCode; CREATE TABLE player ( id INTEGER NOT NULL

    PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, country TEXT AS CountryCode NOT NULL, ranking INTEGER NOT NULL );
  37. SQLite Compiler Generated Code import com.tennis.db.CountryCode; CREATE TABLE player (

    id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, country TEXT AS CountryCode NOT NULL, ranking INTEGER NOT NULL );
  38. class QueryWrapper( database: SqlDatabase, internal val playerAdapter: Player.Adapter ) {A

    val playerQueries: PlayerQueries }A interface Player {A class Adapter( val countryAdapter: ColumnAdapter<CountryCode, String> ) }A
  39. class QueryWrapper( database: SqlDatabase, internal val playerAdapter: Player.Adapter ) {A

    val playerQueries: PlayerQueries }A val queryWrapper = QueryWrapper( database = database, playerAdapter = Player.Adapter( countryAdapter = EnumColumnAdapter() ), )
  40. class QueryWrapper( database: SqlDatabase, internal val playerAdapter: Player.Adapter ) {A

    val playerQueries: PlayerQueries }A val queryWrapper = QueryWrapper( database = database, playerAdapter = Player.Adapter( countryAdapter = EnumColumnAdapter() ), ) val playerQueries: PlayerQueries = queryWrapper.playerQueries val playerQueries: PlayerQueries = queryWrapper.playerQueries val playerQueries: PlayerQueries
  41. class QueryWrapper {A object Schema : SqlDatabase.Schema {A override val

    version: Int get() = 2 override fun create(db: SqlDatabaseConnection) override fun migrate( db: SqlDatabaseConnection, oldVersion: Int, newVersion: Int ) }A }A
  42. SQLite Generated Code Compiler CREATE TABLE player (A id INTEGER

    NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, country TEXT NOT NULL, ranking INTEGER NOT NULL ); withRanking: SELECT * FROM player WHERE ranking = ?; class QueryWrapper {A object Schema : SqlDatabase.Schema {A override val version: Int get() = 2 override fun create(db: SqlDatabaseConnection) override fun migrate( db: SqlDatabaseConnection, oldVersion: Int, newVersion: Int ) }A }A
  43. ALTER TABLE player ADD COLUMN id INTEGER NOT NULL PRIMARY

    KEY AUTOINCREMENT; CREATE TABLE new_match ( player1 INTEGER NOT NULL REFERENCES player, player2 INTEGER NOT NULL REFERENCES player, winner INTEGER NOT NULL REFERENCES player, PRIMARY KEY (player1, player2) ) WITHOUT ROWID; -- migrate match to new_match
  44. SQLite Migrations SQLite Generated Code Compiler ALTER TABLE player ADD

    COLUMN id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT; CREATE TABLE new_match ( player1 INTEGER NOT NULL REFERENCES player, player2 INTEGER NOT NULL REFERENCES player, winner INTEGER NOT NULL REFERENCES player, PRIMARY KEY (player1, player2) ) WITHOUT ROWID; -- migrate match to new_match
  45. db

  46. SQLite Generated Code SQL Delight CREATE TABLE player (A id

    INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, country TEXT NOT NULL, ranking INTEGER NOT NULL ); withRanking: SELECT * FROM player WHERE ranking = ?; db
  47. android.database.* android.database.sqlite.* sqlite3.h Node? WASM? Vanilla JS? Generated Code Android

    Native JavaScript Database Abstraction java.sql.* org.sqlite.* (xerial) JVM
  48. android.database.* android.database.sqlite.* sqlite3.h Node? WASM? Vanilla JS? Generated Code Android

    JVM Native JavaScript Database Abstraction java.sql.* org.sqlite.* (xerial) S Sql Sql Tran T S Q
  49. android.database.* android.database.sqlite.* sqlite3.h Node? WASM? Vanilla JS? Generated Code Android

    Native JavaScript Database Abstraction java.sql.* org.sqlite.* (xerial) Android Driver knarch.db Driver JDBC Driver JS Driver JVM
  50. android.database.* android.database.sqlite.* sqlite3.h Android Native java.sql.* org.sqlite.* (xerial) Android Driver

    knarch.db Driver JDBC Driver JS JVM AndroidX Sqlite Abstraction AndroidX Sqlite Framework Impl
  51. android.database.* android.database.sqlite.* sqlite3.h Android Native java.sql.* org.sqlite.* (xerial) Android Driver

    knarch.db Driver JDBC Driver JS JVM AndroidX Sqlite Abstraction AndroidX Sqlite Framework Impl Android android.database.* net.sqlcipher.database.* AndroidX SQLCipher Impl
  52. android. android. sqlite3.h Node? WASM? Vanilla JS? ed Native JavaScript

    Database Abstraction java.sql.* org.sqlite.* (xerial) Android Driver knarch.db Driver JDBC Driver JS Driver JVM AndroidX Sqlite Abstraction AndroidX Sqlite Framework Impl android. net.sqlc AndroidX SQLCipher Impl
  53. android. android. sqlite3.h Node? WASM? Vanilla JS? ed Native JavaScript

    Database Abstraction java.sql.* org.sqlite.* (xerial) Android Driver knarch.db Driver JDBC Driver JS Driver JVM AndroidX Sqlite Abstraction AndroidX Sqlite Framework Impl android. net.sqlc AndroidX SQLCipher Impl AAA BBB
  54. sqlite3.h Node? WASM? Vanilla JS? ed Native JavaScript Database Abstraction

    java.sql.* org.sqlite.* (xerial) Driver knarch.db Driver JDBC Driver JS Driver JVM Abstraction android. net.sqlc AndroidX SQLCipher Impl AAA BBB
  55. sqlite3.h ed Native Node? WASM? Vanilla JS? JavaScript Database Abstraction

    java.sql.* org.sqlite.* (xerial) Driver knarch.db Driver JDBC Driver JVM Abstraction android. net.sqlc AndroidX SQLCipher Impl
  56. android.database.* android.database.sqlite.* sqlite3.h Generated Code Android Native Node? WASM? Vanilla

    JS? JavaScript Database Abstraction java.sql.* org.sqlite.* (xerial) Android Driver knarch.db Driver JDBC Driver JVM AndroidX Sqlite Abstraction AndroidX Sqlite Framework Impl Android android.database.* net.sqlcipher.database.* AndroidX SQLCipher Impl
  57. android.database.* android.database.sqlite.* sqlite3.h Generated Code Android Native Node? WASM? Vanilla

    JS? JavaScript Database Abstraction java.sql.* org.sqlite.* (xerial) Android Driver knarch.db Driver JDBC Driver JVM AndroidX Sqlite Abstraction AndroidX Sqlite Framework Impl Android android.database.* net.sqlcipher.database.* AndroidX SQLCipher Impl
  58. package com.squareup.sqldelight.db expect interface Closeable { fun close() } expect

    inline fun <T : Closeable?, R> T.use(body: (T) -> R): R
  59. package com.squareup.sqldelight.db expect interface Closeable { fun close() } expect

    inline fun <T : Closeable?, R> T.use(body: (T) -> R): R package com.squareup.sqldelight.db import kotlin.io.use as kotlinIoUse actual typealias Closeable = java.io.Closeable actual inline fun <T : Closeable?, R> T.use(body: (T) -> R): R = kotlinIoUse(body)
  60. package com.squareup.sqldelight.db expect interface Closeable { fun close() } expect

    inline fun <T : Closeable?, R> T.use(body: (T) -> R): R package com.squareup.sqldelight.db actual interface Closeable { actual fun close() } actual inline fun <T : Closeable?, R> T.use(body: (T) -> R): R { // Copy/paste JVM implementation… }
  61. apply plugin: 'org.jetbrains.kotlin.platform.common' dependencies { compile 'com.squareup.sqldelight:runtime-common:1.0.0' } apply plugin:

    'org.jetbrains.kotlin.platform.jvm' // Or .android dependencies { compile 'com.squareup.sqldelight:runtime-jdk:1.0.0' } apply plugin: 'org.jetbrains.kotlin.platform.js' dependencies { compile 'com.squareup.sqldelight:runtime-js:1.0.0' }
  62. apply plugin: 'org.jetbrains.kotlin.platform.common' dependencies { compile 'com.squareup.sqldelight:runtime-common:1.0.0' } apply plugin:

    'org.jetbrains.kotlin.platform.jvm' // Or .android dependencies { compile 'com.squareup.sqldelight:runtime-jdk:1.0.0' } apply plugin: 'org.jetbrains.kotlin.platform.js' dependencies { compile 'com.squareup.sqldelight:runtime-js:1.0.0' } apply plugin: 'org.jetbrains.kotlin.platform.native' // ???
  63. apply plugin: 'org.jetbrains.kotlin.platform.common' dependencies { compile 'com.squareup.sqldelight:runtime-common:1.0.0' } apply plugin:

    'org.jetbrains.kotlin.platform.jvm' // Or .android dependencies { compile 'com.squareup.sqldelight:runtime-jdk:1.0.0' } apply plugin: 'org.jetbrains.kotlin.platform.js' dependencies { compile 'com.squareup.sqldelight:runtime-js:1.0.0' } apply plugin: 'org.jetbrains.kotlin.platform.native' // nope!
  64. runtime-1.0.0.module { "variants": [ { "name": "iosArm64-api", "available-at": { "url":

    "../../runtime-iosarm64/runtime-iosarm64-1.0.0.module" } }, ... ]
 }
  65. com.squareup.sqldelight - android-driver android-driver-1.0.0.aar android-driver-1.0.0-javador.jar android-driver-1.0.0-sources.jar android-driver-1.0.0-pom.xml + runtime +

    runtime-common + runtime-iosarm64 + runtime-iosx64 + runtime-jvm + runtime-js android-driver-1.0.0-pom.xml + runtime-jvm
  66. com.squareup.sqldelight - android-driver android-driver-1.0.0.aar android-driver-1.0.0-javador.jar android-driver-1.0.0-sources.jar android-driver-1.0.0-pom.xml + runtime +

    runtime-common + runtime-iosarm64 + runtime-iosx64 + runtime-jvm + runtime-js android-driver-1.0.0-pom.xml + runtime
  67. com.squareup.sqldelight - android-driver android-driver-1.0.0.aar android-driver-1.0.0-javador.jar android-driver-1.0.0-sources.jar android-driver-1.0.0-pom.xml + runtime +

    runtime-common + runtime-iosarm64 + runtime-iosx64 + runtime-jvm + runtime-js android-driver-1.0.0-pom.xml + runtime-jdk
  68. apply plugin: ‘com.android.application’ apply plugin: 'org.jetbrains.kotlin.multiplatform' dependencies {A implementation 'com.squareup.sqldelight:android-driver:1.0.0'

    }A kotlin {A sourceSets {A commonMain {A dependencies {A implementation project(':sqldelight-sample:sample-common') }A }A }A }A
  69. apply plugin: ‘com.android.application’ apply plugin: 'org.jetbrains.kotlin.multiplatform' dependencies {A implementation 'com.squareup.sqldelight:android-driver:1.0.0'

    }A kotlin {A sourceSets {A commonMain {A dependencies {A implementation project(':sqldelight-sample:sample-common') }A }A }A }A A implementation 'com.squareup.sqldelight:android-driver:1.0.0' A A implementation project(':sqldelight-sample:sample-common')
  70. apply plugin: ‘com.android.application’ apply plugin: 'org.jetbrains.kotlin.multiplatform' dependencies {A implementation (“com.squareup.sqldelight:android-driver:1.0.0)

    {A exclude group: 'com.squareup.sqldelight', module: 'runtime-jdk' }A }A kotlin {A sourceSets {A commonMain {A dependencies {A implementation project(':sqldelight-sample:sample-common') }A }A }A }A implementation ('com.squareup.sqldelight:android-driver:1.0.0') {A exclude group: 'com.squareup.sqldelight', module: 'runtime-jdk' }A implementation project(':sqldelight-sample:sample-common')
  71. abstract class Query<out RowType : Any> { fun execute(): SqlCursor

    fun executeAsList(): List<RowType> fun executeAsOne(): RowType fun executeAsOneOrNull(): RowType? }X
  72. abstract class Query<out RowType : Any> { fun execute(): SqlCursor

    fun executeAsList(): List<RowType> fun executeAsOne(): RowType fun executeAsOneOrNull(): RowType? fun addListener(listener: Listener) fun removeListener(listener: Listener) interface Listener { fun queryResultsChanged() }Y }X
  73. abstract class Query<out RowType : Any> { fun execute(): SqlCursor

    fun executeAsList(): List<RowType> fun executeAsOne(): RowType fun executeAsOneOrNull(): RowType? fun addListener(listener: Listener) fun removeListener(listener: Listener) interface Listener { fun queryResultsChanged() }Y fun notifyDataChanged() }X
  74. val usersQuery: Query<User> = // … usersQuery.addListener(object : Listener {

    override fun queryResultsChanged() { val users = usersQuery.executeAsList() // Show users somewhere… }G }) usersQuery.notifyDataChanged()
  75. val usersQuery: Query<User> = // … val executor = //

    … usersQuery.addListener(object : Listener { override fun queryResultsChanged() { executor.execute { val users = usersQuery.executeAsList() // Show users somewhere… }G }G }) usersQuery.notifyDataChanged()
  76. val usersQuery: Query<User> = // … val executor = //

    … val mainThread = Handler(Looper.getMainLooper()) usersQuery.addListener(object : Listener { override fun queryResultsChanged() { executor.execute { val users = usersQuery.executeAsList() mainThread.post { // Show users somewhere… } }G }G }) usersQuery.notifyDataChanged()
  77. val usersQuery: Query<User> = // … val usersObservable: Observable<List<User>> =

    usersQuery.asObservable() .map { it.executeAsList() } Query
  78. val usersQuery: Query<User> = // … val usersChannel: ReceiveChannel<List<User>> =

    usersQuery.asChannel() .map { it.executeAsList() } Query
  79. suspend fun users() { val usersQuery: Query<User> = // …

    val usersChannel: ReceiveChannel<List<User>> = usersQuery.asChannel() .mapToList() return usersChannel.receive() }
  80. suspend fun users() { val usersQuery: Query<User> = // …

    val usersChannel: ReceiveChannel<List<User>> = usersQuery.asChannel() .mapToList() return usersChannel.receive() }
  81. suspend fun users() { val usersQuery: Query<User> = // …

    val usersChannel: ReceiveChannel<List<User>> = usersQuery.asChannel() .mapToList() return usersChannel.receive() }
  82. val usersQuery: Query<User> = // … val executor = //

    … val usersLiveData: LiveData<List<User>> = usersQuery.asLiveDataList(executor)B
  83. val pagedQuery = QueryDataSourceFactory( queryProvider = { limit, offset ->

    queries.users(limit, offset) }, count = queries.usersCount())
  84. apply plugin: 'com.squareup.sqldelight' apply plugin: 'org.jetbrains.kotlin.jvm' // automatically adds… dependencies

    { api 'com.squareup.sqldelight:runtime-jdk:1.0.0' }X tasks.create('generateSqlDelight', SqlDelightTask) { // … }X tasks.getByName('compileKotlin').dependsOn('generateSqlDelight')
  85. apply plugin: 'com.squareup.sqldelight' apply plugin: 'org.jetbrains.kotlin.js' // automatically adds… dependencies

    { api 'com.squareup.sqldelight:runtime-js:1.0.0' }X tasks.create('generateSqlDelight', SqlDelightTask) { // … }X tasks.getByName('compileKotlin').dependsOn('generateSqlDelight')
  86. apply plugin: 'com.squareup.sqldelight' apply plugin: 'org.jetbrains.kotlin.multiplatform' // automatically adds… dependencies

    { api 'com.squareup.sqldelight:runtime:1.0.0' }X tasks.create('generateSqlDelight', SqlDelightTask) { // … }X tasks.getByName('compileKotlin').dependsOn('generateSqlDelight')
  87. apply plugin: 'com.squareup.sqldelight' apply plugin: 'org.jetbrains.kotlin.android' // automatically adds… dependencies

    { api 'com.squareup.sqldelight:runtime-jdk:1.0.0' }X tasks.create('generateSqlDelight', SqlDelightTask) { // … }X tasks.getByName('compileKotlin').dependsOn('generateSqlDelight')
  88. m u l t i p l a t f

    o r m apply plugin: 'com.squareup.sqldelight' apply plugin: 'org.jetbrains.kotlin.android' // automatically adds… dependencies { api 'com.squareup.sqldelight:runtime-jdk:1.0.0' }X tasks.create('generateDebugSqlDelight', SqlDelightTask) { // … }X tasks.getByName('compileDebugKotlin').dependsOn('generateDebugSqlDelight') tasks.create('generateReleaseSqlDelight', SqlDelightTask) { // … }X tasks.getByName('compileReleaseKotlin').dependsOn('generateReleaseSqlDelight')
  89. tasks.getByName('compileDebugKotlin').dependsOn('generateDebugSqlDelight') tasks.getByName('compileReleaseKotlin').dependsOn('generateReleaseSqlDelight') m u l t i p l a

    t f o r m apply plugin: 'com.squareup.sqldelight' apply plugin: 'org.jetbrains.kotlin.android' // automatically adds… dependencies { api 'com.squareup.sqldelight:runtime-jdk:1.0.0' }X tasks.create('generateDebugSqlDelight', SqlDelightTask) { /* … */ }X tasks.create('generateReleaseSqlDelight', SqlDelightTask) { /* … */ }X tasks.create('verifySqlDelightMigration', VerifyMigrationTask) { // … }X
  90. sqlite3.h ed Native Node? WASM? Vanilla JS? JavaScript Database Abstraction

    java.sql.* org.sqlite.* (xerial) Driver knarch.db Driver JDBC Driver JS Driver JVM Abstraction android. net.sqlc AndroidX SQLCipher Impl
  91. sqlite3.h ed Native Node? WASM? Vanilla JS? JavaScript Database Abstraction

    java.sql.* org.sqlite.* (xerial) Driver knarch.db Driver JDBC Driver JVM Abstraction android. net.sqlc AndroidX SQLCipher Impl
  92. iOS

  93. struct Player {A var id: NSNumber var name: String var

    country: String var ranking: NSNumber }A let firstPlace = playerQueries.withRanking( ranking: 1 ).executeAsOne()
  94. struct Player {A var id: NSNumber var name: String var

    country: String var ranking: NSNumber }A func createPlayer(id: NSNumber, name: String, country: String, ranking: NSNumber) -> Player {A return Player(id: id, name: name, country: country, ranking: ranking) }A let firstPlace = playerQueries.withRanking( ranking: 1 ).executeAsOne()
  95. struct Player {A var id: NSNumber var name: String var

    country: String var ranking: NSNumber }A func createPlayer(id: NSNumber, name: String, country: String, ranking: NSNumber) -> Player {A return Player(id: id, name: name, country: country, ranking: ranking) }A let firstPlace = playerQueries.withRanking( ranking: 1, mapper: createPlayer ).executeAsOne() as! Player
  96. withRanking: SELECT name FROM player WHERE ranking = ?; insert:

    INSERT INTO player (name, country, ranking) VALUES (?, ?, ?);
  97. withRanking: SELECT name WHERE ranking = ?; insert: (name, country,

    ranking) VALUES (?, ?, ?); FROM player INSERT INTO player
  98. withRanking: SELECT name FROM player WHERE ranking = ?; insert:

    INSERT INTO player (name, country, ranking) VALUES (?, ?, ?); private inner class Insert { fun execute(): Long { val result = statement.execute() notifyQueries(queryWrapper.playerQueries.withRanking) return result } }
  99. withRanking: SELECT name FROM player WHERE ranking = ?; insert:

    INSERT INTO player (name, country, ranking) VALUES (?, ?, ?); private inner class Insert { fun execute(): Long { val result = statement.execute() notifyQueries(queryWrapper.playerQueries.withRanking) return result } } private inner class Insert { fun execute(): Long { val result = statement.execute() notifyQueries(queryWrapper.playerQueries.withRanking) return result } } withRanking: SELECT name FROM player WHERE ranking = ?; insert: INSERT INTO player (name, country, ranking) VALUES (?, ?, ?);
  100. withRanking: SELECT name FROM player WHERE ranking = ?; insert:

    INSERT INTO player (name, country, ranking) VALUES (?, ?, ?); private inner class Insert { fun execute(): Long { val result = statement.execute() notifyQueries(queryWrapper.playerQueries.withRanking) return result } } withRanking: SELECT name FROM player WHERE ranking = ?; insert: INSERT INTO player (name, country, ranking) VALUES (?, ?, ?);