Why DisposableEffect does not dispose whenever I m...
# compose-desktop
n
Why DisposableEffect does not dispose whenever I move a window from one screen to a secondary screen? It seems that everything is recomposing but onDispose is not triggered. This really hits badly for couroutines being subscribed twice.
p
Did you keyed the Disposable effect with some variable that changes when the screen changes?
Looks like a bug?
p
Otherwise if it has no key and the position in the slot table doesn't change from one composition to the other, there is no disposal of the effect. I believe it is called positional memoization and compose does that by default. That's why is a good practice keying effects and remember
a
The original question says that DisposableEffect is called second time without calling onDispose. At least this is how I understand it. I believe this should never happen regardless of the keys. Either the same effect continues to run, or the old one is disposed and the new one is started. That's why from my point of view this looks like a bug. Correct me if I'm wrong.
a
The composition doesn’t get disposed so there’s no reason for onDispose to be called.
Unless you keyed it on something that changes when the screen changes, as PablichJenkov wrote.
Note that there’s a bug/issue with
Density
that I think has been already fixed, where the values change, but the object stays the same, so there’s no recomposition.
n
I was not able to find such a key yet. I tried (besides Unit)
window
object but it did not work. Indeed DisposableEffect is called when old onDispose was not executed
🚨 2
👍 1
It (most probably) causes memory leak, as my code inside old
rememberCoroutinescope
keeps executing
At this point the only solution to my problem is using atomic boolean to execute my code but this is temprorary solution
a
File a bug, we’ll take a look at it. Sounds like quite a serious problem if true.
👍 1
n
Okay, apparently something else is problem, I could not reproduce the DisposableEffect problem as before, as my problem was with coroutines. Apparently if I run the following:
Copy code
val myflow = flow {
    repeat(100) {
        delay(1000)
        println("emitting $it")
        emit(it)
    }
}

@Composable
fun TestDispose() {
    Box {
        val scope = rememberCoroutineScope()
        scope.launch {
            println("scope started")
            myflow.collectLatest {
                println("collecting $it")
            }
        }
    }
}
then the scope.launch block will be executed again once I move from a screen to another. So Box is instantiated, but scope is not remembered apparently? But if I put that block into Disposable then everything works (as should be), and Disposable is not started again but also onDispose is not executed either. Which is also weird because if Box is re-instantiated, then why old Disposable is working as it is in a new box?
a
In the example above, it’s expected that
scope.launch
will be executed on every (re)composition.
The scope itself is remembered, of course, but you’re launching a new coroutine on every (re)composition. It’s typically a bug to do this.
onDispose
is not meant to be called on every recomposition; only when the composition of the current function goes away
👍 1
n
Okay, that is clear now. But then another question, if re-compostiontion occurs, why onDispose is not executed when I move form one window to another?
Ah okay, that is clear I guess, I think everything is solved now
Also what do you mean by
composition of the current function goes away
?
You mean the key that I pass to DisposableEffect ?
a
If the key changes, or if the function is no longer part of the composition
n
And if key is Unit and composable re-composes then there is no guarantee that onDispose will be called?
a
With a
Unit
key there’s no reason for onDispose to be called. It will only be called when the function is no longer part of the composition:
Copy code
@Composable
fun MyScreen() {
    var showWidget by remember { mutableStateOf(true) }
    if (showWidget)
        MyWidget()
    Button(
        onClick = { showWidget = false },
    ) {
        Text("Hide Widget")
    }
}

@Composable
fun MyWidget() {
    DisposableEffect(Unit) {
        onDispose {
            println("onDispose")
        }
    }
}
n
Ah so if showWidget becomes false then onDispose will be called
That clarifies a lot, thank you!
a
Yes
216 Views