Skip to main content

Tune lazy-list performance

Required dependencies

This page is standalone. Collection policies are part of the base UI contract; no optional performance artifact is required:

build.gradle.kts
repositories { mavenCentral() }

dependencies {
implementation("com.viewcompose:viewcompose-material3-android:0.1.0-alpha01")
implementation("androidx.activity:activity:1.12.4")
implementation("com.google.android.material:material:1.13.0")
}

Add measured collection hints

Create LazyListPerformanceTutorialActivity.kt:

package com.viewcompose.samples.tutorials

import android.os.Bundle
import androidx.activity.ComponentActivity
import com.viewcompose.material3.android.setMaterial3UiContent
import com.viewcompose.ui.modifier.Modifier
import com.viewcompose.ui.modifier.fillMaxSize
import com.viewcompose.ui.modifier.fillMaxWidth
import com.viewcompose.ui.modifier.padding
import com.viewcompose.ui.node.policy.CollectionReusePolicy
import com.viewcompose.ui.node.policy.LazyLayoutPrefetchPolicy
import com.viewcompose.ui.unit.dp
import com.viewcompose.ui.foundation.LazyColumn
import com.viewcompose.ui.foundation.Text

class LazyListPerformanceTutorialActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setMaterial3UiContent {
val rows = List(500) { index -> "Row #${index + 1}" }

LazyColumn(
items = rows,
key = { row -> row },
contentType = { "text-row" },
contentRevision = { row -> row },
prefetchPolicy = LazyLayoutPrefetchPolicy(
nestedInitialPrefetchItemCount = 4,
itemViewCacheSize = 4,
),
reusePolicy = CollectionReusePolicy(
sharePool = true,
mountedTreeCacheSize = 2,
),
modifier = Modifier.fillMaxSize().padding(16.dp),
) { row ->
Text(row, modifier = Modifier.fillMaxWidth().padding(8.dp))
}
}
}
}

Prefetch and cache sizes are bounded renderer policies; they never define business state. Shared pools retain only empty holder shells. The mounted-tree cache retains reset physical trees by contentType and releases them deterministically on eviction. contentRevision does define item semantics: when a captured non-State value changes, its revision must change too. Measure before increasing either cache because larger values consume more memory.

If a stable parent recomposes often and selector scans appear in the profile, introduce a remembered LazyItemsSnapshot at the application's immutable data boundary. It is not a general replacement for an ordinary list: creating a new snapshot on every composition still performs the scan, while retaining one after ordinary data or captures change is incorrect.

Verify the result

Profile scrolling with the default values and with the explicit policy on the same release build and device. Compile the sample with:

./gradlew :samples:tutorials:assembleDebug

Use the Performance guide for benchmark conditions and regression budgets.