TabRow/Tab Recomposition Issue in Compose Accompanist Pager
T

1

6

I was trying to create a sample Tab View in Jetpack compose, so the structure will be like Inside a Parent TabRow we are iterating the tab title and create Tab composable.

More precise code will be like this.

@OptIn(ExperimentalPagerApi::class)
@Composable
private fun MainApp() {
    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text(stringResource(R.string.app_name)) },
                backgroundColor = MaterialTheme.colors.surface
            )
        },
        modifier = Modifier.fillMaxSize()
    ) { padding ->
        Column(Modifier.fillMaxSize().padding(padding)) {
            val pagerState = rememberPagerState()
            val coroutineScope = rememberCoroutineScope()
            val tabContents = listOf(
                "Home" to Icons.Filled.Home,
                "Search" to Icons.Filled.Search,
                "Settings" to Icons.Filled.Settings
            )
            HorizontalPager(
                count = tabContents.size,
                state = pagerState,
                contentPadding = PaddingValues(horizontal = 32.dp),
                modifier = Modifier
                    .weight(1f)
                    .fillMaxWidth()
            ) { page ->
                PagerSampleItem(
                    page = page
                )
            }

            TabRow(
                selectedTabIndex = pagerState.currentPage,
                backgroundColor = MaterialTheme.colors.surface,
                contentColor = MaterialTheme.colors.onSurface,
                indicator = { tabPositions ->
                    TabRowDefaults.Indicator(
                        Modifier
                            .pagerTabIndicatorOffset(pagerState, tabPositions)
                            .height(4.dp)
                            .background(
                                color = Color.Green,
                                shape = RectangleShape
                            )
                    )
                }
            ) {
                tabContents.forEachIndexed { index, pair: Pair<String, ImageVector> ->
                    Tab(
                        selected = pagerState.currentPage == index,
                        selectedContentColor = Color.Green,
                        unselectedContentColor = Color.Gray,
                        onClick = {
                            coroutineScope.launch {
                                pagerState.animateScrollToPage(index)
                            }
                        },
                        text = { Text(text = pair.first) },
                        icon = { Icon(imageVector = pair.second, contentDescription = null) }
                    )
                }
            }
        }
    }
}

@Composable
internal fun PagerSampleItem(
    page: Int
) {
    // Displays the page index
    Text(
        text = page.toString(),
        modifier = Modifier
            .padding(16.dp)
            .background(MaterialTheme.colors.surface, RoundedCornerShape(4.dp))
            .sizeIn(minWidth = 40.dp, minHeight = 40.dp)
            .padding(8.dp)
            .wrapContentSize(Alignment.Center)
    )
}

And coming to my question is whenever we click on the tab item, the inner content get recompose so weirdly. Im not able to understand why it is happens.

Am attaching an image of the recomposition counts below, please take a look that too, it would be good if you guys can help me more for understand this, also for future developers.

There are two question we have to resolve in this stage

  1. Whether it will create any performance issue, when the view getting more complex
  2. How to resolve this recompostion issue

Thanks alot.

enter image description here

Taryntaryne answered 15/11, 2022 at 17:29 Comment(0)
I
2

… whenever we click on the tab item, the inner content get recompose so weirdly. Im not able to understand why it is happens...

It's hard to determine what this "weirdness" is, there could be something inside the composable your'e mentioning here.

You also didn't specify what the API is, so I copied and pasted your code and integrated accompanist view pager, then I was able to run it though not on an Android Studio with a re-composition count feature.

And since your'e only concerned about the Text and the Icon parameter of the API, I think that's something out of your control. I suspect the reason why your'e getting those number of re-composition count is because your'e animating the page switching.

coroutineScope.launch {
       pagerState.animateScrollToPage(index)
}

Though 'm not able to try this on another Android Studio version with the re-composition feature, I think (though I'm not sure) scrolling to another page without animation will yield less re-composition count.

coroutineScope.launch {
      pagerState.scrollToPage(index)
}

If it still bothers you, the best course of action is to ask them directly, though personally I wouldn't concerned much about this as they are part of an accepted API and its just Text and Icon being re-composed many times by an animation which is also fine IMO.

Now if you have some concerns about your PagerSampleItem stability(which you have a full control), based on the provided code and screenshot, I think your'e fine.

There's actually a feature suggested from this article to check the stability of a composable, I run it and I got this report.

restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun PagerSampleItem(
  stable page: Int
)

Everything about this report is within the article I linked.

Also, your Text and Icon are using String and ImageVector which is stable and immutable (marked by @Immutable) respectively.

So TLDR, IMO your code is fine, your PagerSampleItem is not re-composing in the screenshot.

Insecticide answered 16/11, 2022 at 4:21 Comment(5)
Thank you for the detail response. Still you havent answered why only text recomposes to 1-10 times on click and the icon inside the tab skips.Taryntaryne
they are part of their API , though you can skim down their implementation of them and its difficult to understand the lowest functioning pieces of the Snapshot framework to predict the exact numbers, the only thing thats obvious is mutiple re-composition is expected during compose animationInsecticide
Also, even if we try to understand their API, like I said, its out of our control, either we re-invent a Pager, file an issue to them or just focus on the composables that we have more control over with. The link I provided is a very very good read and a good basis for performance decisions.Insecticide
OK Understood, Well don't wish to re compose a textview without my control. :) Let me accept your answer.Taryntaryne
Thank you, and I highly suggest reading the medium article I linked on my answer, and also this Interpreting Compose Compiler Metrics. I may get back to this topic, once I'm able to run snippets on a latest Android Studio.Insecticide

© 2022 - 2025 — McMap. All rights reserved.