Koin: Provided object is from wrong scope

Created on 17 Sep 2018  路  2Comments  路  Source: InsertKoinIO/koin

Describe the bug
Instance of object is from wrong (previous) scope, because the scope is not closed yet.
In Android fragment lifecycle, it's possible that the new fragment will call onCreate method, while previous fragment is still executing onDestroy.

To Reproduce
To reproduce the issue let's create environment like this:

Test module:

val testFragmentModule = module {
    scope(TEST_SCOPE) { (fragment: TestFragment) -> TestLogger(fragment) }
}

Test fragment:

    // Test object with fragment injected via constructor
    private val logger by inject<TestLogger>(parameters = { parametersOf(this) })

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        bindScope(getOrCreateScope(TEST_SCOPE))
        Log.d("BKZ", "Fragment: " + this.toString())
        logger.test()
    }

Test logger class:

class TestLogger(private val testFragment: TestFragment) {

    fun test() {
        Log.d("BKZ", "Logger: " + testFragment.toString())
    }
}

Now let's rotate the device few times and check Logcat:

// First creation: fragment instance in the logger class is correct
D/BKZ: Fragment: TestFragment{13b19eb #3 id=0x7f08002d}
D/BKZ: Logger: TestFragment{13b19eb #3 id=0x7f08002d}
// Device rotation: Fragment has been recreated
D/BKZ: Fragment: TestFragment{a449e78 #4 id=0x7f08002d}
// TestFragment instance in the logger class is wrong, it's from the previous scope.
D/BKZ: Logger: TestFragment{13b19eb #3 id=0x7f08002d}

As you can see when bindScope(getOrCreateScope(TEST_SCOPE)) method in TestFragment was called for the second time, previous scope was not closed yet and we've received wrong instance of the fragment object, the one which was destroyed.

Expected behavior
Scope should be closed before bindScope in onCreate is called.

For now, in my project I've found workaround for this problem to close the scope when bindScope is called (I'm not sure if it will not cause any other issues):

scopedWith extension:

fun LifecycleOwner.scopedWith(scopeId: String)  {
    try { 
        getScope(scopeId).close()
    } catch(e: NoScopeFoundException) { }
    bindScope(createScope(scopeId))
}

in fragment:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        scopedWith(TEST_SCOPE)
}

Koin project used and used version (please complete the following information):
koin-android:1.0.0
koin-androidx-scope:1.0.0

android check

Most helpful comment

We introduced the detach function, to detach a scope and then don't have the scope overlapping problem. This means that your are responsible of your Scope and manage it manually:

class MyScopeActivity : AppCompatActivity() {

    val scope = getKoin().detachScope("session")
    val presenter: MyScopePresenter by inject(scope = scope)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_simple)

        bindScope(scope)


    }
}

This feature is part of 1.0.2.

All 2 comments

Sure, problem is about opening/closing a scope against current lifecycle that is still closed and opening a new one on the fly 馃憤

We introduced the detach function, to detach a scope and then don't have the scope overlapping problem. This means that your are responsible of your Scope and manage it manually:

class MyScopeActivity : AppCompatActivity() {

    val scope = getKoin().detachScope("session")
    val presenter: MyScopePresenter by inject(scope = scope)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_simple)

        bindScope(scope)


    }
}

This feature is part of 1.0.2.

Was this page helpful?
0 / 5 - 0 ratings