0

I am using LazyColumn in my project and it is very slow on scrolling the items. I created a very small project to show what is the problem in my code. It scroll smoothy on Pixel, OnePlus devices, but it's laggy on Samsung device. So what are the things we can improve in this code.

Problem

You can see the scroll is laggy in LazyColumn.

SeeAllView.kt

@Composable
fun SeeAllView(
    viewModel: MainActivityViewModel,
    handleItemSelection: (TestDataModel) -> Unit,
    ctaAction: (CurrentModel) -> Unit,
) {
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(20.dp),
    ) {
        items(viewModel.itemList, key = { it.index }) { item ->
            ListContent(false, item, ctaAction, handleItemSelection)
        }
    }
}

@Composable
fun ListContent(
    useSpaceView: Boolean = true,
    myTestDataItem: TestDataModel,
    ctaAction: (CurrentModel) -> Unit,
    handleItemSelection: (TestDataModel) -> Unit,
) {
    val name = myTestDataItem.name
    val image = myTestDataItem.iconUrl
    val summary = myTestDataItem.summary
    val currentModel = myTestDataItem.eventModel.currentModel

    AnimatedVisibility(visible = useSpaceView) {
        Spacer(Modifier.height(20.dp))
    }
    ListItem(name, image, summary, currentModel, ctaAction) {
        handleItemSelection(myTestDataItem)
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ListItem(
    name: String,
    image: Int,
    summary: String,
    currentModel: CurrentModel,
    ctaAction: (CurrentModel) -> Unit,
    openBottomSheet: () -> Unit
) {
    Card(
        modifier = Modifier
            .fillMaxSize(),
        colors = CardDefaults.cardColors(containerColor = Color.White),
        shape = RoundedCornerShape(20.dp),
        elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
        onClick = { openBottomSheet() },
    ) {
        Column(
            modifier = Modifier.padding(20.dp)
        ) {
            PanelHeaderView(name, image)
            SummaryView(summary)
            CtaView(
                modifier = Modifier
                    .align(Alignment.End),
                currentModel,
                ctaAction,
            )
        }
    }
}

@Composable
private fun PanelHeaderView(testName: String, testImage: Int) {
    Row(
        modifier = Modifier.fillMaxWidth(),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Image(
            modifier = Modifier.weight(0.1f),
            painter = painterResource(testImage),
            contentDescription = null
        )
        AnimatedVisibility(
            visible = testName.isNotEmpty(),
            modifier = Modifier
                .weight(0.8f)
                .padding(start = 20.dp),
        ) {
            Text(
                text = testName,
                maxLines = 1,
                overflow = TextOverflow.Ellipsis
            )
        }
        Icon(
            imageVector = Icons.Filled.ArrowBack,
            contentDescription = null,
            tint = Color.Black
        )
    }
}

@Composable
private fun SummaryView(summary: String) {
    AnimatedVisibility(visible = summary.isNotEmpty()) {
        AndroidView(
            modifier = Modifier.padding(top = 20.dp),
            factory = { context -> TextView(context) },
            update = { textView ->
                with(textView) {
                    text = HtmlCompat.fromHtml(summary, HtmlCompat.FROM_HTML_MODE_COMPACT)
                    setTextColor(ContextCompat.getColor(context, R.color.black))
                    setTextSize(TypedValue.COMPLEX_UNIT_PX, 60F)
                }
            }
        )
    }
}

@Composable
private fun CtaView(
    modifier: Modifier,
    currentModel: CurrentModel,
    ctaAction: (CurrentModel) -> Unit,
) {
    AnimatedVisibility(
        modifier = modifier,
        visible = currentModel.showCtaAction
    ) {
        ClickableText(
            modifier = Modifier.padding(top = 20.dp),
            text = AnnotatedString(currentModel.actionTitle),
            onClick = { ctaAction(currentModel) }
        )
    }
}

I am using latest version of compose, you can find the gradle.kt

dependencies {

    implementation 'androidx.core:core-ktx:1.10.1'
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.9.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    implementation 'androidx.core:core-ktx:1.10.1'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'

    def composeBom = platform "androidx.compose:compose-bom:2023.05.01"
    implementation composeBom
    androidTestImplementation composeBom

    implementation "androidx.compose.ui:ui"
    implementation "androidx.compose.foundation:foundation"
    implementation "androidx.compose.foundation:foundation-layout"
    implementation "androidx.compose.material:material"
    implementation "androidx.compose.material3:material3"
    implementation "androidx.compose.runtime:runtime"
    implementation "androidx.compose.runtime:runtime-livedata"
    implementation "androidx.compose.ui:ui-tooling"
    implementation "androidx.compose.ui:ui-tooling-preview"
    implementation "androidx.lifecycle:lifecycle-viewmodel-compose"
    implementation "androidx.activity:activity-compose:1.7.2"
    androidTestImplementation "androidx.compose.ui:ui-test"
    androidTestImplementation "androidx.compose.ui:ui-test-junit4"
    debugImplementation "androidx.compose.ui:ui-test-manifest"

    implementation "io.insert-koin:koin-android:3.4.0"
    implementation "io.insert-koin:koin-androidx-workmanager:3.4.0"
    implementation "io.insert-koin:koin-androidx-compose:3.4.3"

}

MainActivity

class MainActivity : AppCompatActivity() {

    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
    private val viewModel by viewModel<MainActivityViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setupViewModel()
        setContentView(binding.root)
    }

    private fun setupViewModel() {
        viewModel.requestCompleteLiveData.observe(this) { dataReturned ->
            if (dataReturned) {
                setupView()
            }
        }

        viewModel.createData()
    }

    private fun setupView() {
        binding.composeView.setContent {
            AppBarScaffold(R.string.app_name, true) {
                SeeAllView(viewModel, {}, {})
            }
        }
    }
}

You can find the whole project in here github.

Thanks

Kotlin Learner
  • 3,995
  • 6
  • 47
  • 127
  • Is the Samsung device you're testing on a low-end device? LazyRow/Column are known to suffer from some performance limitations. There was even recently an official Android Developers video where they suggested wrapping a RecyclerView in a Composable to avoid the issue. – Tenfour04 Jun 21 '23 at 13:24
  • @Tenfour04 what do you mean by low-end device ? – Kotlin Learner Jun 21 '23 at 13:50
  • Can you show me which video is suggested wrap a recycler view in compose. I don't know about that. Thanks – Kotlin Learner Jun 21 '23 at 13:51
  • Sorry, I remembered wrong. This is definitely not an official recommendation! Here's the link: https://speakerdeck.com/louiscad/unusable-or-undebuggable-rebelling-against-best-practices – Tenfour04 Jun 21 '23 at 16:08
  • Low-end = not very powerful compared to the others. Slower CPU/GPU devices will show performance problems more obviously. – Tenfour04 Jun 21 '23 at 16:08
  • Thanks for sharing above information. The logic I tried in all samsung device. If device is slower `CPU/GPU` then it cause more scrolling issue. – Kotlin Learner Jun 21 '23 at 16:29

0 Answers0