https://kotlinlang.org logo
Title
z

Zsolt.bertalan

07/13/2022, 4:36 PM
Is there a way to make sure all Intents are executed when the Fragment is being destroyed? I have to do some analytics tracking when the Fragment is paused. My Fragment is in a ViewPager, and on some devices there are no more than 2 milliseconds to send the intent before everything is destroyed, but I have to do a little calculation as well, which is enough to miss it sometimes. What I did for now is that I copied the code from com.arkivanov.mvikotlin.extensions.coroutines.Binder.kt, and added a delay to the BuilderBinder.stop function like this:
override fun stop() {
        GlobalScope.launch(mainContext) {
            delay(DELAY_STOP)
            job?.cancel()
            job = null
        }
    }
First of all, is this correct? Is this the best I can do? Maybe, does it make sense to add this to the library?
a

Arkadii Ivanov

07/13/2022, 5:00 PM
I usually just don't dispose such an operation. E.g. with Rx I just call
subscribe
and don't remember the Disposable. And also put a descriptive comment. With coroutines, perhaps this would by launching the job in the
GlobalScope
. Delaying the Destroy even sounds also interesting. Most likely I would create an extensions as follows:
fun Lifecycle.delayedDestroy(millis: Long): Lifecycle = TODO()
z

Zsolt.bertalan

07/13/2022, 6:18 PM
Maybe I missed out an important information. I send the Intent because I want to use a dependency that I don’t want in the Fragment. So the Store needs to be used, but it’s stopped by the lifecycle. Not sure if the extension on the LifeCycle can avoid this?
a

Arkadii Ivanov

07/13/2022, 8:49 PM
You should be able to substitute the lifecycle with a delayed one, I guess.
Ahh indeed, if the Store is created via InstanceKeeper, the delayed lifecycle won't help
Are using InstanceKeeper?
Btw, there is another option. You can override
dispose
method in the
Executor
and call
super.dispose()
after a certain delay.
I guess the following should work:
override fun dispose() {
            scope.launch { 
                delay(1000L)
                super.dispose()
            }
        }
z

Zsolt.bertalan

07/14/2022, 7:49 AM
I don’t use InstanceKeeper yet. I will try delaying dispose. Thanks for your help
👍 1
The delay in dispose did not work unfortunately. I do the calculation, or a single call really in the Fragment. I need to track the percentage scrolled in a ViewPager item. By the time I dispatch the Intent two milliseconds later, the binding is already stopped.
a

Arkadii Ivanov

07/14/2022, 9:53 AM
In this case you should delay the store disposal. Could you show the related code? E.g. where store.dispose() is called.
z

Zsolt.bertalan

07/14/2022, 10:03 AM
It’s simply in a ViewModel:
override fun onCleared() {
    topicStore::dispose
}
But I already tried that. The binding is already stopped when this is called.
a

Arkadii Ivanov

07/14/2022, 10:04 AM
In this case you could delay right there, e.g. using GlobalScope.
z

Zsolt.bertalan

07/14/2022, 10:10 AM
Sorry, I edited my post. ☝️
I tried to add a delay with GlobalScope
a

Arkadii Ivanov

07/14/2022, 10:13 AM
First of all, looks like there is a mistake in the last snippet -
topicStore::dispose
is just a method reference, it doesn't actually call
dispose
. You could use the following to delay the disposal of the store:
override fun onCleared() {
    GlobalScope.launch {
        delay(DELAY_STOP)
        topicStore.dispose()
    }
}
z

Zsolt.bertalan

07/14/2022, 11:11 AM
Ah, rookie mistake. It means that I have to add delay to the store disposal too. But it doesn’t mean I can avoid the delay in the stop function.
a

Arkadii Ivanov

07/14/2022, 11:20 AM
Yeah, binding depends on Lifecycle, so the whole Lifecycle could be wrapped with a delayed one.
z

Zsolt.bertalan

07/14/2022, 1:12 PM
I’m not sure how to wrap the lifecycle, sorry. Could you give me an example, please?
a

Arkadii Ivanov

07/14/2022, 1:20 PM
Sure I can help! I guess the following should work, but this is just from the top of my head.
fun Lifecycle.delayedDestroy(millis: Long): Lifecycle {
    val lifecycle = LifecycleRegistry()

    when (state) {
        Lifecycle.State.CREATED -> lifecycle.create()
        Lifecycle.State.STARTED -> lifecycle.start()
        Lifecycle.State.RESUMED -> lifecycle.resume()
        Lifecycle.State.DESTROYED -> {
            lifecycle.create()
            lifecycle.destroy()
        }
        Lifecycle.State.INITIALIZED -> Unit
    }

    subscribe(
        object : Lifecycle.Callbacks by lifecycle {
            override fun onDestroy() {
                GlobalScope.launch {
                    delay(millis)
                    lifecycle.onDestroy()
                }
            }
        }
    )

    return lifecycle
}
z

Zsolt.bertalan

07/14/2022, 1:38 PM
Works like a charm. Thanks again!
🎉 1
a

Arkadii Ivanov

07/14/2022, 1:49 PM
The snippet was incomplete. If the original lifecycle is already in some state (e.g. RESUMED), the wrapped lifecycle would stay INITIALIZED. And this uncovered one point to improve - to add
initialState
argument to
LifecycleRegistry(...)
function - filed https://github.com/arkivanov/Essenty/issues/56. I have updated the snippet here
👍 1