Search code examples
kotlinandroid-jetpack-composenavigation-drawerexpandablecollapsable

Expandable NavigationDrawerItem - Android Jetpack Compose


I am using jetpack compose / Kotlin to create my first android app.

I am using a navigation drawer like so

ModalNavigationDrawer(
    drawerContent = {
        ModalDrawerSheet {
            Text(text = stringResource(id = R.string.app_name), modifier = Modifier.padding(16.dp))
            Divider()
            NavigationDrawerItem(
                label = { Text(text = stringResource(id = R.string.settings_title)) },
                icon = {Icon(
                    Icons.Filled.Settings,
                    contentDescription = stringResource(id = R.string.settings_title)
                )},
                selected = false,
                onClick = { /*TODO*/ }
            )
            NavigationDrawerItem(
                label = { Text(text = stringResource(id = R.string.collections)) },
                icon = {Icon(
                    painterResource(id = R.drawable.baseline_collections_bookmark_24),
                    contentDescription = stringResource(id = R.string.collections)
                )},
                selected = false,
                onClick = { /*TODO*/ }
            )
            // ...other drawer items
        }
    },

    gesturesEnabled = true,
    content = content
)

which gives this

enter image description here

I want to make the second item expandable like in this question, however all resources I find use the view system which as I understand is outdated and which anyway I know nothing about.

How can I implement this using Compose ?


Solution

  • There is a lot of option you can play around showing option.

    I'm showing you one of the simple option to show/hide the list below the option in ModalNavigationDrawer

    I have made this sample using Material3. Checkout the complete code.

    MainActivity.kt

    import android.os.Bundle
    import androidx.activity.ComponentActivity
    import androidx.activity.compose.setContent
    import androidx.compose.foundation.background
    import androidx.compose.foundation.clickable
    import androidx.compose.foundation.layout.Box
    import androidx.compose.foundation.layout.fillMaxSize
    import androidx.compose.foundation.layout.fillMaxWidth
    import androidx.compose.foundation.layout.padding
    import androidx.compose.material3.DrawerValue
    import androidx.compose.material3.MaterialTheme
    import androidx.compose.material3.Surface
    import androidx.compose.material3.Text
    import androidx.compose.material3.rememberDrawerState
    import androidx.compose.runtime.rememberCoroutineScope
    import androidx.compose.ui.Alignment
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.unit.dp
    import com.md.bottomsheetjetpackcompose.ui.theme.BottomSheetJetpackComposeTheme
    import kotlinx.coroutines.launch
    
    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                BottomSheetJetpackComposeTheme {
                    Surface(
                        modifier = Modifier.fillMaxSize(),
                        color = MaterialTheme.colorScheme.background
                    ) {
    
                        val scope = rememberCoroutineScope()
                        val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
    
                        Sidebar(drawerState = drawerState) {
                            Box(
                                modifier = Modifier
                                    .fillMaxWidth()
                                    .background(color = MaterialTheme.colorScheme.primary)
                                    .padding(12.dp)
                                    .clickable {
                                        scope.launch {
                                            drawerState.open()
                                        }
                                    },
                                contentAlignment = Alignment.Center
                            ) {
                                Text(text = "Click To Open Drawer")
                            }
                        }
                    }
                }
            }
        }
    }
    

    ModalNavigation.kt

    import androidx.compose.animation.AnimatedVisibility
    import androidx.compose.foundation.layout.Column
    import androidx.compose.foundation.layout.padding
    import androidx.compose.material.icons.Icons
    import androidx.compose.material.icons.filled.List
    import androidx.compose.material.icons.filled.Settings
    import androidx.compose.material3.Divider
    import androidx.compose.material3.DrawerState
    import androidx.compose.material3.Icon
    import androidx.compose.material3.ModalDrawerSheet
    import androidx.compose.material3.ModalNavigationDrawer
    import androidx.compose.material3.NavigationDrawerItem
    import androidx.compose.material3.Text
    import androidx.compose.runtime.Composable
    import androidx.compose.runtime.mutableStateOf
    import androidx.compose.runtime.remember
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.res.stringResource
    import androidx.compose.ui.unit.dp
    
    @Composable
    fun Sidebar(drawerState: DrawerState, content: @Composable () -> Unit) {
    
        val showSubMenu = remember {
            mutableStateOf(false)
        }
    
        ModalNavigationDrawer(
            drawerState = drawerState,
            drawerContent = {
                ModalDrawerSheet {
                    Text(
                        text = stringResource(id = R.string.app_name),
                        modifier = Modifier.padding(16.dp)
                    )
                    Divider()
                    NavigationDrawerItem(
                        label = { Text(text = stringResource(id = R.string.settings_title)) },
                        icon = {
                            Icon(
                                Icons.Filled.Settings,
                                contentDescription = stringResource(id = R.string.settings_title)
                            )
                        },
                        selected = false,
                        onClick = { /*TODO*/ }
                    )
                    NavigationDrawerItem(
                        label = { Text(text = stringResource(id = R.string.collections)) },
                        icon = {
                            Icon(
                                Icons.Filled.List,
                                contentDescription = stringResource(id = R.string.collections)
                            )
                        },
                        selected = false,
                        onClick = { showSubMenu.value = !showSubMenu.value }
                    )
                    AnimatedVisibility(visible = showSubMenu.value) {
                        SubMenu()
                    }
    
                    // ...other drawer items
                }
            },
    
            gesturesEnabled = true,
            content = content
        )
    }
    
    @Composable
    fun SubMenu() {
        Column ( Modifier.padding(start = 16.dp, end = 24.dp, top = 15.dp)){
            Text(text = "Option 1")
            Text(text = "Option 2")
            Text(text = "Option 2")
        }
    }
    

    Explanation about the code.

    1. I have used AnimatedVisibility composable to show/hide content with default Animation.
    2. Also, take a local variable which will be responsible for storing the state of the menu whether to show or not.

    Preview Result

    result