androidkotlinandroid-jetpack-composeandroid-jetpackandroid-jetpack-navigation

How to stop a composable function to fire while navigating?


I have a very simple app:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyTheme {
                Navigation(
                    navController = rememberNavController()
                )
            }
        }
    }
}

With two screens:

sealed class Screen(val route: String){
    data object MainScreen : Screen("main_screen")
    data object DetailScreen : Screen("detail_screen")

    fun withArgs(vararg args: String) = buildString {
        append(route)
        args.forEach { arg ->
            append("/$arg")
        }
    }
}

What I want is to pass a text from one screen to the other using navigation:

@Composable
fun Navigation(
    navController: NavHostController
) {
    NavHost(
        navController = navController,
        startDestination = MainScreen.route
    ) {
        composable(
            route = MainScreen.route
        ) {
            MainScreen(
                navigateToDetailScreen = { text ->
                    navController.navigate(DetailScreen.withArgs(text))
                }
            )
        }
        composable(
            route = DetailScreen.route + "/{text}",
            arguments = listOf(
                navArgument("text") {
                    type = NavType.StringType
                }
            )
        ) { entry ->
            DetailScreen(
                text = entry.arguments?.getString("text")!!
            )
        }
    }
}

And here are my two screens:

@Composable
fun MainScreen(
    navigateToDetailScreen: (text: String) -> Unit
) {
    Log.d("MyTag", "MainScreen")

    var text by remember { mutableStateOf("") }

    Column(
        modifier = Modifier.fillMaxSize()
    ) {
        TextField(
            value = text,
            onValueChange = {
                text = it
            }
        )
        Button(
            onClick = {
                navigateToDetailScreen(text)
            }
        ) {
            Text(
                text = "Navigate"
            )
        }
    }
}

@Composable
fun DetailScreen(text: String) {
    Log.d("MyTag", "DetailScreen")

    Column(
        modifier = Modifier.fillMaxSize()
    ) {
        Text(
            text = text
        )
    }
}

The two problems I'm facing are:

  1. Each time I write a new character inside the TextField, the MainScreen fun fires again and again.

  2. When I press Navigate, the MainScreen fun fires again, the DetailScreen fires once and the MainScreen fun fires again resulting in displaying:

    MainScreen //when the app starts
    MainScreen //when adding the first character
    MainScreen //when adding the second character
    MainScreen //when clicking the "Navigate" button
    DetailScreen //when the second screen is displayed
    MainScreen //For no reason
    

How to stop that from happening?


Solution

  • Recomposition doesn’t necessarily mean that the UI has changed; it’s just a request to re-evaluate the composable to see if anything needs to be updated. Not all recompositions result in actual UI updates. Sometimes recompositions are triggered but then discarded due to some optimization

    Because of this, if you place a log statement directly inside your composable, it will be executed every time the composable is recomposed. This can lead to a large number of log entries, which might make it seem like your UI is being rebuilt or re-created multiple times, even if that’s not the case.

    Prefer using LaunchedEffect or SideEffect