My apps performance is pretty good overall, the only part where the FPS drops is when swiping through the HorizontalPager by accompanist.
Each Page has a simple LazyVerticalGrid with 3 fixed columns but the performance is way worse compared to XML.
Here is how the LazyVerticalGrid looks like:
For testing purposes I developed the same setup in XML to compare it using Perfetto and the Android Studio Compiler. The Jetpack Compose version has a lot more Janky frames compared to XML.
Here is the result of compose in Perfetto:
And here XML:
Also the Profiler is showing a bunch of janky frames each time I swipe the HorizontalPager:
I have basic knowledge of Jetpack Compose and also read through the Android Developer Compose Performance guide and I am pretty sure that I am not doing anything stupid which causes a ton of recompositions.
Here is the code I used in my testproject:
Scrollable TabRow + HorizontalPager:
val tabList = listOf("Following", "Trending", "New", "Top", "Most Shared", "Most Saved", "All")
val pagerState: PagerState = rememberPagerState(initialPage = 1)
val coroutineScope = rememberCoroutineScope()
Column(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colors.background)
) {
ScrollableTabRow(
modifier = Modifier.fillMaxWidth(),
backgroundColor = MaterialTheme.colors.surface,
contentColor = Color.White,
edgePadding = 8.dp,
selectedTabIndex = pagerState.currentPage,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
Modifier.pagerTabIndicatorOffset(pagerState, tabPositions),
color = MaterialTheme.colors.primary
)
}
) {
// Add tabs for all of our pages
tabList.forEachIndexed { index, title ->
Tab(
text = { Text(title) },
selected = pagerState.currentPage == index,
onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}
},
)
}
}
HorizontalPager(
state = pagerState,
count = tabList.size
) { page: Int ->
when (page) {
0 -> MyList()
1 -> MyList()
2 -> MyList()
3 -> MyList()
4 -> MyList()
5 -> MyList()
6 -> MyList()
}
}
MyList:
@Composable
fun MyList(){
LazyVerticalGrid(
modifier = Modifier.fillMaxSize(),
columns = GridCells.Fixed(3),
content = {
items(100) { item ->
Button(onClick = { /*TODO*/ }) {
Text(text = "Hello")
}
}
})
}
I have R8 enabled and running in release mode on a Samsung Galaxy s10+. Overall the performance with the setup from above is not making any huge visible FPS drops but its still noticably not as smooth as XML, the problem is that I need a little bit more inside the LazyVerticalGrid items than just a simple button with the words "Hello" . For my setup I need in each item:
- 1x 512x512 webp image
- 2x TextView
- 2x Icons
- 1x Card with elevation
The more I add to the items the more visible the lags get. This is roughly how the items will look like:
Running this inside my Jetpack Compose Horizontal Pager is causing massive lags (10 fps aprox) and is causing a very poor user experience.
Someone from the Jetpack Compose Slack channel suggested defining baseline profiles, as far as I understood correctly those will only boost the startup performance and the first time a library gets used. So unfortunately this will not fix my HorizontalPager performance.
Are there any ideas how to boost the HorizontalPager performance besides switching back to XML?
Currenlty using Compose Version 1.3.0-beta01 and Kotlin 1.7.10. Would be happy if someone could tell me if the code above also causes janky frames on their device.
UPDATE
I created up a Baseline Profile for the Horizontal Pager swipe gesture and tested it with Macrobenchmark. The results look promising:
But unfortunately there is no visible change when sideloading the baseline profile, its still janky.
Here is the Baseline Profile:
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {
@OptIn(ExperimentalBaselineProfilesApi::class)
@get:Rule
val rule = BaselineProfileRule()
@OptIn(ExperimentalBaselineProfilesApi::class)
@Test
fun generate() {
rule.collectBaselineProfile("com.example.composespeedtest") {
startActivityAndWait()
device.wait(Until.hasObject(By.res("horizontal_pager")), 10_000)
val snackList = device.findObject(By.res("horizontal_pager"))
snackList.setGestureMargin(device.displayWidth / 5)
snackList.scroll(Direction.RIGHT, 1f)
device.waitForIdle()
}
}
}