androiddependency-injectiondatastorekoincompose-multiplatform

Dependency Injection for DataStore with Koin


I have Compose multiplatform project that implement datastore. I use multiplatform datastore as reference. I have problem when injecting the data store.

commonMain :

fun getDataStore(producePath: () -> String): DataStore<Preferences> =
    synchronized(lock) {
        if (::dataStore.isInitialized) {
            dataStore
        } else {
            PreferenceDataStoreFactory.createWithPath(produceFile = { producePath().toPath() })
                .also { dataStore = it }
        }
    }

internal const val dataStoreFileName = "app.preferences_pb"

androidMain :

fun getDataStore(context: Context): DataStore<Preferences> = getDataStore(
    producePath = { context.filesDir.resolve(dataStoreFileName).absolutePath }
)

iOSMain :

@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
fun createDataStore(): DataStore<Preferences> = getDataStore(
    producePath = {
        val documentDirectory: NSURL? = NSFileManager.defaultManager.URLForDirectory(
            directory = NSDocumentDirectory,
            inDomain = NSUserDomainMask,
            appropriateForURL = null,
            create = false,
            error = null,
        )
        requireNotNull(documentDirectory).path + "/$dataStoreFileName"
    }
)

and I have this class that access dataStore

class GameskiiSettingRepository(
    private val dataStore: DataStore<Preferences>
) 

I don't know how to inject that dataStore and I keep getting this error

Caused by: org.koin.core.error.NoBeanDefFoundException: No definition 
found for type 'androidx.datastore.core.DataStore'. Check your Modules
configuration and add missing type and/or qualifier!

Solution

  • I solved this issue by declaring an expect actual koin module that instantiates the datastore. Your getDataStore signature is a little different but this should work for you as well.

    Declare a preferenceModule in commonMain:

    // commonMain
    expect val preferenceModule: Module
    

    Define the actual implementations for preferenceModule in androidMain and iosMain:

    // androidMain
    actual val preferenceModule: Module = module {
        single { createDataStore(androidContext()) }
    }
    
    // iosMain
    actual val preferenceModule: Module = module {
    // Here you don't need to pass null
        single { createDataStore(null) }
    }
    

    Add this module to the Koin initializer for both Android and iOS:

    // androidMain
    actual class KoinInitializer(
        private val context: Context,
    ) {
        actual fun init() {
            startKoin {
                androidContext(context)
                androidLogger()
                modules(
                    appModule, viewModelModule, preferenceModule
                )
            }
        }
    }
    
    // iosMain
    actual class KoinInitializer {
        actual fun init() {
            startKoin {
                modules(appModule, viewModelModule, preferenceModule)
            }
        }
    }
    

    Finally, provide the GameskiiSettingRepository dependency like this:

    single { GameskiiSettingRepository(get()) }
    

    Now you can inject this Repo into Viewmodel.