androidkotlinandroid-jetpack-composejetpack-compose-accompanistcompose-recomposition

TabRow/Tab Recomposition Issue in Compose Accompanist Pager


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


Solution

  • … 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.