chore(docs, design-system, ping-service): integrate SQLDelight with KMP, refine design-system components, and enhance logging
- Added a comprehensive guide for SQLDelight integration in Kotlin Multiplatform, covering setup for Android, iOS, desktop, and web platforms. - Introduced `DashboardCard` and `DenseButton` to the design system, focusing on enterprise-grade usability and visual consistency. - Enhanced `PingViewModel` with structured logging (`LogEntry`) functionality for better debugging and traceability across API calls. - Updated `AppTheme` with a refined color palette, typography, and shapes to align with enterprise UI standards. - Extended Koin integration and modularized database setup for smoother dependency injection and code reuse.
This commit is contained in:
@@ -0,0 +1,524 @@
|
||||
# SQLDelight Integration in Compose Multiplatform
|
||||
|
||||
This guide shows how to integrate SQLDelight in a Compose Multiplatform project with Koin dependency injection.
|
||||
|
||||
## Step 1: Add Dependencies
|
||||
|
||||
Add below dependencies In `gradle/libs.versions.toml`:
|
||||
|
||||
```toml
|
||||
[versions]
|
||||
sqldelight = "2.0.1"
|
||||
koin = "3.5.3"
|
||||
|
||||
[libraries]
|
||||
sqldelight-driver-sqlite = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" }
|
||||
sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
|
||||
sqldelight-driver-native = { module = "app.cash.sqldelight:native-driver", version.ref = "sqldelight" }
|
||||
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
|
||||
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
|
||||
koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" }
|
||||
|
||||
[plugins]
|
||||
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
|
||||
```
|
||||
|
||||
In `build.gradle.kts` (project level):
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
alias(libs.plugins.sqldelight) apply false
|
||||
}
|
||||
```
|
||||
|
||||
In `shared/build.gradle.kts`:
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
alias(libs.plugins.sqldelight)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(libs.koin.core)
|
||||
implementation(libs.sqldelight.driver.sqlite)
|
||||
}
|
||||
|
||||
androidMain.dependencies {
|
||||
implementation(libs.koin.android)
|
||||
implementation(libs.sqldelight.driver.android)
|
||||
}
|
||||
|
||||
iosMain.dependencies {
|
||||
implementation(libs.sqldelight.driver.native)
|
||||
}
|
||||
|
||||
desktopMain.dependencies {
|
||||
implementation(libs.sqldelight.driver.sqlite)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sqldelight {
|
||||
databases {
|
||||
create("AppDatabase") {
|
||||
packageName.set("com.example.database")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
##Step 2: Create SQL Schema
|
||||
|
||||
**Create directory structure:**
|
||||
|
||||
`shared/src/commonMain/sqldelight/com/example/database/`
|
||||
|
||||
Create `User.sq` file:
|
||||
|
||||
```sql
|
||||
CREATE TABLE User
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
imageUrl TEXT
|
||||
);
|
||||
|
||||
-- Insert a new user
|
||||
insertUser
|
||||
:
|
||||
INSERT INTO User(name, imageUrl)
|
||||
VALUES (?, ?);
|
||||
|
||||
-- Get all users
|
||||
getAllUsers
|
||||
:
|
||||
SELECT *
|
||||
FROM User;
|
||||
|
||||
-- Get user by ID
|
||||
getUserById
|
||||
:
|
||||
SELECT *
|
||||
FROM User
|
||||
WHERE id = ?;
|
||||
|
||||
-- Update user
|
||||
updateUser
|
||||
:
|
||||
UPDATE User
|
||||
SET name = ?,
|
||||
imageUrl = ?
|
||||
WHERE id = ?;
|
||||
|
||||
-- Delete user
|
||||
deleteUser
|
||||
:
|
||||
DELETE
|
||||
FROM User
|
||||
WHERE id = ?;
|
||||
|
||||
-- Delete all users
|
||||
deleteAllUsers
|
||||
:
|
||||
DELETE
|
||||
FROM User;
|
||||
```
|
||||
|
||||
## Step 3: Create Database Driver Interface
|
||||
|
||||
In `shared/src/commonMain/kotlin/database/DatabaseDriverFactory.kt`:
|
||||
|
||||
```kotlin
|
||||
package com.example.database
|
||||
|
||||
import app.cash.sqldelight.db.SqlDriver
|
||||
|
||||
expect class DatabaseDriverFactory {
|
||||
fun createDriver(): SqlDriver
|
||||
}
|
||||
```
|
||||
|
||||
## Step 4: Platform-Specific Implementations
|
||||
|
||||
### Android —
|
||||
|
||||
`shared/src/androidMain/kotlin/database/DatabaseDriverFactory.android.kt`:
|
||||
|
||||
```kotlin
|
||||
package com.example.database
|
||||
|
||||
import android.content.Context
|
||||
import app.cash.sqldelight.db.SqlDriver
|
||||
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
|
||||
|
||||
actual class DatabaseDriverFactory(private val context: Context) {
|
||||
actual fun createDriver(): SqlDriver {
|
||||
return AndroidSqliteDriver(
|
||||
schema = AppDatabase.Schema,
|
||||
context = context,
|
||||
name = "app.db"
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### iOS —
|
||||
|
||||
`shared/src/iosMain/kotlin/database/DatabaseDriverFactory.ios.kt`:
|
||||
|
||||
```kotlin
|
||||
package com.example.database
|
||||
|
||||
import app.cash.sqldelight.db.SqlDriver
|
||||
import app.cash.sqldelight.driver.native.NativeSqliteDriver
|
||||
|
||||
actual class DatabaseDriverFactory {
|
||||
actual fun createDriver(): SqlDriver {
|
||||
return NativeSqliteDriver(
|
||||
schema = AppDatabase.Schema,
|
||||
name = "app.db"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Desktop —
|
||||
|
||||
`shared/src/desktopMain/kotlin/database/DatabaseDriverFactory.desktop.kt`:
|
||||
|
||||
```kotlin
|
||||
package com.example.database
|
||||
|
||||
import app.cash.sqldelight.db.SqlDriver
|
||||
import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
|
||||
|
||||
actual class DatabaseDriverFactory {
|
||||
actual fun createDriver(): SqlDriver {
|
||||
val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
|
||||
AppDatabase.Schema.create(driver)
|
||||
return driver
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Step 5: Create Repository
|
||||
|
||||
In `shared/src/commonMain/kotlin/repository/UserRepository.kt`:
|
||||
|
||||
```kotlin
|
||||
package com.example.repository
|
||||
|
||||
import com.example.database.AppDatabase
|
||||
import com.example.database.User
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.IO
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class UserRepository(private val database: AppDatabase) {
|
||||
|
||||
private val queries = database.userQueries
|
||||
|
||||
suspend fun insertUser(name: String, imageUrl: String?) = withContext(Dispatchers.IO) {
|
||||
queries.insertUser(name, imageUrl)
|
||||
}
|
||||
|
||||
suspend fun getAllUsers(): List<User> = withContext(Dispatchers.IO) {
|
||||
queries.getAllUsers().executeAsList()
|
||||
}
|
||||
|
||||
suspend fun getUserById(id: Long): User? = withContext(Dispatchers.IO) {
|
||||
queries.getUserById(id).executeAsOneOrNull()
|
||||
}
|
||||
|
||||
suspend fun updateUser(id: Long, name: String, imageUrl: String?) = withContext(Dispatchers.IO) {
|
||||
queries.updateUser(name, imageUrl, id)
|
||||
}
|
||||
|
||||
suspend fun deleteUser(id: Long) = withContext(Dispatchers.IO) {
|
||||
queries.deleteUser(id)
|
||||
}
|
||||
|
||||
suspend fun deleteAllUsers() = withContext(Dispatchers.IO) {
|
||||
queries.deleteAllUsers()
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Step 6: Setup Koin Modules
|
||||
|
||||
In `shared/src/commonMain/kotlin/di/DatabaseModule.kt`:
|
||||
|
||||
```kotlin
|
||||
package com.example.di
|
||||
|
||||
import com.example.database.AppDatabase
|
||||
import com.example.database.DatabaseDriverFactory
|
||||
import com.example.repository.UserRepository
|
||||
import org.koin.dsl.module
|
||||
|
||||
val databaseModule = module {
|
||||
single { DatabaseDriverFactory() }
|
||||
single { AppDatabase(get<DatabaseDriverFactory>().createDriver()) }
|
||||
single { UserRepository(get()) }
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Platform-specific modules
|
||||
|
||||
### Android —
|
||||
|
||||
`shared/src/androidMain/kotlin/di/PlatformModule.android.kt`:
|
||||
|
||||
```kotlin
|
||||
package com.example.di
|
||||
|
||||
import com.example.database.DatabaseDriverFactory
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.dsl.module
|
||||
|
||||
actual val platformModule = module {
|
||||
single { DatabaseDriverFactory(androidContext()) }
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### iOS —
|
||||
|
||||
`shared/src/iosMain/kotlin/di/PlatformModule.ios.kt`:
|
||||
|
||||
```kotlin
|
||||
package com.example.di
|
||||
|
||||
import com.example.database.DatabaseDriverFactory
|
||||
import org.koin.dsl.module
|
||||
|
||||
actual val platformModule = module {
|
||||
single { DatabaseDriverFactory() }
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Desktop —
|
||||
|
||||
`shared/src/desktopMain/kotlin/di/PlatformModule.desktop.kt`:
|
||||
|
||||
```kotlin
|
||||
package com.example.di
|
||||
|
||||
import com.example.database.DatabaseDriverFactory
|
||||
import org.koin.dsl.module
|
||||
|
||||
actual val platformModule = module {
|
||||
single { DatabaseDriverFactory() }
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Common module declaration —
|
||||
|
||||
`shared/src/commonMain/kotlin/di/PlatformModule.kt`:
|
||||
|
||||
```kotlin
|
||||
package com.example.di
|
||||
|
||||
import org.koin.core.module.Module
|
||||
|
||||
expect val platformModule: Module
|
||||
|
||||
```
|
||||
|
||||
## Step 7: Initialize Koin
|
||||
|
||||
In `shared/src/commonMain/kotlin/di/KoinInit.kt`:
|
||||
|
||||
```kotlin
|
||||
package com.example.di
|
||||
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.dsl.KoinAppDeclaration
|
||||
|
||||
fun initKoin(appDeclaration: KoinAppDeclaration = {}) = startKoin {
|
||||
appDeclaration()
|
||||
modules(
|
||||
platformModule,
|
||||
databaseModule
|
||||
)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Step 8: Platform Initialization
|
||||
|
||||
### Android —
|
||||
|
||||
In `MainActivity.kt`:
|
||||
|
||||
```kotlin
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
initKoin {
|
||||
androidContext(this@MainActivity)
|
||||
}
|
||||
|
||||
setContent {
|
||||
App()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### iOS —
|
||||
|
||||
In `iosApp/iosApp/iOSApp.swift`:
|
||||
|
||||
```kotlin
|
||||
import SwiftUI
|
||||
import shared
|
||||
|
||||
@main
|
||||
struct iOSApp : App {
|
||||
|
||||
init() {
|
||||
KoinInitKt.doInitKoin()
|
||||
}
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Desktop —
|
||||
|
||||
In `desktopApp/src/jvmMain/kotlin/main.kt`:
|
||||
|
||||
```kotlin
|
||||
fun main() {
|
||||
initKoin()
|
||||
|
||||
application {
|
||||
Window(onCloseRequest = ::exitApplication) {
|
||||
App()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Step 9: Use in Compose
|
||||
|
||||
### Create VieModel —
|
||||
|
||||
In `shared/src/commonMain/kotlin/viewmodel/UserViewModel.kt`:
|
||||
|
||||
```kotlin
|
||||
package com.example.viewmodel
|
||||
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.example.database.User
|
||||
import com.example.repository.UserRepository
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class UserViewModel(private val userRepository: UserRepository) : ViewModel() {
|
||||
|
||||
var users by mutableStateOf<List<User>>(emptyList())
|
||||
private set
|
||||
|
||||
var isLoading by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
init {
|
||||
loadUsers()
|
||||
}
|
||||
|
||||
fun loadUsers() {
|
||||
viewModelScope.launch {
|
||||
isLoading = true
|
||||
users = userRepository.getAllUsers()
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
fun addUser(name: String, imageUrl: String?) {
|
||||
viewModelScope.launch {
|
||||
userRepository.insertUser(name, imageUrl)
|
||||
loadUsers()
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteUser(id: Long) {
|
||||
viewModelScope.launch {
|
||||
userRepository.deleteUser(id)
|
||||
loadUsers()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Use in Compose Screen:
|
||||
|
||||
```kotlin
|
||||
@Composable
|
||||
fun UserScreen() {
|
||||
val userViewModel: UserViewModel = koinInject()
|
||||
|
||||
LazyColumn {
|
||||
items(userViewModel.users) { user ->
|
||||
UserItem(
|
||||
user = user,
|
||||
onDelete = { userViewModel.deleteUser(user.id) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UserItem(user: User, onDelete: () -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = user.name,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
Button(onClick = onDelete) {
|
||||
Text("Delete")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### That’s It!
|
||||
|
||||
You now have SQLDelight fully integrated in your Compose Multiplatform project with:
|
||||
|
||||
- Database working on Android, iOS, and Desktop
|
||||
- Koin dependency injection setup
|
||||
- Repository pattern for clean architecture
|
||||
- Ready-to-use User table with CRUD operations
|
||||
|
||||
The database will automatically handle platform-specific implementations while sharing the same business logic across
|
||||
all platforms.
|
||||
@@ -0,0 +1,167 @@
|
||||
# Architekturstrategien für Asynchrone Persistenz in Kotlin Multiplatform: Eine umfassende Analyse zur Integration von SQLDelight in Web-Umgebungen
|
||||
|
||||
## 1. Einleitung und Problemstellung
|
||||
|
||||
Die Entwicklung plattformübergreifender Anwendungen mittels Kotlin Multiplatform (KMP) hat in den letzten Jahren einen paradigmatischen Wandel vollzogen. Ein zentraler Bestandteil dieser Architektur ist die Datenpersistenz, für die sich SQLDelight als Industriestandard etabliert hat.
|
||||
|
||||
Die Integration der Web-Plattform stellt jedoch eine signifikante architektonische Herausforderung dar. Wie in der Problemstellung korrekt identifiziert, existiert eine fundamentale Diskrepanz zwischen den synchronen I/O-Operationen nativer Plattformen (Android, iOS) und der zwingend asynchronen Natur des Webs. Während native SQLite-Treiber (`AndroidSqliteDriver`, `NativeSqliteDriver`) Datenbankoperationen blockierend ausführen können, erfordert der Browser die Nutzung eines `WebWorkerDriver` und asynchrone Initialisierungsmuster.
|
||||
|
||||
Dieser Bericht liefert eine Lösungsarchitektur basierend auf dem "Lazy Async Wrapper"-Muster und Koin.
|
||||
|
||||
---
|
||||
|
||||
## 2. Theoretisches Fundament: Die Asynchronitäts-Lücke
|
||||
|
||||
### 2.1 Native vs. Web-Laufzeitumgebungen
|
||||
|
||||
Auf nativen Systemen kann der `SqlDriver` synchron instanziiert werden. Im Browser hingegen nutzt SQLDelight `sql.js` oder `sqlite-wasm` in einem Web Worker. Die Kommunikation erfolgt über Message Passing, was `suspend`-Funktionen für die Initialisierung erzwingt.
|
||||
|
||||
### 2.2 Der Paradigmenwechsel mit SQLDelight 2.0
|
||||
|
||||
Mit Version 2.0 wurde die Konfiguration `generateAsync` eingeführt:kotlin sqldelight { databases { create("AppDatabase") { packageName.set("com.example.db") generateAsync.set(true) } } }
|
||||
|
||||
Setzt man dieses Flag auf `true`, werden alle Datenbankoperationen als `suspend`-Funktionen generiert.[1, 4] Dies ist der erste Schritt zur Vereinheitlichung: Auch native Plattformen nutzen nun (formal) asynchrone Schnittstellen, was den gemeinsamen Code homogenisiert.
|
||||
|
||||
---
|
||||
|
||||
## 3. Die Lösungsarchitektur: Das "Lazy Async Wrapper"-Muster
|
||||
|
||||
Anstatt die Datenbank direkt beim App-Start zu initialisieren (was im Web blockieren oder fehlschlagen würde, wenn der Worker noch nicht bereit ist), kapseln wir den Treiber in einer Wrapper-Klasse.[5, 2]
|
||||
|
||||
### 3.1 Definition der Factory
|
||||
|
||||
**Datei:** `shared/src/commonMain/kotlin/.../DatabaseDriverFactory.kt`
|
||||
|
||||
```kotlin
|
||||
import app.cash.sqldelight.db.SqlDriver
|
||||
|
||||
interface DatabaseDriverFactory {
|
||||
suspend fun createDriver(): SqlDriver
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 3.2 Der Database Wrapper
|
||||
|
||||
Diese Komponente löst das Problem des Nutzers, indem sie die Initialisierung bis zum ersten Zugriff verzögert und mittels `Mutex` absichert.
|
||||
|
||||
**Datei:** `shared/src/commonMain/kotlin/.../DatabaseWrapper.kt`
|
||||
|
||||
```kotlin
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
class DatabaseWrapper(private val driverFactory: DatabaseDriverFactory) {
|
||||
private var _database: AppDatabase? = null
|
||||
private val mutex = Mutex()
|
||||
|
||||
suspend fun get(): AppDatabase {
|
||||
_database?.let { return it }
|
||||
return mutex.withLock {
|
||||
_database?: AppDatabase(driverFactory.createDriver()).also { _database = it }
|
||||
}
|
||||
}
|
||||
|
||||
// Helper für Repositories
|
||||
suspend operator fun <R> invoke(block: suspend (AppDatabase) -> R): R {
|
||||
return block(get())
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Implementierung der Plattform-Treiber
|
||||
|
||||
### 4.1 Web (Kotlin/Wasm & JS)
|
||||
|
||||
Hier liegt der Kern der Lösung: Wir warten explizit auf die Schema-Erstellung (`awaitCreate`), bevor wir den Treiber zurückgeben.
|
||||
|
||||
**Datei:** `shared/src/jsMain/kotlin/.../WebDatabaseDriverFactory.kt`
|
||||
|
||||
```kotlin
|
||||
import app.cash.sqldelight.driver.worker.WebWorkerDriver
|
||||
import org.w3c.dom.Worker
|
||||
|
||||
class WebDatabaseDriverFactory : DatabaseDriverFactory {
|
||||
override suspend fun createDriver(): SqlDriver {
|
||||
val worker = Worker(
|
||||
js("""new URL("@cashapp/sqldelight-sqljs-worker/sqljs.worker.js", import.meta.url)""")
|
||||
)
|
||||
val driver = WebWorkerDriver(worker)
|
||||
|
||||
// WICHTIG: Hier wird asynchron gewartet!
|
||||
AppDatabase.Schema.create(driver).await()
|
||||
return driver
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
**Webpack Konfiguration:**
|
||||
Damit dies funktioniert, muss die `sql-wasm.wasm` Datei korrekt kopiert werden.
|
||||
|
||||
```javascript
|
||||
// webpack.config.d/sqljs.js
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
config.plugins.push(
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
'../../node_modules/sql.js/dist/sql-wasm.wasm'
|
||||
]
|
||||
})
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
### 4.2 Android (Synchron)
|
||||
|
||||
Für Android geben wir den synchronen Treiber einfach in der `suspend`-Funktion zurück.
|
||||
|
||||
```kotlin
|
||||
class AndroidDatabaseDriverFactory(private val context: Context) : DatabaseDriverFactory {
|
||||
override suspend fun createDriver(): SqlDriver {
|
||||
return AndroidSqliteDriver(AppDatabase.Schema, context, "app.db")
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Integration mit Koin
|
||||
|
||||
Da der `DatabaseWrapper` selbst leichtgewichtig ist (er erstellt die DB noch nicht im Konstruktor), kann er problemlos als `single` in Koin registriert werden.
|
||||
|
||||
```kotlin
|
||||
val appModule = module {
|
||||
single { DatabaseWrapper(get()) }
|
||||
single { MyRepository(get()) }
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Das Repository nutzt dann den Wrapper:
|
||||
|
||||
```kotlin
|
||||
class MyRepository(private val dbWrapper: DatabaseWrapper) {
|
||||
suspend fun getItems() = dbWrapper { db ->
|
||||
db.itemQueries.selectAll().executeAsList()
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 6. Zusammenfassung
|
||||
|
||||
Diese Architektur löst den Konflikt zwischen synchronen und asynchronen Welten durch:
|
||||
|
||||
1. **`generateAsync = true`**: Erzwingt `suspend` überall.
|
||||
|
||||
|
||||
2. **Wrapper Pattern**: Kapselt die asynchrone Initialisierung (`await()`) im Web.
|
||||
|
||||
|
||||
3. **Koin Singleton**: Der Wrapper kann sofort injiziert werden, die DB wird erst beim ersten `invoke` geladen.
|
||||
|
||||
Reference in New Issue
Block a user