Vishal Bhimporwala
Vishal Bhimporwala

Reputation: 139

Android Tv Jetpack Compose BackHandler not called when any element will focused

In my @Composable function, whenever a focusable component is added, pressing the remote back button does not trigger the BackHandler event. At that time, the focusable component clears its focus, and only after pressing the back button again does the BackHandler event get triggered.

I want the BackHandler event to trigger every time, regardless of whether a focusable item is present or not.

Video of Issue

Here’s my code:

App.kt

@Composable
fun App() {
    val navController = rememberNavController()
    val context = LocalContext.current

    NavHost(navController = navController, startDestination = Splash) {
        composable<Splash> {
            SplashScreen {
                navController.navigate(MainContent) {
                    popUpTo(Splash) { inclusive = true }
                }
            }
        }

        composable<Download> {
            DownloadScreen {
                navController.navigate(MainContent) {
                    popUpTo(Download) { inclusive = true }
                }
            }
        }

        composable<MainContent> {
            MainContentScreen(onBackClick = {
                (context as? ComponentActivity)?.finish()
            })
        }
    }
}

MainContentScreen.kt

@Composable
fun MainContentScreen(onBackClick: () -> Unit) {
    val focusRequester = remember { FocusRequester() }
    val isSettingOpen = remember { mutableStateOf(false) }

    BackHandler {
        Log.e("TAG", "MainContentScreen: BackHandler")
        if (isSettingOpen.value) {
            isSettingOpen.value = false
        } else {
            onBackClick()
        }
    }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .focusRequester(focusRequester = focusRequester)
            .onDpadCenterClick {
                Log.e("TAG", "MainContentScreen: onDpadCenterClick")
                isSettingOpen.value = true
            }, contentAlignment = Alignment.Center
    ) {
        Text("MainContent Screen")
    }

    if (isSettingOpen.value) {
        SettingsScreen(onBackClick = {
            isSettingOpen.value = false
        })
    }

    LaunchedEffect(Unit) {
        focusRequester.requestFocus()
    }
}

Extension Fun for Dpad center click

fun Modifier.onDpadCenterClick(onDpadCenterClick: () -> Unit): Modifier {
    return this.then(Modifier
        .focusable()
        .onKeyEvent { keyEvent ->
        if (keyEvent.nativeKeyEvent.action == KeyEvent.ACTION_UP) {
            when (keyEvent.nativeKeyEvent.keyCode) {
                KeyEvent.KEYCODE_DPAD_CENTER -> {
                    onDpadCenterClick()
                    true // Consume the event
                }

                else -> false
            }
        } else {
            false
        }
    })
}

SettingsScreen.kt

@Composable
fun SettingsScreen(onBackClick: () -> Unit) {
    val focusRequester = remember { FocusRequester() }
    BackHandler {
        Log.e("TAG", "SettingsScreen: BackHandler Called Close")
        onBackClick()
    }
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(color = SettingsBG)
            .focusable()
            .focusRequester(focusRequester = focusRequester)
    ) {
        Column {
            MPButton(text = "Text 1") {
                Log.e("TAG", "SettingsScreen: text 1 click")
            }
            MPButton(text = "Text 2") {
                Log.e("TAG", "SettingsScreen: text 2 click")
            }
        }
    }

    LaunchedEffect(Unit) {
        focusRequester.requestFocus()
    }
}

MPButton.kt

@Composable
fun MPButton(text: String, onClick: () -> Unit) {
    Card(
        modifier = Modifier.padding(all = 10.dp), onClick = onClick
    ) {
        Text(text = text)
    }
}

In my case On the MainContentScreen, when the D-pad center button is clicked, it should open the SettingsScreen. Inside the SettingsScreen, there are two MPButtons labeled text1 and text2, with the focus initially on text1. I want to handle the BackHandler event based on whether text1 or text2 is focused or not.

Video of Issue

Upvotes: 4

Views: 64

Answers (1)

Vishal Bhimporwala
Vishal Bhimporwala

Reputation: 139

After research and trial and error, I discovered that I simply needed to remove Modifier.focusable() from the Box modifier.

Updated code... SettingsScreen.kt

@Composable
fun SettingsScreen(onBackClick: () -> Unit) {
    val focusRequester = remember { FocusRequester() }
    BackHandler {
        Log.e("TAG", "SettingsScreen: BackHandler Called Close")
        onBackClick()
    }
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(color = SettingsBG)
            .focusRequester(focusRequester = focusRequester)
    ) {
        Column {
            MPButton(text = "Text 1") {
                Log.e("TAG", "SettingsScreen: text 1 click")
            }
            MPButton(text = "Text 2") {
                Log.e("TAG", "SettingsScreen: text 2 click")
            }
        }
    }

    LaunchedEffect(Unit) {
        focusRequester.requestFocus()
    }
}

Let me know if you need any help with Jetpack Compose. We need to build a large community for Jetpack Compose.

Upvotes: 0

Related Questions