SQLDelight, a powerful Kotlin library for writing platform-agnostic SQL queries, joins forces with caching to create a dynamic duo that elevates your app’s efficiency. Here will be storing the refreshToken which we will get from the response of login & signUp API call. So next time when user opens app, it won’t ask for authentication.

Setup SQLDelight Dependencies

First you need to add required SQLDelight dependencies and plugins in libs.versions.toml file inside gradle folder.

[versions]
...
...
sqlDelight = "2.0.1"

[libraries]
...
...
sqldelight-androidDriver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqlDelight" }
sqldelight-jvmDriver = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqlDelight" }
sqldelight-nativeDriver = { module = "app.cash.sqldelight:native-driver", version.ref = "sqlDelight" }
sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqlDelight" }
sqldelight-primitiveAdapters = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqlDelight" }

[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
androidLibrary = { id = "com.android.library", version.ref = "agp" }
jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlinxSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
sqlDelight = { id = "app.cash.sqldelight", version.ref = "sqlDelight" }

Next step is to add plugin in project-level build.gradle.kts file

plugins {
// this is necessary to avoid the plugins to be loaded multiple times
// in each subproject's classloader
alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.androidLibrary) apply false
alias(libs.plugins.jetbrainsCompose) apply false
alias(libs.plugins.kotlinMultiplatform) apply false
//SQLDelight Plugin
alias(libs.plugins.sqlDelight) apply false
}

Finally, you need to configure SQLDelight in composeApp build.gradle.kts file. Here we will add dependencies based on the required platform and add name of SQLDelight database. You will see sqlDelight block at the end which will contain a list of databases and their parameter

import org.jetbrains.compose.ExperimentalComposeLibrary
import org.jetbrains.compose.desktop.application.dsl.TargetFormat

plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidApplication)
alias(libs.plugins.jetbrainsCompose)
alias(libs.plugins.kotlinxSerialization)
alias(libs.plugins.sqlDelight)
}

kotlin {

androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = "1.8"
}
}
}

jvm("desktop")

listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "ComposeApp"
// This should be set to false to run on iOS
isStatic = false
// Add it to avoid sqllite3 issues in iOS
linkerOpts.add("-lsqlite3")
}
}

sourceSets {
val desktopMain by getting

androidMain.dependencies {
implementation(libs.compose.ui.tooling.preview)
implementation(libs.androidx.activity.compose)
implementation(libs.ktor.client.okhttp)
//SqlDelight for Android
implementation(libs.sqldelight.androidDriver)
}
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.ui)
@OptIn(ExperimentalComposeLibrary::class)
implementation(compose.components.resources)
//Ktor
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.serialization.kotlinx.json)
//Moko MVVM
implementation(libs.moko.mvvm.core)
implementation(libs.moko.mvvm.compose)
//Kamel
implementation(libs.kamel)
// Navigator
implementation(libs.voyager.navigator)
//SqlDelight for common
implementation(libs.sqldelight.coroutines)
implementation(libs.sqldelight.primitiveAdapters)
}
desktopMain.dependencies {
implementation(compose.desktop.currentOs)
implementation(libs.kotlinx.coroutines.swing)
implementation(libs.ktor.client.okhttp)
//SqlDelight for jvm
implementation(libs.sqldelight.jvmDriver)
}
iosMain.dependencies {
//SqlDelight for iOS
implementation(libs.sqldelight.nativeDriver)
}
}
}

sqldelight {
databases {
//Note: Name of your Database and .sq file should be same
create("Database") {
packageName.set("com.dwarshb.firebaseauthentication")
}
}
// Add this line to avoid library linking issues
linkSqlite = true
}

android {
...
}

compose.desktop {
...
}

Create Database.sq file for Queries

Next step is to create .sq file.which will contain all the needed SQL queries. By default, the SQLDelight plugin reads .sq from the packages folder in sqldelight folder which will be directly inside commainMain folder.
Please check below Screenshot of Folder Structure where Database.sq file is placed.

If your package name is com.example.projectName, then your Database.sq file should be placed inside commonMain/sqldelight/com/example/projectName/Database.sq

Add below code inside your Database.sq file which contains queries such as createTable, insertUser, removeAllUsers and getAllUsers

After the project is compiled, the generated Kotlin code will be stored in the composeApp/build/generated/sqldelight directory. Or else you can also use ./gradlew generateSqlDelightInterface command in terminal to generate sqldelight kotlin code.

Create database drivers

SQLDelight provides multiple platform-specific implementations of the SQLite driver, so you need to create them for each platform separately. You can do this by using expected and actual declarations.

In composeApp/src/commonMain/kotlin create the package and inside it create DriverFactory.kt class

package com.dwarshb.firebaseauthentication

import app.cash.sqldelight.db.SqlDriver

expect class DriverFactory {
fun createDriver(): SqlDriver
}

Now we need to implement this for each target platform.

On Android, the AndroidSqliteDriver class implements the SQLite driver.
So, in composeApp/src/androidMain/kotlin create the package and inside it create DriverFactory.kt class

package com.dwarshb.firebaseauthentication

import android.content.Context
import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.android.AndroidSqliteDriver

actual class DriverFactory(var appContext: Context) {

actual fun createDriver(): SqlDriver {
return AndroidSqliteDriver(Database.Schema, appContext, "firebase.db")
}
}

Now you need to create its instance in MainActivity.kt file available in composeApp/src/androidMain/kotlin to make it work for Android.


class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Create instance of DriverFactory for Android
val driverFactory = DriverFactory(this)
setContent {
App(driverFactory.createDriver())
}
}
}

On iOS, the SQLite driver implementation is the NativeSqliteDriver class.
So, in composeApp/src/iosMain/kotlin create the package and inside it create DriverFactory.kt class

package com.dwarshb.firebaseauthentication

import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.native.NativeSqliteDriver

actual class DriverFactory {
actual fun createDriver(): SqlDriver {
return NativeSqliteDriver(Database.Schema, "firebase.db")
}
}

Now you need to create its instance in MainViewController.kt file available in composeApp/src/iosMain/kotlin to make it work for iOS.

import androidx.compose.ui.window.ComposeUIViewController
import com.dwarshb.firebaseauthentication.DriverFactory

fun MainViewController() = ComposeUIViewController {
val driverFactory = DriverFactory()
App(driverFactory.createDriver())
}

On Desktop, the SQLite driver implementation is the JdbcSqliteDriver class.
So, in composeApp/src/desktopMain/kotlin create the package and inside it create DriverFactory.kt class

package com.dwarshb.firebaseauthentication

import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
import java.io.File

actual class DriverFactory {
actual fun createDriver(): SqlDriver {
val databasePath = File(System.getProperty("java.io.tmpdir"), "firebase.db")
val driver: SqlDriver = JdbcSqliteDriver(url = "jdbc:sqlite:${databasePath.absolutePath}")
Database.Schema.create(driver)
return driver
}
}

Now you need to create its instance in main.kt file available in composeApp/src/desktopMain/kotlin to make it work for Desktop.

import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import com.dwarshb.firebaseauthentication.DriverFactory

fun main() = application {
Window(onCloseRequest = ::exitApplication, title = "FirebaseAuthentication") {
val driverFactory = DriverFactory()
App(driverFactory.createDriver())
}
}

Source link