Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TopAppBar flashing when navigating with Compose Navigation

I have 2 screens which both have their own Scaffold and TopAppBar. When I navigate between them using the Jetpack Navigation Compose library, the app bar flashes. Why does it happen and how can I get rid of this?

enter image description here

Code:

Navigation:

@Composable
fun TodoNavHost(
    navController: NavHostController,
    modifier: Modifier = Modifier
) {
    NavHost(
        navController = navController,
        startDestination = TodoScreen.TodoList.name,
        modifier = modifier
    ) {
        composable(TodoScreen.TodoList.name) {
            TodoListScreen(
                onTodoEditClicked = { todo ->
                    navController.navigate("${TodoScreen.AddEditTodo.name}?todoId=${todo.id}")
                },
                onFabAddNewTodoClicked = {
                    navController.navigate(TodoScreen.AddEditTodo.name)
                }
            )
        }
        composable(
            "${TodoScreen.AddEditTodo.name}?todoId={todoId}", 
            arguments = listOf(
                navArgument("todoId") {
                    type = NavType.LongType
                    defaultValue = -1L
                }
            )
        ) {
            AddEditTodoScreen(
                onNavigateUp = {
                    navController.popBackStack() 
                },
                onNavigateBackWithResult = { result ->
                    navController.navigate(TodoScreen.TodoList.name)
                }
            )
        }
    }
}

Todo list screen Scaffold with TopAppBar:

@Composable
fun TodoListBody(
    todos: List<Todo>,
    todoExpandedStates: Map<Long, Boolean>,
    onTodoItemClicked: (Todo) -> Unit,
    onTodoCheckedChanged: (Todo, Boolean) -> Unit,
    onTodoEditClicked: (Todo) -> Unit,
    onFabAddNewTodoClicked: () -> Unit,
    onDeleteAllCompletedConfirmed: () -> Unit,
    modifier: Modifier = Modifier,
    errorSnackbarMessage: String = "",
    errorSnackbarShown: Boolean = false
) {

    var menuExpanded by remember { mutableStateOf(false) }
    var showDeleteAllCompletedConfirmationDialog by rememberSaveable { mutableStateOf(false) }

    Scaffold(
        modifier,
        topBar = {
            TopAppBar(
                title = { Text("My Todos") },
                actions = {
                    IconButton(
                        onClick = { menuExpanded = !menuExpanded },
                        modifier = Modifier.semantics {
                            contentDescription = "Options Menu"
                        }
                    ) {
                        Icon(Icons.Default.MoreVert, contentDescription = "Show menu")
                    }
                    DropdownMenu(
                        expanded = menuExpanded,
                        onDismissRequest = { menuExpanded = false }) {
                        DropdownMenuItem(
                            onClick = {
                                showDeleteAllCompletedConfirmationDialog = true
                                menuExpanded = false
                            },
                            modifier = Modifier.semantics {
                                contentDescription = "Option Delete All Completed"
                            }) {
                            Text("Delete all completed")
                        }
                    }
                }

            )
        },
[...]

Add/edit screen Scaffold with TopAppBar:

@Composable
fun AddEditTodoBody(
    todo: Todo?,
    todoTitle: String,
    setTitle: (String) -> Unit,
    todoImportance: Boolean,
    setImportance: (Boolean) -> Unit,
    onSaveClick: () -> Unit,
    onNavigateUp: () -> Unit,
    modifier: Modifier = Modifier
) {
    Scaffold(
        modifier,
        topBar = {
            TopAppBar(
                title = { Text(todo?.let { "Edit Todo" } ?: "Add Todo") },
                actions = {
                    IconButton(onClick = onSaveClick) {
                        Icon(Icons.Default.Save, contentDescription = "Save Todo")
                    }
                },
                navigationIcon = {
                    IconButton(onClick = onNavigateUp) {
                        Icon(Icons.Default.ArrowBack, contentDescription = "Back")
                    }
                }
            )
        },
    ) { innerPadding ->
        BodyContent(
            todoTitle = todoTitle,
            setTitle = setTitle,
            todoImportance = todoImportance,
            setImportance = setImportance,
            modifier = Modifier.padding(innerPadding)
        )
    }
}
like image 352
Florian Walther Avatar asked Aug 03 '21 09:08

Florian Walther


People also ask

What is topappbar in Android using jetpack compose?

The action bar is used to represent the app name and action items in Android. In this article, we will take a look at the implementation of the TopAppBar in Android using Jetpack compose. this is use to represent the title for our action bar. this is use as a leading icon which is use to open navigation drawer.

What is the use of topappbar?

TopAppBar is a bar that is placed at the top of screen. TopAppBar can be used to display information like title related to the current screen in the application. Also, TopAppBar can display actions, if any, related to the current screen. The following code is the definition of TopAppBar.

How do I use compose with Android navcontroller?

To support Compose, use the following dependency in your app module’s build.gradle file: The NavController is the central API for the Navigation component. It is stateful and keeps track of the back stack of composables that make up the screens in your app and the state of each screen.

How do I navigate to a composable in a navigation graph?

To navigate to a composable destination in the navigation graph, you must use the navigate () method. navigate () takes a single String parameter that represents the destination’s route. To navigate from a composable within the navigation graph, call navigate ():


Video Answer


2 Answers

The flashing is caused by the default cross-fade animation in more recent versions of the navigation-compose library. The only way to get rid of it right now (without downgrading the dependency) is by using the Accompanist animation library:

implementation "com.google.accompanist:accompanist-navigation-animation:0.20.0"

And then replace the normal NavHost with Accompanist's AnimatedNavHost, replace rememberNavController() with rememberAnimatedNavController() and disable the transitions animations:

AnimatedNavHost(
        navController = navController,
        startDestination = bottomNavDestinations[0].fullRoute,
        enterTransition = { _, _ -> EnterTransition.None },
        exitTransition = { _, _ -> ExitTransition.None },
        popEnterTransition = { _, _ -> EnterTransition.None },
        popExitTransition = { _, _ -> ExitTransition.None },
        modifier = modifier,
    ) {
        [...}
    }
like image 114
Florian Walther Avatar answered Sep 20 '22 21:09

Florian Walther


I think I found an easy solution for that issue (works on Compose version 1.4.0).

My setup - blinking

All of my screens have their own toolbar wrapped in the scaffold:

// Some Composable screnn

Scaffold(
    topBar = { TopAppBar(...) }
) {
    ScreenContent()
}

Main activity which holds the nav host is defined like that:

// Activity with NavHost

setContent {
    AppTheme {
        NavHost(...) { }
    }
}

Solution - no blinking!

Wrap you NavHost in activity in a Surface:

setContent {
    AppTheme {
        Surface {
            NavHost(...) { }
        }
    }
}

Rest of the screens stay the same. No blinking and transition animation between destinations is almost the same like it was with fragments (subtle fade in/fade out). So far I haven't found any negative side effects of that.

like image 21
rafakob Avatar answered Sep 19 '22 21:09

rafakob