Hello everyone, I am facing an when I am trying to...
# multiplatform
a
Hello everyone, I am facing an when I am trying to inject AndroidContext in my compose multiplatform app. Here's the setup: I have created
Koin.kt
class under
commonMain
package and declared a module like this
Copy code
val dataStoreModule: Module = module {
    // Provide the coroutine scope
    single { CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO> + SupervisorJob()) }

    // Provide the DataStore
    single {
        DataStoreProvider(
            coroutineScope = get(),
            migrations = listOf(),
            context = get()
        )
    }

    // Provide DataStore using DataStoreProvider
    single<DataStore<Preferences>> { get<DataStoreProvider>().provide() }
}
Now, under the
androidMain
in my Application class, I have done this
Copy code
class MyApp: Application() {
    override fun onCreate() {
        super.onCreate()
        initKoin()
    }

    private fun initKoin() {
        startKoin {
            androidContext(this@MyApp)
            modules(
                dataStoreModule,
                useCaseModule,
                viewModelModule
            )
        }
    }
}
But somehow, the context is not getting injected correctly. Any help appreciated.
g
what are the arguments of DataStoreProvider? I have it setup with Context, but maybe you have it as different class
Also it is a good idea to ask it in #C67HDJZ2N
s
In my project ,I initialize the necessary basic dependencies externally and pass them as parameters of startAppKoin function.
g
that's a bit confusing for me. It seems like you have 2 ways to initialize koin. One via initKoin from androidMain and then you also have koinApp. my guess is that, you are calling startKoin multiple times and one of those instances does not have Context in it
do you have this project public?
you know this issue is that one where the issue is sort of spread in multiple files...
s
Each target platform calls this function and passes the platform-specific base dependencies as parameters, but does not pass the platform context. For example, on Android, the data store depends on the context to be created, so I first use the context to create an instance of the data store and then pass it into koin.
g
Are you using VIewModels? what's your architecture? are you sharing same UI to other platforms?
I think I have an idea to fix it
and also make it a bit simpler
s
Yes
a
Yes, I am using VM and as @Sanlorng mentioned, Android depend upon the context but not the iOS. Here's the complete class including DataStorePreferences
Copy code
class DataStoreProvider(
    private val coroutineScope: CoroutineScope,
    private val migrations: List<DataMigration<Preferences>>,
    private val context: Any? = null
) {
    fun provide(): DataStore<Preferences> = dataStorePreferences(
        coroutineScope = coroutineScope,
        migrations = migrations,
        context = context
    )
}

expect fun dataStorePreferences(
    coroutineScope: CoroutineScope,
    migrations: List<DataMigration<Preferences>>,
    context: Any? = null,
): DataStore<Preferences>

internal fun createDataStore(
    coroutineScope: CoroutineScope = CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO> + SupervisorJob()),
    migrations: List<DataMigration<Preferences>>,
    context: Any? = null,
    path: (context: Any?) -> String,
) = PreferenceDataStoreFactory.createWithPath(
    scope = coroutineScope,
    migrations = migrations,
    produceFile = {
        path(context).toPath()
    }
)
and the actual implementation for Android is as follow:
Copy code
actual fun dataStorePreferences(
    coroutineScope: CoroutineScope,
    migrations: List<DataMigration<Preferences>>,
    context: Any?,
): DataStore<Preferences>  = createDataStore(
    coroutineScope = coroutineScope,
    migrations = migrations,
    path = { mCtx ->
        if(mCtx == null) {
            throw IllegalStateException("You must provide context for Android")
        }
        else
            File(appContext.filesDir, "datastore/$DATA_STORE_FILE_NAME").path
    }
)
and iOS doesn't need the context as I said above.
🚀 1
g
Here is my solution while also using Koin. First, startKoin should happen in commonCode. Here this video shows the setup

https://www.youtube.com/watch?v=5qc24AQ6ktc

ViewModel should not have koin as an argument, instead Koin should create ViewModel and pass it's dependencies. I dont understand the need to have
val koin
in BaseActivity. The correct way would be to extend KoinComponent, which has
getKoin()
which is pretty much same. So maybe you could remove it. for creating a DataStore, or any class that needs context on Android here how I usually do. first I create
expect val dataStoreModule: Module
, and implement this in Android and iOS source set accordingly. For example, for Android:
Copy code
actual val dataStoreModule: Module = module {
    single {
        DataStoreFactory.create(
            storage = OkioStorage<UserEntity>(
                fileSystem = FileSystem.SYSTEM,
                serializer = UserEntitySerializer,
                producePath = {
                    val appContext = get<Context>().applicationContext
                    val dataStoreFile = appContext.filesDir.resolve(dataStoreUserFileName)
                    dataStoreFile.absolutePath.toPath()
                },
            ),
        )
    }
}
and for iOS
Copy code
actual val dataStoreModule: Module = module {
    single {
        DataStoreFactory.create(
            storage = OkioStorage<UserEntity>(
                fileSystem = FileSystem.SYSTEM,
                serializer = UserEntitySerializer,
                producePath = {
                    val dataStoreFile = documentDirectory() + "/" + dataStoreUserFileName
                    dataStoreFile.toPath()
                },
            ),
        )
    }
}
They both result in returning same class so now whichever class needs it will have argument like
Copy code
class UserDataStore(
    @Named(IO)
    private val ioDispatcher: CoroutineDispatcher,
    private val userStore: DataStore<UserEntity>
) {
Also dont forget to include this
dataStoreModule
module in
startKoin
call.
s
Because if activity recreate ,the startKoin will cause a exception