3

Is it possible to set the value of a FloatingActionButton's onClick inside of a Scaffold from a screen composable from a NavHost, which is also inside the Scaffold? Here in my example, I want to be able to change the FAB's onClick to do some calculations from a value (name) inside the screen composable, without hoisting the value to the composable where the Scaffold is located (MyActivity()).

@Composable
fun MyActivity() {
    val navController = rememberNavController()

    Scaffold(
        floatingActionButton = {
            FloatingActionButton(onClick = { /* set this lambda from Screen1 */ }) { ... }
        }
    ) { paddingValues ->
        NavHost(
            navController = navController,
            startDestination = Screen.Screen1.route,
            modifier = Modifier.padding(paddingValues)
        ) {
            composable(route = Screen.Screen1.route) {
                Screen1()
            }
            ...
        }
    }
}

@Composable
fun Screen1() {
    var name by remember { mutableStateOf(TextFieldValue("") }

    TextField(value = name, onValueChange = { name = it })

    // Set FAB onClick to do some calculations on name without hoisting the variable
    setFabOnClick { name.calculate() }
}
hamthenormal
  • 856
  • 8
  • 21

1 Answers1

7

I'm not sure why you don't want to put this value in the top view. If you do that in your example, updating the text will not cause recomposition of MyActivity, only Screen1 - Compose is smart enough to do that.

In any case, you can create a mutable state to store the handler and update it in Screen1. I use LaunchedEffect because updating the state is a side effect and should not be done directly from the view constructor, also there is no need to do it at every recomposition.

@Composable
fun Screen1(
    setFabOnClick: (() -> Unit) -> Unit,
) {
    var name by remember { mutableStateOf(TextFieldValue("")) }

    TextField(value = name, onValueChange = {name = it})

    LaunchedEffect(Unit) {
        setFabOnClick { println("$name") }
    }
}

@Composable
fun MyActivity() {
    val navController = rememberNavController()
    val (fabOnClick, setFabOnClick) = remember { mutableStateOf<(() -> Unit)?>(null) }

    Scaffold(
        floatingActionButton = {
            FloatingActionButton(onClick = {
                fabOnClick?.invoke()
            }) {
                Icon(Icons.Default.ReportProblem, null)
            }
        }
    ) { paddingValues ->
        NavHost(
            navController = navController,
            startDestination = Screen.Screen1.route,
            modifier = Modifier.padding(paddingValues)
        ) {
            composable(route = Screen.Screen1.route) {
                Screen1(setFabOnClick = setFabOnClick)
            }
        }
    }
}
Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
  • Thank you so much for spending your time to answer my question. Will the code inside the onClick automatically update as the value of name changes? – hamthenormal Feb 20 '22 at 04:30
  • 1
    @hamthenormal why don't you check it by yourself? your state variable value will be up to date. If you'll face some data being captured, which you don't expect, you can pass value as `key` to `LaunchedEffect` – Phil Dukhov Feb 20 '22 at 04:39
  • Okay. I'll try it later. Thank you – hamthenormal Feb 20 '22 at 07:51
  • Looks like a nice solution, thanks for sharing. About the LaunchEffect, as the reference to the function doesn't change on recomposition (except when changing screen) and doesn't do heavy/long running work, shouldn't it be fine doing this without LaunchEffect? – stefan.at.kotlin Jun 05 '22 at 12:11
  • 2
    @stefan.at.wpf not using `LaunchedEffect` here is against [Compose principles](https://developer.android.com/jetpack/compose/mental-model) - composables should be free of side-effects for better performance. One of the reasons is that without `LaunchedEffect` each time `name` is updated it's gonna recompose `MyActivity` too, but it's better to always make your composables free of side-effects, even if in the particular case it looks like it's ok to just make a call – Phil Dukhov Jun 05 '22 at 12:19
  • Thanks, convinved by applying the general principle of side effect free :-) Was thinking too much about this specific case. – stefan.at.kotlin Jun 05 '22 at 12:24