0

I have the MVVM app build with KMM. ViewModel contains several use cases. Each use case calls methods of Repository and Repository calls NetworkService to execute the API call. Use cases, Repository and NetworkService are in the shared module. I need to store all cookies in shared preferences which I receive from the server. For this purpose I created my own cookies storage and installed it in this way:

private val httpClient = HttpClient {
        install(HttpCookies) {
            storage = CookiesStorage(di)
        }
    }

Here's the code of cookies storage:

class CookiesStorage(val di: DI) : CookiesStorage {

    private val cookiesStorageImpl = CookiesStorageImpl(di)

    override suspend fun addCookie(requestUrl: Url, cookie: Cookie) {
        cookiesStorageImpl.addCookie(requestUrl, cookie)
    }

    override fun close() {
    }

    override suspend fun get(requestUrl: Url) = cookiesStorageImpl.getCookies()
}

expect class CookiesStorageImpl(di: DI) {
    val di: DI
    fun addCookie(requestUrl: Url, cookie: Cookie)
    fun getCookies(): MutableList<Cookie>
}

As work with storing key-value data in iOS and Android is different, I added expect class CookiesStorageImpl. Android implementation of this class is now the following:

actual class CookiesStorageImpl actual constructor(actual val di: DI) {

    private val cookieMap = mutableMapOf<String, String>()

    private val context: Context by di.instance()

    actual fun addCookie(requestUrl: Url, cookie: Cookie) {
        println("Set cookie name=${cookie.name}, value=${cookie.value}")
        cookieMap[cookie.name] = cookie.value
        context.getSharedPreferences("kmm_preferences", Context.MODE_PRIVATE)
    }

    actual fun getCookies() = mutableListOf<Cookie>().apply {
        cookieMap.forEach {
            this.add(Cookie(it.key, it.value))
        }
    }

}

As you can see, I initialize the context with di here.

Here's the di graph in android app:

val appModule = DI.Module("app module") {
    import(viewModelModule)
    bind<Context>() with multiton { app: App ->
        app.applicationContext
    }
}

val viewModelModule = DI.Module("view model module") {
    import(useCaseModule)

    bind<ViewModelProvider.Factory>() with singleton {
        ViewModelFactory(instance())
    }
    bind<LoginViewModel>() with provider {
        LoginViewModel(instance())
    }
}

And here's the DI of shared module:

val useCaseModule = DI.Module("use case module") {
    bind<LoginUseCase>() with singleton {
        LoginUseCase(di)
    }
}

So, as you can see, I just pass di from use case to CookiesStorageImpl. But when I run the app I got the following error while accessing to context:

org.kodein.di.DI$NotFoundException: No binding found for bind<Context> { ? { ? } }

So, as I understand, the problem is that UseCase doesn't know anything about the context, but I cannot understand how can I pass the binding to the use case module. Thanks in advance for any help!

UPD

Here's the way how I add graph in the Application class:

class App : Application(), DIAware {

    override val di by DI.lazy {
        import(appModule)
    }

}
Sergei Mikhailovskii
  • 2,100
  • 2
  • 21
  • 43

1 Answers1

0

In fact, you are binding a multiton that take an App as parameter:

bind<Context>() with multiton { app: App ->
    app.applicationContext
}

So this means that you need to pass an App while injecting your Context, something like:

private val context: Context by di.instance(arg = app)

As I don't think you'll have multiple instances of App, thus only one multiton (so a Singleton), you could also have your app as part of your DI graph.

But in a KMM project, context can be tricky to handle. I suggest you to check out our video about Android contextes https://www.youtube.com/watch?v=yCLI_dIx660&t=137s

Also you can use a KMP library to handle local storage for you, like multiplatform-settings: https://github.com/russhwolf/multiplatform-settings

romainbsl
  • 534
  • 3
  • 10