Jetpack Compose: Tabs with swiping

Android Nov 13, 2021

Creating an interface with tabs is quite common in the Android app world.

In the old world, creating a tabbed interface would require adapters and sometimes fragments holding the tab data.

But using Jetpack Compose these complexities belong to the past. Creating a UI with tabs is quick and easy.

Of course, there are things that we were taking for granted in the past that are not (yet) supported natively. The ability to swipe to change tabs is one of those things. But fear not. There are ways to get this kind of functionality using a library.

Building the tabs UI

Creating a tabs strip and switching content when a tab button is selected is quite easy. No need for external dependencies, everything is built-in into Compose.

@Composable
fun tabs() {
    var tabIndex by remember { mutableStateOf(0) } // 1.
    val tabTitles = listOf("Hello", "There", "World")
    Column { // 2.
        TabRow(selectedTabIndex = tabIndex) { // 3.
            tabTitles.forEachIndexed { index, title ->
                Tab(selected = tabIndex == index, // 4.
                    onClick = { tabIndex = index },
                    text = { Text(text = title) }) // 5.
            }
        }
        when (tabIndex) { // 6.
            0 -> Text("Hello content")
            1 -> Text("There content")
            2 -> Text("World content")
        }
    }
}
  1. When recomposition takes place, we need to remember which tab was selected. Otherwise, every time the selected index will switch to the first position.
  2. We use a Column to vertically stack the tab strip row and the tab contents.
  3. This TabRow composable holds the tab strip containing the tab buttons.
  4. Each tab button is represented by a Tab composable.
  5. Customize the text on the tab button (e.g. font, color, etc) by customizing the Text composable here.
  6. Switch contents depending on which tab is selected.

Tabs with swiping

When I see an interface with tabs, my first reaction is to swipe left/right to explore the rest of the tabs. I almost forgot that you can actually click on the tab titles to switch tabs.

This behavior is not included in the previous example of building a basic UI with tabs. And it's not included in Compose in general.

Thankfully, there's the Accompanist library that provides various essential but missing functionality from Compose. The Pager library is what is needed in this case. It provides the ability to swipe on the current tab contents and change tabs.

To install this library, add the following to your build.gradle (check for the latest version):

implementation "com.google.accompanist:accompanist-pager:0.21.2-beta"
implementation "com.google.accompanist:accompanist-pager-indicators:0.21.2-beta"
@ExperimentalPagerApi // 1.
@Preview
@Composable
fun tabsWithSwiping() {
    var tabIndex by remember { mutableStateOf(0) }
    val tabTitles = listOf("Hello", "There", "World")
    val pagerState = rememberPagerState() // 2.
    Column {
        TabRow(selectedTabIndex = tabIndex,
            indicator = { tabPositions -> // 3.
                TabRowDefaults.Indicator(
                    Modifier.pagerTabIndicatorOffset(
                        pagerState,
                        tabPositions
                    )
                )
            }) {
            tabTitles.forEachIndexed { index, title ->
                Tab(selected = tabIndex == index,
                    onClick = { tabIndex = index },
                    text = { Text(text = title) })
            }
        }
        HorizontalPager( // 4.
            count = tabTitles.size,
            state = pagerState,
        ) { tabIndex ->
            Text(
                tabIndex.toString(),
                modifier = Modifier
                    .fillMaxSize()
                    .background(Color.White)
            )
        }
    }
}
  1. This is an experimental API that might change in the future. To acknowledge this, you need to annotate each composable method that uses the Pager library.
  2. Similar to the tab index, the pager state needs to be retained when recomposition takes place.
  3. This is necessary to synchronize the pager with the tab row indicator. Without this, the indicator showing which tab is selected will go out of sync when swiping through the tabs.
  4. This is the pager that allows the swiping of the content. Given the tab index, you can populate the appropriate content for each tab.

You can now have tabs and the ability to swipe between them.

Happy coding!

Tags

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.