7

NavController can't pop programmatically the latest @Composable in the stack. I.e. popBackStack() doesn't work if it's a root page. So the application can be closed by tap on "Close" button view and only hardware Back key allows to leave application.

Example: Activity

class AppActivity : ComponentActivity() {
    override fun onCreate(state: Bundle?) {
        super.onCreate(state)
        setContent {
            val controller = rememberNavController()
            NavHost(controller, startDestination = HOME) {
                composable(HOME) { HomePage(controller) }
                ...
            }
        }
    }
}

HomePage.kt

@Composable
fun HomePage(controller: NavController) {
    Button(onClick = {
        controller.popBackStack()
    }) {
        Text("Exit")
    }
}

Question:

How to close the app in onClick handler if Compose Navigation is used.

Sergey Krivenkov
  • 449
  • 6
  • 12
  • I think you are looking for this: https://stackoverflow.com/questions/6330200/how-to-quit-android-application-programmatically In Compose you have to pass the activity through to the composable that will use it. – 2jan222 May 05 '21 at 13:10
  • We do not know how the framework uses and cashes the @Composable entities. I mean, we do not manage its lifecycle, so there is risk of the Context leak. – Sergey Krivenkov May 05 '21 at 13:20
  • Posted an issue/question to Google tracker https://issuetracker.google.com/issues/187134652 – Sergey Krivenkov May 05 '21 at 13:24

2 Answers2

20

You can use this:

@Composable
fun HomePage(controller: NavController) {
    val activity = (LocalContext.current as? Activity)
    Button(onClick = {
        activity?.finish()
    }) {
        Text("Exit")
    }
}
nglauber
  • 9,971
  • 2
  • 44
  • 50
  • After few months in production, I can say this solution works good. Thank you. – Sergey Krivenkov Sep 01 '21 at 18:02
  • 8
    Getting activity inside composable is not a good idea. It's better to flow up the event to ViewModel, subscribe to it in the activity and then call finish there – Dmytro Marchuk Sep 23 '21 at 09:05
  • 1
    ..or even better: call onClick = { onExitClick() } which is passed as a lambda to the function fun HomePage(onExitClick: () -> Unit) {} – Gleichmut Dec 24 '21 at 11:47
  • Casting local context to Activity is unsafe. Passing a lambda to compose and trigger back to the hosted activity seems to be the best solution. – Bao Le Feb 24 '22 at 07:34
  • 1
    I agree with you guys that pass a lambda might be a better option and of course this solution only works if you're following single activity model.. But here it goes an honest question: Do you guys see a scenario where the local context is not an Activity? – nglauber Feb 25 '22 at 12:40
3
@AndroidEntryPoint
class MainActivity:AppCompatActivity() {
@ExperimentalAnimatedInsets
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        WindowCompat.setDecorFitsSystemWindows(window, false)
        val activityKiller: () -> Unit = {
            this.finish()
        }
        setContent {
            MainAppEntry(activityKiller = activityKiller)
        }
    }
}
@Composable
fun MainAppEntry(activityKiller: () -> Unit) {
    val mainViewModel: MainViewModel = hiltViewModel<MainViewModel>()
    //mutableStateOf .......
    var isKillRequested = mainViewModel.mainActivityState.isActivityFinishRequested
    if (isKillRequested) {
        activityKiller()
        return
    }
    Column(Modifier.fillMaxSize()) {
        TextButton(onClick = {
            mainViewModel.smuaActivityState.requestActivityFinish()
        }) {
            Text(text = "Kill app")
        }
    }
}
WAMii
  • 41
  • 1