https://kotlinlang.org logo
#compose
Title
# compose
v

vide

03/21/2023, 8:53 PM
It seems to be possible to get the new focus modifiers in an inconsistent state and the app will crash on next key press:
Copy code
java.lang.IllegalStateException: Event can't be processed because we do not have an active focus target.
    at androidx.compose.ui.focus.FocusOwnerImpl.dispatchKeyEvent-ZmokQxo(FocusOwnerImpl.kt:171)
EDIT: I have a hypothesis on why this is happening. I think it's a compose bug. Will write explanation in 🧵 EDIT 2: Debugged the root cause and created a minimal repro and recorded a demo of the crash
😁 1
This is quite critical for android TV apps with heavy usage of focus 😅 I will try to debug this and update my findings in this 🧵 and file a bug report when I know what is happening or have no more time for it.
One thing I know for certain is that this is a regression from updating 1.3.0-beta03 -> 1.4.0-rc01
I can reproduce this reliably with the following steps: 1. Begin with nav stack [A, B] in AnimatedNavHost and an overlay C 2. Have focus on a node inside B 3. Pop B off the nav stack (A will start composing) 4. Move focus to a node in C 5. B is now disposed 6. Try to move focus somewhere from C -> crash Since steps 3, 4 and 5 happen in very quick succession, I think there might be some kind of race condition in updating the focus modifier tree. The cause of the crash is that the root focus node is
Inactive
while
(View).isFocused
is true.
I checked with the debugger, in this case
rootFocusNode.focusStateImpl
is ActiveParent but all children are Inactive
I think I managed to figure out the root cause. If I have a tree of focus target modifier nodes: • Root (ActiveParent) -> A (Active) And then recompose to add B in between (in this case .clickable changes from disabled to enabled) • Root (ActiveParent) -> B (Inactive) -> A (Active) I will have to investigate some more since this seems like a quite common scenario to occur, I will try to make a small reproducible example
Hypothesis seems to be correct, here is a minimally reproducible example:
Copy code
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Demo()
        }
    }
}

@Composable
fun ClickableBox(
    modifier: Modifier = Modifier,
    isEnabled: Boolean,
    onClick: () -> Unit,
    content: @Composable BoxScope.() -> Unit
) {
    val interactionSource = remember { MutableInteractionSource() }
    val isFocused = interactionSource.collectIsFocusedAsState()

    Box(modifier = modifier
        .background(if (isFocused.value) Color.Cyan else Color.Magenta)
        .clickable(
            interactionSource = interactionSource,
            indication = LocalIndication.current,
            enabled = isEnabled, onClick = onClick
        )
    ) {
        content()
    }
}

@Composable
fun Demo() {
    var outerBoxClickable by remember { mutableStateOf(false) }
    var innerBoxClickable by remember { mutableStateOf(true) }

    Column(Modifier.background(Color.Black)) {
        ClickableBox(
            Modifier.size(50.dp),
            isEnabled = outerBoxClickable,
            onClick = {}
        ) {
            ClickableBox(
                Modifier.size(25.dp).align(Alignment.Center),
                isEnabled = innerBoxClickable,
                onClick = { outerBoxClickable = true }
            ) {}
        }

        ClickableBox(Modifier.size(50.dp), isEnabled = true, onClick = {}) {}
    }
}
It will crash when you use keyboard to navigate inside the nested box, press enter and then try to navigate again.
And here's a recording blob smile
392 Views