TikTok like navigation with Jetpack Compose and the ModalBottomSheetLayout in Android
Tags: mobile, android, kotlin, tristan
Published: 2023-04-25 21:41:37

### Table of contents
1. [What we are talking about](#talk)
2. [Getting started](#start)
3. [GitHub](#github)
4. [YouTube](#youtube)


### My app on the Google Playstore
- [HERE](https://play.google.com/store/apps/details?id=com.elliottsoftware.calftracker)

### GitHub code <a name="github"></a>
- [HERE](https://github.com/thePlebDev/CalfTracker)

### YouTube version <a name="youtube"></a>
- [HERE](https://www.youtube.com/watch?v=25w1opXTpmM)



### Introduction <a name="intro"></a>
- This series will be an informal demonstration of any problems I face or any observations I make while developing Android apps. Each blog post in this series will be unique and separate from the others, so feel free to look around.

### What we are talking about <a name="talk"></a>
- If you are like me and you have spent anytime on TikTok you may have noticed that when you are on the profile page, you can click on the little hamburger icon and a little modal pops up from the bottom
- In this tutorial we will try our best at a semi recreation of the designs. 


- TikTok's modal navigation:

![TikTok's modal navigation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vh09jxhxpbvohq9q3fef.png)

- Our recreation:

![The copy of TikTok's modal navigation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fgo3or5g6v083dh1rvkb.png)
- Obviously ours is not going to be a one to one recreation. However, we will be recreating the basic functionality of the little modal popping up from the bottom:

### Getting started <a name="start"></a>

- In Jetpack compose there are two main components to this tutorial:

**1) Scaffold**
**2) ModalBottomSheetLayout**


### Scaffold
- As the documentation states: 

`Compose provides convenient layouts for combining Material Components into common screen patterns. Composables such as Scaffold provide slots for various components and other screen elements.`

-But all we really need to know is the Scaffold is what gives us the nice little hamburger menu:
```
val bottomModalState = rememberModalBottomSheetState(
        initialValue = ModalBottomSheetValue.Hidden,
        skipHalfExpanded = true
    )

Scaffold(
        backgroundColor = MaterialTheme.colors.primary,
        topBar = {
            TopAppBar(
                title = { Text("Calf Tracker") },
                navigationIcon = {
                    IconButton(
                        onClick = {
                            scope.launch {
                                bottomModalState.show()

                            }
                        }
                    ) {
                        Icon(Icons.Filled.Menu, contentDescription = "Toggle navigation drawer")
                    }
                }
            )
        },
    ){
// This is where the ModalBottomSheetLayout is going

}

```
- We will talk more about `bottomModalState` in the next section.

- To get our nice looking TopBar we going to rely on the prebuilt `TopAppBar` given to us  by the `androidx.compose.material` library. All we have to do is to provide a title(Really this is optional) and the hamburger icon with:
```
title = { Text("Calf Tracker") },
                navigationIcon = {
                    IconButton(
                        onClick = {
                            scope.launch {
                                bottomModalState.show()

                            }
                        }
                    ) {
                       //Hamburger Icon
                        Icon(Icons.Filled.Menu, contentDescription = "Toggle navigation drawer") 
                    }
                }

```

- However, If the prebuilt `TopAppBar` does not meet your requirements, you can create your own TopAppBar. Here is a custom one I created myself, with a search bar and scrollable chips:
![Custom Top Bar implementation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yaymwf0h8ble7tiqmsn6.png)


- Full GitHub code found [HERE](https://github.com/thePlebDev/CalfTracker/blob/95fa3382ce28ee75e3a5223af1338eb0b101c9a8/app/src/main/java/com/elliottsoftware/calftracker/presentation/components/main/MainView.kt#L726) and implementation below:
```
@Composable
fun CustomTopBar(chipTextList:List<String>,searchMethod:(String)->Unit){
    Column() {
        Surface(
            modifier = Modifier.fillMaxWidth(),
            color = MaterialTheme.colors.primary,
            elevation = 8.dp
        ) {
            Column() {


                 SearchText(searchMethod= { tagNumber -> searchMethod(tagNumber) })
                    //CHIPS GO BELOW HERE
                    LazyRow(
                        modifier= Modifier
                            .fillMaxWidth()
                            .padding(horizontal = 18.dp, vertical = 8.dp),
                        horizontalArrangement = Arrangement.spacedBy(4.dp),
                    ) {
                        items(chipTextList){
                            Chip(it)
                        }
                    }
            }


        }
        
    }
}

```

### ModalBottomSheetLayout
- As the documentation states: `Modal bottom sheets present a set of choices while blocking interaction with the rest of the screen. They are an alternative to inline menus and simple dialogs, providing additional room for content, iconography, and actions.`

- All we need to know is that it is what gives us the faded background and the popup modal. Inside the Scaffold's content lambda we put:
```
ModalBottomSheetLayout(
            sheetState = bottomModalState,
            sheetContent = {
                ModalContents(
                    onNavigate = onNavigate,
                    bottomModalState = bottomModalState
                )
            }

        ){
            YourComposableFunctionHere()//what the modal covers up
        }

```
- The `bottomModalState` is what represents the state of the modal  and determines if it should be shown or not. The initial starting state of the modal is represented by :
```
val bottomModalState = rememberModalBottomSheetState(
        initialValue = ModalBottomSheetValue.Hidden,
        skipHalfExpanded = true
    )

```
- Through the code block above we are telling the modal its initial state is `Hidden`. If you are wondering what `skipHalfExpanded` does, why don't you set it to false and find out ;)

- The `sheetContent` is what is shown to the user when the modal pops up by `bottomModalState.show()`.
- This can be anything you want but my implementation looks like this:
```
data class ModalNavigation(
    val title:String,
    val contentDescription:String,
    val navigationDestination:Int,
    val icon:ImageVector,
    val key:Int,

)
val navItems = listOf(
    ModalNavigation(
        title ="Settings",
        contentDescription = "navigate to settings",
        navigationDestination = R.id.action_subscriptionFragment_to_settingsFragment,
        icon = Icons.Default.Settings,
        key =0
    ),
    ModalNavigation(
        title ="Calves",
        contentDescription = "navigate to calves screen",
        navigationDestination = R.id.action_subscriptionFragment_to_mainFragment22,
        icon = Icons.Default.Home,
        key =1
    )
)
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ModalContents(
    onNavigate: (Int) -> Unit = {},
    bottomModalState:ModalBottomSheetState

){
    val scope = rememberCoroutineScope()

    Box(
        modifier = Modifier
            .fillMaxWidth()
            .background(MaterialTheme.colors.primary),
        contentAlignment = Alignment.Center


    ){
        LazyVerticalGrid(
            columns = GridCells.Adaptive(minSize = 128.dp)
        ) {

            items(navItems) { navItem ->
                Card(
                    modifier = Modifier
                        .padding(8.dp).clickable {
                            scope.launch {
                                bottomModalState.hide()
                                onNavigate(navItem.navigationDestination)
                            }
                        },
                    backgroundColor = MaterialTheme.colors.secondary,
                    elevation = 8.dp,
                ) {
                    Column(
                        verticalArrangement = Arrangement.Center,
                        horizontalAlignment = Alignment.CenterHorizontally,
                        modifier = Modifier
                            .padding(vertical = 12.dp, horizontal = 4.dp),

                        ){
                        Icon(
                            imageVector = navItem.icon,
                            contentDescription = navItem.contentDescription,
                            modifier = Modifier.size(28.dp)
                        )
                        Text(navItem.title)
                    }
                }
            }
        }
    }
}

```

- If you are using Compose based navigation, then the `navigationDestination ` can be changed to your destination string and the `onNavigate` function will be your compose based navigation function.


### Conclusion
- Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on [Twitter](https://twitter.com/AndroidTristan).