SQLDelight in Kotlin Multiplatform Mobile (KMM)

Karim Reda
arconsis
Published in
6 min readJul 31, 2023

In Kotlin Multiplatform Mobile (KMM) development, one of the key challenges is managing data persistence across multiple platforms efficiently. To address this, developers can leverage SQLDelight, a powerful database library that integrates seamlessly with KMM projects. SQLDelight provides a type-safe SQL query and schema generation, making it easier to work with databases in a multiplatform environment. In this article, we will explore the process of implementing SQLDelight into a KMM project and demonstrate its capabilities for data persistence.

What is SQLDelight?

SQLDelight is an open-source library developed by Square that enables type-safe SQL queries and schema generation for Kotlin. It provides a seamless way to work with databases, making it ideal for KMM projects where developers need to share data persistence logic across iOS and Android platforms.

Advantages of SQLDelight in KMM

  1. Cross-platform compatibility: SQLDelight supports both iOS and Android platforms, allowing developers to write database-related code once and share it across platforms. This eliminates the need for separate database implementations and ensures consistency in data persistence.
  2. Type-safe queries: SQLDelight provides a compile-time query verification system. It uses a DSL (Domain-Specific Language) approach that allows you to write SQL queries in Kotlin code and ensures that queries are checked for syntax errors and type mismatches during compilation.
  3. Schema generation: SQLDelight automatically generates Kotlin code based on your database schema, including table definitions, column names, and query functions. This eliminates the need to write boilerplate code manually and reduces the chances of introducing errors.

Implementing SQLDelight into a KMM Project

To implement SQLDelight into your KMM project, follow these steps:

Step 1: Set up your KMM project

Create a new KMM project using the instructions provided by JetBrains. Ensure that you have the necessary project structure and configuration for both iOS and Android platforms.

If you’re unfamiliar with the process of creating a KMM project, we encourage you to refer to our article on getting started with Kotlin Multiplatform Mobile (KMM). It provides detailed guidance on the topic.

Step 2: Add SQLDelight to your project dependencies

In your KMM project, add the SQLDelight Gradle plugin and dependency to your shared module’s build.gradle file.

plugins {
id("app.cash.sqldelight") version "2.0.0-alpha05"
}

repositories {
google()
mavenCentral()
}

sqldelight {
databases {
create("Database") {
packageName.set("com.example")
}
}
}

Also, add the appropriate SQLDelight dependency to your iOS and Android-specific modules.

kotlin {
// The drivers needed will change depending on what platforms you target:

sourceSets.androidMain.dependencies {
implementation("app.cash.sqldelight:android-driver:2.0.0-alpha05")
}

sourceSets.iosMain.dependencies {
implementation("app.cash.sqldelight:native-driver:2.0.0-alpha05")
}
}

Step 3: Platform-specific configuration

For each platform (iOS and Android), you need to configure the database driver and provide platform-specific implementations of the database-related functions. This includes initializing the database, handling transactions, and accessing platform-specific APIs if needed.

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

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

// Do more work with the database (see below).
}

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

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

Step 4: Define your database schema

Create a .sq file in your shared module and define your database schema using SQL syntax. Specify the tables, columns, and any necessary constraints for your database.

CREATE TABLE hockeyPlayer (
player_number INTEGER NOT NULL,
full_name TEXT NOT NULL
);

CREATE INDEX hockeyPlayer_full_name ON hockeyPlayer(full_name);

INSERT INTO hockeyPlayer (player_number, full_name)
VALUES (15, 'Ryan Getzlaf');

SQL statements inside a .sq file can be labeled to have a typesafe function generated for them available at runtime.

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

Step 5: Generate Kotlin code

Run the SQLDelight Gradle task to generate Kotlin code based on your schema file. This will generate Kotlin classes representing your tables, columns, and queries. These generated classes will be available in your shared module.

Step 6: Access the database in the shared code

In your shared Kotlin code, you can now create instances of the generated database class and execute type-safe queries using the provided functions. This allows you to perform CRUD (Create, Read, Update, Delete) operations on your database without worrying about SQL syntax or type safety.

val database = Database(driver)

val playerQueries: PlayerQueries = database.playerQueries
playerQueries.insert(player_number = 10, full_name = "Corey Perry")

Using a dependency injection framework like Koin is strongly advised in order to establish a singular instance of the database and its driver. By employing this approach, you can conveniently inject them into your code whenever necessary.

If you’re not acquainted with the usage of a dependency injection framework, we recommend checking out our article on dependency injection with Koin in Kotlin Multiplatform Mobile (KMM). This resource offers comprehensive guidance specifically on this subject.

Step 7: Create a Database Instance Using Koin

Creating your database instance just once and injecting it into your code when necessary is essential for the app’s performance. You can do this using a dependency injection framework like Koin.

// in src/commonMain/kotlin
fun initKoinAndroid(additionalModules: List<Module>) {
startKoin {
modules(additionalModules + getBaseModules())
}
}

fun initKoiniOS(appConfig: AppConfig) {
initKoin(listOf(module { single { appConfig } }))
}

internal fun getBaseModules() = appModule + platformModule

// in src/androidMain/kotlin
actual val platformModule: Module = module {
single<SqlDriver> {
AndroidSqliteDriver(
schema = MeetingsDatabase.Schema,
context = get(),
name = "meetings.db"
)
}
}

// in androidApp/src/main/java/…/app
class App : Application() {
override fun onCreate() {
super.onCreate()
initKoinAndroid(
listOf(
module {
single<Context> { this@App }
}
)
)
}
}

// in src/iosMain/kotlin
actual val platformModule: Module = module {
single<SqlDriver> {
NativeSqliteDriver(MeetingsDatabase.Schema, "meetings.db")
}
}

// in iosApp/…/app
@main
struct MeetingsApp: App {
init() {
SetUpKoin.setupNativeCore()
}
}

public enum SetUpKoin {
public static func setupNativeCore() {
KoinKt.doInitKoiniOS(appConfig: config)
}
}

Step 8: Testing and deployment

With SQLDelight, you can write tests for your shared database logic using the generated Kotlin classes. These tests can be executed on both iOS and Android platforms. Once your database implementation is tested and ready, you can build and deploy your KMM application to both platforms.

Best Practices for SQLDelight in KMM

  1. Keep database-related logic in the shared module: To maximize code reuse, keep your database-related code in the shared module as much as possible. This includes table definitions, queries, and database access functions.
  2. Leverage platform-specific configuration: Although SQLDelight provides a cross-platform solution, there may be cases where you need to access platform-specific APIs or customize database behavior for a specific platform. Utilize the platform-specific configuration to handle such cases.
  3. Regularly update your schema: As your database requirements evolve, update your schema file accordingly and regenerate the Kotlin code. This ensures that your database-related code stays up to date and reflects the latest schema changes.
  4. Map Database Data to Custom Data Classes: To enhance flexibility when handling data obtained from the database, it is highly advisable to convert the auto-generated entities into personalized data classes. These data classes can then be employed for transferring data between different layers of the application.

Conclusion

SQLDelight is a powerful tool for implementing data persistence in Kotlin Multiplatform Mobile (KMM) projects. By providing type-safe SQL queries and automatic schema generation, SQLDelight simplifies database management and promotes code reuse across iOS and Android platforms. By following the steps outlined in this article and adhering to best practices, you can integrate SQLDelight into your KMM project and leverage its benefits for efficient data persistence.

--

--