Reputation: 1299
I want to inject Room db files into the koin in compose multiplatform but I get the runtime error from the injected Dao interface in the data source contractor. how can I inject it to the appModule This is my dataSource:
class LocalDataSourceImpl(private val noteDao: NoteDao) : LocalDataSource {
private val mutex = Mutex()
override suspend fun getAllNotes(categoryId: Int?): List<Note> = mutex.withLock {
return if (categoryId == 1) noteDao.getAllNotes().toModel() else noteDao.getNotes(
categoryId
).first().toModel()
}
override suspend fun upsertNote(note: NoteEntity) = mutex.withLock {
noteDao.upsertNote(note)
}
override suspend fun deleteNote(noteId: String): Int = mutex.withLock {
noteDao.deleteById(noteId)
}
override suspend fun completeNote(noteId: String) = mutex.withLock {
noteDao.updateCompleted(noteId = noteId, completed = true)
}
override suspend fun activateNote(noteId: String) = mutex.withLock {
noteDao.updateCompleted(noteId = noteId, completed = false)
}
override suspend fun getNoteById(noteId: String): NoteEntity? = mutex.withLock {
return noteDao.getNoteById(noteId = noteId).first()
}
override suspend fun clearCompletedNotes(): Int = mutex.withLock {
noteDao.deleteCompleted()
}
}
The koin module
fun appModule() = module {
factory { SavedStateHandle() }
single<LocalDataSource> {
LocalDataSourceImpl(get())
}
single<NotesRepository> {
NotesRepositoryImpl(get(), get())
}
}
and commonMain module:
@Composable
internal fun App() {
KoinApplication(application = {
modules(appModule())
}) {
AppTheme {
App()
}
}
}
I created an expect class and created actual classes:
expect class Factory {
fun createRoomDatabase(): AppDatabase
}
actual class Factory(private val app: Application) {
actual fun createRoomDatabase(): AppDatabase {
val dbFile = app.getDatabasePath(dbFileName)
return Room.databaseBuilder<AppDatabase>(app, dbFile.absolutePath)
.setDriver(BundledSQLiteDriver())
.setQueryCoroutineContext(Dispatchers.IO)
.build()
}
}
actual class Factory {
actual fun createRoomDatabase(): AppDatabase {
val dbFile = "${fileDirectory()}/$dbFileName"
return Room.databaseBuilder<AppDatabase>(
name = dbFile,
factory = { AppDatabase::class.instantiateImpl() }
).setDriver(BundledSQLiteDriver())
.setQueryCoroutineContext(Dispatchers.IO)
.build()
}
@OptIn(ExperimentalForeignApi::class)
private fun fileDirectory(): String {
val documentDirectory: NSURL? = NSFileManager.defaultManager.URLForDirectory(
directory = NSDocumentDirectory,
inDomain = NSUserDomainMask,
appropriateForURL = null,
create = false,
error = null,
)
return requireNotNull(documentDirectory).path!!
}
}
where should I call the createRoomDatabase() function? and why I get this error: Caused by: org.koin.core.error.NoBeanDefFoundException: No definition found for type 'database.NoteDao'. Check your Modules configuration and add missing type and/or qualifier!
how can I fix them?
Upvotes: 1
Views: 1030
Reputation: 11
In your database abstract class you should define a getRoomDatabase function. This function should be marked with expect, as you will implement it both on iOS and Android.
expect fun getRoomDatabase(): MyDatabase
Then you should implement it. On iOS you shouldn;t have any issue, but on android you will need context. I retrieved it from the application, you can create a lateinit var
on your application and initialize it on onCreate
, then return it on a function inside a companion object.
Finally, to use it on your module you call the getRoomDatabase()
function inside of a single.
Upvotes: 0
Reputation: 1
You don't specified the dao so let's work with an example
First let's declare the Dao:
@Dao
interface NoteDao {
@Insert
suspend fun insert(item: NoteEntity)
@Query("SELECT * FROM NoteEntity")
fun getAllEntityAsFlow(): Flow<List<NoteEntity>>
@Query("DELETE FROM NoteEntity WHERE id = :id")
suspend fun deleteById(id: Int)
}
Then we need to declare AppDatabase:
@Database(entities = [NoteEntity::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun getDao(): NoteDao
}
In the SharedModules we inject Dao module like this:
private val databaseModule = module {
single { get<AppDatabase>().getDao() }
}
This is the answer that how can we inject room database into koin. But in my case this isn't enough to use room database. If you are using mvvm structure you need to give databaseBuilder to repository implementation like this:
class NoteRepositoryImpl(
private val appDatabase: AppDatabase,
) : NoteRepository {
private val dao = appDatabase.getDao()
//Insert, Get and Delete will be here
}
In shared modules we need to give appDatabase to RepositoryImpl. I used function to make so:
private fun getDbBuilder(databaseBuilder: RoomDatabase.Builder<AppDatabase>): Module {
return module {
single {
getRoomDatabase(databaseBuilder)
}
single<NoteRepository> {
NoteRepositoryImpl(get())
}
single { NoteViewModel() }
}
}
fun getSharedModules(databaseBuilder: RoomDatabase.Builder<AppDatabase>) =
getAllModules(databaseBuilder)
At last we need to start the koin in MainActivity.kt (Andoroid):
startKoin {
androidContext(INSTANCE)
modules(getSharedModules(databaseBuilder))
}
Note: 1- I didn't check if androidContext() effect something. 2- Currently i don't have any ios device to test the code.
I hope this helped you.
Upvotes: -1
Reputation: 4185
It looks like you only have Koin in your Android app?
You can add Koin core to your commonMain
dependencies in the shared module then define a Koin module for platform specific use such as:
In commonMain
:
expect val platformModule: Module
In androidMain
, iosMain
, etc:
actual val platformModule = module {
...
}
Add Koin dependencies as normal and then include the module in your Android app module.
But, by using expect/actual approach for your Room builder etc, you'll have to define at least some dummy actual implementations for other platforms.
I think there are better options, such as:
commonMain
and it'll work on other platformsUpvotes: 0