I'm not being a massive dingus am I? Or have I som...
# compose
c
I'm not being a massive dingus am I? Or have I some how come across a compose bug 🧵
Copy code
@Composable
fun RealPhoto(modifier: Modifier, onPhoto: (String) -> Unit) {
    var cameraAccessible by remember { mutableStateOf(false) }

    Box(
        modifier = modifier,
        contentAlignment = Alignment.Center
    ) {
        if (!cameraAccessible) {
            RequireCameraPermission(onGranted = {
                cameraAccessible = true
            })
        } else {
            // Never executes, even after cameraAccessible = true
            DeklutterCameraPreview()
        }
    }
}
BUT
Copy code
@Composable
fun RealPhoto(modifier: Modifier, onPhoto: (String) -> Unit) {
    var cameraAccessible by remember { mutableStateOf(false) }

    Box(
        modifier = modifier,
        contentAlignment = Alignment.Center
    ) {
        if (!cameraAccessible) {
            RequireCameraPermission(onGranted = {
                cameraAccessible = true
            })
        } 
        
        if (cameraAccessible){
            // executes!!!
            DeklutterCameraPreview()
        }
    }
}
TL;DR - using if (!stateBool) ... else ... doesn't seem to work, but if (!stateBool) ... if (stateBool) ... does
d
For something like that use the debugger or log statements to see what the state is on the recomposition
z
Also show Kotlin bytecode and decompile to see if the compiler is doing something weird with returns or something
RequireCameraPermision isn’t immediately calling that on granted callback, is it? Like, so the second F in your second code is running immediately in the same composition?
p
In such case there also should be recomposition, isn't it?
c
I think Zach has nailed it, I guess having the second if adds a second read request causing the recomposition?
Dingus confirmed.
p
Why it shouldn't be triggered with first read?
c
I think it's a 'back write', I'm unsure 😞
p
But it should trigger
Copy code
var x by remember {mutableStateOf(false)}
if (!x) x = true
Should recompose
c
I guess the write happening in a lambda confuses matters, I'm not even sure that the lambda is executed from a composable function, but maybe a callback from one of the crusty android api's for determining whether or not permissions have been granted
z
shouldn't matter where the callback is executed as long as its within the callstack of the composition
this test fails and only prints "state=initial":
Copy code
@Test fun backWrite() {
  var compositions = 0
  rule.setContent {
    var state by remember { mutableStateOf("initial") }
    println("state=$state")
    if (state == "initial") state = "changed"
    compositions++
  }
  rule.runOnIdle {
    assertEquals(2, compositions)
  }
}
p
@Zach Klippenstein (he/him) [MOD] is it expected behaviour? I remember such cases caused recompositions in my project.
I remember this was infinite recomposing but now it is not: https://developer.android.com/develop/ui/compose/performance/bestpractices#avoid-backwards
With current logic there might be leaking abstraction. Now one should know (look into details) if lambda should be executed in current stack or not.
Created an issue https://issuetracker.google.com/issues/443261876 A least for making it clear
z
Well it should be documented at least. You probably do need to know if it can be executed immediately since in general an API that looks asynchronous often needs special handling if it suddenly runs synchronously instead (see: all the things that will break if you just replace a normal coroutine dispatcher with Unconfined)
d
One thing to note for this specific case. It would be better to start with the correct initial state instead of going through a composition to get it. And then potentially you want to always be listening for a change in the permission and/or checking right before it's needed