@Composable
fun AndroidViewContainer(modifier: Modifier = Modifier, int: () -> Int) {
int() // for causing recomposition
var view: View? = null
SideEffect {
println("Log sideeffect $view")
}
DisposableEffect(Unit) {
onDispose {
println("Log detached $view")
}
}
AndroidView(
modifier = modifier,
factory = { context ->
View(context).also {
println("Log factory")
view = it
}
},
update = {
println("Log: $view")
}
)
}
Having the code like above, I would assume that when first composition happens, the factory
callback is getting called and view is getting set to be not null
, but the View
. After the recomposition, the var view: View? = null
should be executed and override the View
that was set before and value of the view
should be null again.
This is true for SideEffect
or update
, those receive the null
value. But when we leave the screen, the onDispose
is getting called but not with null
value but with the View
that was set in factory
callback.
And here comes the question: Do compose compiler somehow wraps it for the onDispose
call? Why only onDispose
still have this value and not other side effects?
Wrapping the view
like var view: View? by remember { mutableStateOf(null) }
will make it work like I expect, but I am just wondering of why the value is saved for the onDispose
call.
When you specify a DisposableEffect
(or LaunchedEffect
) with Unit
as key, what happens is that the DisposableEffect
is only executed once when the Composable initially enters the composition. It keeps any variables in the state they were in when the DisposableEffect
was called.
This behavior can be demonstrated by this simplified code:
@Composable
fun MainComposable() {
Column {
var count by remember {
mutableIntStateOf(0)
}
Button(onClick = { count++ }) {
Text(text = "RECOMPOSE")
}
if (count <= 3) {
AndroidViewContainer(count)
}
}
}
@Composable
fun AndroidViewContainer(int: Int) {
SideEffect {
println("Log sideeffect $int")
}
DisposableEffect(Unit) {
println("Log DisposableEffect attached with $int")
onDispose {
println("Log DisposableEffect detached with $int")
}
}
}
Logs:
I Log DisposableEffect attached with 0
I Log sideeffect 0
I Log sideeffect 1
I Log sideeffect 2
I Log sideeffect 3
I Log DisposableEffect detached with 0
In comparison, the SideEffect
is executed after every single successful recomposition and thus always will print the latest value.
If you want the DisposableEffect to be updated whenever a variable changes, you need to specify that variable as a key:
DisposableEffect(int) {
//...
}
or use rememberUpdatedState
to observe a variable in a way that does not require the DisposableEffect
to restart every time the variable changes, what would be the case when providing that variable as key
:
@Composable
fun AndroidViewContainer(modifier: Modifier = Modifier, int: Int) {
val currentInt by rememberUpdatedState(int)
SideEffect {
println("Log sideeffect $int")
}
DisposableEffect(Unit) {
println("Log DisposableEffect attached with $currentInt")
val oldCounter = int
onDispose {
println("Log DisposableEffect detached with $currentInt")
}
}
}