Search code examples
androidandroid-jetpack-composeandroid-jetpack-navigationandroid-bottomnav

How to handle bottom navigation button click 2-nd time at inner screen using compose navigation


I have an app, which represent a screen with bottom bar with 4 tabs. I use compose navigation with bottom bar. When user clicks on the bar the screen appears. How can I handle the situation when user clicks on the bar button for the 2-nd time, I need to listen to this event from the current inner screen and scroll the content up or do something else.

I investigate that I should use currentBackStackEntryAsState, but I am not sure which way.


Solution

  • You can build classes shared in your compose tree with staticCompositionLocalOf.

    For this purpose I've build CurrentTabClickHandler - it's API is similar to BackHandler:

    @Composable
    fun CurrentTabClickHandler(
        enabled: Boolean = true,
        onTabClick: suspend () -> Unit,
    ) {
        val currentOnTabClick by rememberUpdatedState(onTabClick)
        val currentEnabled by rememberUpdatedState(enabled)
        val coroutineScope = rememberCoroutineScope()
        val handler = remember {
            CurrentTabClickDispatcher.Handler {
                if (currentEnabled) {
                    coroutineScope.launch {
                        currentOnTabClick()
                    }
                }
            }
        }
        val currentTabClickDispatcher = LocalCurrentTabClickDispatcher.current
        DisposableEffect(Unit) {
            currentTabClickDispatcher.addHandler(handler)
            onDispose {
                currentTabClickDispatcher.removeHandler(handler)
            }
        }
    }
    
    class CurrentTabClickDispatcher {
        class Handler(val action: () -> Unit)
    
        private val handlers = mutableListOf<Handler>()
    
        fun addHandler(handler: Handler) {
            handlers.add(handler)
        }
    
        fun removeHandler(handler: Handler) {
            handlers.remove(handler)
        }
    
        fun currentHandler() =
            handlers.lastOrNull()
    }
    
    val LocalCurrentTabClickDispatcher = staticCompositionLocalOf {
        CurrentTabClickDispatcher()
    }
    

    So in the screen you use it like this:

    CurrentTabClickHandler {
        // to your action
    }    
    

    And to perform this action add this code to BottomNavigationItem:

    val currentTabClickDispatcher = LocalCurrentTabClickDispatcher.current
    BottomNavigationItem(
        // ...
        selected = isCurrent,
        onClick = {
            if (isCurrent) {
                when (val handler = currentTabClickDispatcher.currentHandler()) {
                    null -> {
                        // no action is specified - I pop to initial tab screen here
                    }
                    else -> handler.action()
                }
            } else {
                // change tab
            }
        },