I'm also having trouble upgrading to v2.1.0 in my ...
# koin
e
I'm also having trouble upgrading to v2.1.0 in my library. My lib is agnostic of the app using it. So my lib doesn't know if we already have a Koin instance, e.g. whether or not the app used
startKoin {}
. So basically I want to have a lib-private
KoinApplication
/
Koin
instance. How do I approach this? It used to work on v2.0.1.
If my host application does not use
startKoin {}
(in onCreate), I get an exception:
Copy code
java.lang.IllegalStateException: No Koin Context configured. Please use startKoin or koinApplication DSL.
However, I do use the
koinApplication
DSL in my lib
If my host application does use
startKoin {}
(in onCreate), I get an exception too:
Copy code
E  java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
                         E      at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:502)
                         E      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
                         E  Caused by: java.lang.reflect.InvocationTargetException
                         E      at java.lang.reflect.Method.invoke(Native Method)
                         E      at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
                         E      ... 1 more
                         E  Caused by: org.koin.core.error.NoBeanDefFoundException: No definition found for class:'com.example.MyViewModel'. Check your definitions!
That view model is injected using
by sharedViewModel()
and my conclusion is that somehow Koin looks in the application's Koin instance for the view model definition, which logically is not there. It should have used the lib's koin instance.
My fragment implements a custom
KoinComponent
interface that provide's the lib's
Koin
instance, yet, somehow this no longer works.
I've found the culprit...
That's a bug:
requireActivity()
suddenly changes the
LifecycleOwner
that this call runs on
The activity, in my case, is hosted by the application, not by my library
This change also changes the
Koin
instance that my fragment uses to obtain the shared view model
Maybe / probably that change was intended, but as a Koin user it is unclear that to use
by sharedViewModel()
the fragment's activity's must either be a
KoinComponent
(which isn't the case for me) or there must be a global Koin instance registered with
KoinContextHandler
(which also isn't necessarily the case, nor is that the instance where the view model is defined!).
So my solution is to enforce that activity to be a
KoinComponent
. This makes Koin leak from my library into its app clients....
Is there another way around this? Or is this a bug?
a
KoinContextHandler
is the new component that let us setup the
GlobalContext
You can’t access statically to GlobalContext anymore
this is one key for us to unlock Koin for Kotlin MP
but then, some changes for those our are working in isolated instances
KoinContextHandler.getOrNull()
can tell you if you have already a Koin instance
but using your own Koin instance, you should be dependant of GlobalContext
e
Thanks for the response! 🙂
I don't get why I would want to use
KoinContextHandler.getOrNull()
in my library, because in any case I don't want to know about or touch the app's Koin instance
a
how are you using Koin then?
loadKoinModules
or isolated
koinApplication
instance?
e
The issue is that Koin assumes in
Fragment.sharedViewModels()
that the activity uses the same koin instance. This is an incorrect assumption
I use an isolated
koinApplication
instance
a
ok
For your isolated instance, you have to use on overloaded
KoinComponent
interface
e
I have done that
It works now that I have made my activity also implement that interface
However, that activity is part of the public API of my library, so now that Koin-specific interface
KoinComponent
leaks into the public API of my lib
a
as isolated, you can’t rely directly on standards extensions ...
as by default, we are looking into the current GlobalContext
was it androix-viewmodel in 2.0.1 that you were using?
e
OK, this should be very explicitly and clearly documented then. It was unclear why somehow no Koin instance existed (default) or why no bean definition existed (if a global koin app existed)
androidx.lifecycle:lifecycle-common-java8:2.2.0
, probably transitively includes
viewmodel:2.2.0
Let me double check that for you
a
interesting 🤔
e
Yes, I depend on
androidx.lifecycle:lifecycle-viewmodel:2.2.0
transitively
a
and then? Sorry, didin’t follow the impact 😕
e
In Koin v2.0.1 here there used to be no
requireActivity()
call: https://github.com/InsertKoinIO/koin/blob/38c855c1f22f4f1d3afe8555ff7ef2e8dd87ad99/koin-projects/koin-androidx-viewmodel/src/main/java/org/koin/androidx/viewmodel/ext/android/FragmentExt.kt#L55 The impact was that
getKoin()
would be called on my fragment, which is internal in my lib. In Koin v2.1.0
getKoin()
is called on the activity, which is public in my lib and therefore should not be my custom
KoinComponent
implementation.
So what's not very obvious to me, as a user, that if I call
Fragment.sharedViewModel()
from my fragment that is a custom
KoinComponent
, that under water a
Koin
instance is obtained from the activity instead!
That's unexpected
So this crashes with an isolated Koin instance, because the acitvity is not a `MyKoinComponent`:
Copy code
class MyActivity : Activity() {
    // Hosts MyFragmentOne and MyFragmentTwo
}

internal class MyFragmentOne: Fragment(), MyKoinComponent {
    val vm: MyViewModel by sharedViewModel()
}

internal class MyFragmentTwo : Fragment(), MyKoinComponent {
    val vm: MyViewModel by sharedViewModel()
}
I'd expect that the
Fragment.sharedViewModel()
call would happen using the
MyKoinComponent
, but it does not. Koin assumes that the activity also is a
MyKoinComponent
or that the default global Koin application is the correct instance. Both assumptions are wrong.
Why is
requireActivity()
used in the first place in
Fragment.sharedViewModel()
? Why can't the
Koin
instance be obtained from the fragment itself? Suggestion: try to obtain a koin instance from the fragment, then from the activity, then try the global instance
a
that can explains yes
can make a patch to let you test @Erik 👍
e
BTW, my view model is shared between fragments hosted by the same activity. But the activity doesn't know about or use the view model; it doesn't even know about the isolated lib-internal Koin instance. I prefer to keep it that way
Awesome, thanks!
I will surely test it!
I hope it doesn't break other use cases.... 🙏
(for other people, I mean)
a
it has been part of the API cleaning ... it has badly growth
I hope it doesn’t break other use cases
that’s why I need people to help me test in beta also 🙂
@Erik try with
2.1.1-alpha-1
e
Testing now. Am I right that the diff between
2.1.0
and that version is
Copy code
diff --git a/org/koin/androidx/viewmodel/ext/android/FragmentExt.kt b/org/koin/androidx/viewmodel/ext/android/FragmentExt.kt
index 9db6749..475f198 100644
--- a/org/koin/androidx/viewmodel/ext/android/FragmentExt.kt
+++ b/org/koin/androidx/viewmodel/ext/android/FragmentExt.kt
@@ -16,7 +16,10 @@
 package org.koin.androidx.viewmodel.ext.android
 
 import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
 import androidx.lifecycle.ViewModel
+import org.koin.android.ext.android.getKoin
+import org.koin.androidx.viewmodel.koin.getViewModel
 import org.koin.core.parameter.ParametersDefinition
 import org.koin.core.qualifier.Qualifier
 import kotlin.reflect.KClass
@@ -52,7 +55,8 @@ fun <T : ViewModel> Fragment.getSharedViewModel(
     qualifier: Qualifier? = null,
     parameters: ParametersDefinition? = null
 ): T {
-    return requireActivity().getViewModel(
+    return getKoin().getViewModel(
+        this.activity as FragmentActivity,
         clazz,
         qualifier,
         parameters
Why do you get the
activity as FragmentActivity
? Why not
requireActivity()
here? That returns a
FragmentActivity
, not a
FragmentActivity?
.
This version works perfectly for my use case. The
by sharedViewModel()
call now uses the fragment to call
ComponentCallbacks.getKoin()
I still wonder about my previous comment's question. And I wonder if there is a use case where the fragment is not a
KoinComponent
, but the attached activity is. A user of
by sharedViewModel()
might assume that? If you think that's a valid use case, you could add a branch to the
when
expression in `ComponentCallbacks.getKoin()`:
Copy code
/**
 * Get Koin context
 */
fun ComponentCallbacks.getKoin() = when (this) {
    is KoinComponent -> this.getKoin()
    is Fragment -> requireActivity().getKoin()
    else -> KoinContextHandler.get()
}
a
in a simple case, I would say either we have a KoinComponent either we have to deal with the current context
I believe we can keep it like that
e
You now call
Copy code
getKoin().getViewModel(
    this.activity as FragmentActivity,
    clazz,
    qualifier,
    parameters
)
where
this.activity as FragmentActivity
is used as a
LifecycleOwner
. Why not use
this
, i.e. the
Fragment
itself, as the lifecycle owner?
a
no, because the sharedViewModel use the ViewModelStore from the activity
has always been like that
e
Ah ok
So then use
requireActivty()
instead?
a
I just revamped teh API to be clearer ... before it was not clear at all on how to use this API directly
will
requireActivty()
will return a FragmentActivity?
e
Not sure, but should there be a function like this?
Copy code
fun <T : ViewModel> Scope.getViewModel(
        viewModelStoreOwner: ViewModelStoreOwner,
        clazz: KClass<T>,
        qualifier: Qualifier? = null,
        parameters: ParametersDefinition? = null
): T
In
fragment:1.2.0
yes:
Copy code
@NonNull
public final FragmentActivity requireActivity() { ... }
a
koin-viewmodeldoesn’t depend directly on fragment:1.2.0
e
Indirectly it does, so it's probably a good idea to include it explicitly:
a
I tried,
requireActivy
works without any extra deps
effectively, I cleaned a bit the API 👍
can check 2.1.1-alpha-2
e
Checked, works great, as expected. Looking into the source code changes now to see if anything peculiar changed 😉
Code changes look good too. Carry on! 🙂
a
thanks for your help @Erik 👍