RIPDPI's own Compose conventions: ViewModel pattern, Route/Screen split, DataStore->StateFlow, RipDpiThemeTokens. Use for how this app does Compose, not generic Compose API questions (see compose).
中文简介正在准备中。当前显示原文说明。
RIPDPI Compose performance: stability annotations, compiler metrics, LazyColumn key/contentType, per-screen recomposition notes. Use when diagnosing recomposition, stability, or list performance here.
中文简介正在准备中。当前显示原文说明。
查看安装方法安装前可以查看交给 AI 代理的指令内容。
The Compose compiler decides at compile time whether each class is stable (safe to skip recomposition when equal) or unstable (must always recompose). Classes from non-Compose modules (protobuf, java.time, Room entities) are unstable by default unless declared in the stability config.
@Immutable -- all public properties never change after construction. Use for sealed-class hierarchies and pure value types (DiagnosticsUiModels, HistoryUiModels, theme tokens like Color, Spacing, Shape, Surface, Motion).
@Stable -- property reads return equal values between recompositions, or the runtime is notified on change. Use for state holders with callbacks or MutableState (SettingsUiModels: DesyncCoreUiState, ProxyNetworkUiState, etc.).
activities/DiagnosticsUiModels.kt -- 60+ annotations, @Immutable for data,
@Stable for models with callbacks.activities/SettingsUiModels.kt -- @Stable on all section state holders.activities/HistoryUiModels.kt -- @Immutable on all models (pure data).activities/MainViewModel.kt -- @Immutable on sealed states.ui/theme/ -- @Immutable on all token classes.Rule: every new class passed to a @Composable MUST be annotated. Explicit annotation prevents regressions when a field type changes.
app/compose-stability.conf marks external types as stable:
com.poyka.ripdpi.proto.*, com.google.protobuf.GeneratedMessageLitejava.time.{Instant,Duration,LocalDate,LocalDateTime,ZonedDateTime,ZoneId}kotlin.time.Duration, kotlinx.collections.immutable.*com.poyka.ripdpi.data.{TcpChainStepModel,UdpChainStepModel,ActivationFilterModel,NumericRangeModel}Add new non-Compose-module types here rather than wrapping in UI models.
Convention plugin ripdpi.android.compose.gradle.kts enables reports when
CI=true or -Pripdpi.composeReports=true:
./gradlew :app:assembleGithubFullRelease -Pripdpi.composeReports=true
Output: app/build/compose-reports/ and app/build/compose-metrics/.
| File | Purpose |
|---|---|
*-composables.txt | Restartable/skippable status and param stability |
*-classes.txt | Stability of every class seen by the compiler |
*-composables.csv | Machine-readable, diff between commits |
*-module.json | Module summary: skippable %, restartable count |
Look for "not skippable" composables (unstable parameter) and "unstable"
classes (often a List<T> that should be ImmutableList<T>).
TrackRecomposition(tag) -- SideEffect counting recompositions. Already in
HomeScreen. Add to any suspect composable.RecompositionReportEffect(intervalMs) -- periodic logcat dump. Place once
in root composable. Markers: ! (delta>5), !!! (delta>20).adb logcat -s RecomposeTracker:D RecomposeReport:I *:SEnable "Show Recomposition Counts" in the toolbar. High recomposition count with low skip count = hot path that needs investigation.
The project includes androidx-compose-runtime-tracing. Capture a Perfetto
trace and open in ui.perfetto.dev, filter for Compose: slices.
Every items() call MUST provide key. Without keys, Compose recreates item
state on every list change. Also provide contentType when mixing different
item structures -- the slot pool is keyed by content type.
Project status:
HistorySections.kt): key = { it.id }, contentType = { "connection_session" } / { "diagnostics_session" } / { "event" }key = { it.label }, contentType = { "trend" }key = { _, entry -> entry.id }, contentType = { _, _ -> "log_entry" }Avoid new lambda instances inside items {}. Hoist callbacks:
// Bad: new lambda per recompose
items(list, key = { it.id }) { item ->
Row(onClick = { viewModel.onClick(item.id) })
}
// Good: stable outer lambda
val onClick = remember(viewModel) { { id: String -> viewModel.onClick(id) } }
items(list, key = { it.id }) { item ->
Row(onClick = { onClick(item.id) })
}
DiagnosticsScanSection's live-probe preview (livePreviewProbes) is a
remember-memoized reversed().take(N) window over the growing
completedProbes list, so the raw display index is not stable across new
completions. The list key uses liveProbeItemKey(completedProbeCount, previewIndex): Int = completedProbeCount - previewIndex - 1, which resolves
to each probe's own append-order position in the untruncated list rather
than its position in the preview window -- that position never changes once
assigned, and it no longer risks colliding on repeated target/outcome
pairs the way a literal "$index-${probe.target}-${probe.outcome}" key
would. Follow this pattern (a stable position derived from a monotonically
growing count, not the display index) for any other reversed/truncated
live-preview list.
rememberInfiniteTransition for connecting-state pulse -- correctly scoped
to ConnectionState.Connecting, stops when idle.animateColorAsState, animateFloatAsState for press feedback.AnimatedContent for state transitions.TrackRecomposition("HomeScreen").derivedStateOf for event filtering -- correct pattern.beyondBoundsPageCount = 0 -- do not increase it.derivedStateOf for FAB visibility based on scroll position.key but no contentType (fix opportunity).ImmutableList<LogEntry> before UI exposure.entries.size in header causing parent recomposition on each new
log line. Int is stable, but keep parent scope narrow.item(key = "..."), all state holders are @Stable.items() calls with key +
contentType live in HistorySections.kt, not HistoryScreen.kt.derivedStateOf for empty-state detection.-composables.csv against baselinekey and contentTyperemember lambdas passed to LazyColumn itemsImmutableList for list params to @Composable, not List<T>还没有评价。欢迎分享你的使用感受。
无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。
还没有评论。来写第一条吧。
RIPDPI's own Compose conventions: ViewModel pattern, Route/Screen split, DataStore->StateFlow, RipDpiThemeTokens. Use for how this app does Compose, not generic Compose API questions (see compose).
中文简介正在准备中。当前显示原文说明。
ADB-based RIPDPI device/emulator debugging: build/install/launch, logcat filtering, fixture port-forwarding, instrumented tests, crash/ANR triage. Use when debugging on a real device or emulator.
中文简介正在准备中。当前显示原文说明。
RIPDPI Appium launch-contract reference: start routes and permission/service/data presets. Use when a test launches to the wrong screen or a new automation route is added. General flakiness: appium-test-debug.
中文简介正在准备中。当前显示原文说明。
RIPDPI Appium test authoring: page objects, resource-id locators, assertions, wait tiers. Use when writing a new Appium test or page object, or adding coverage for a new screen.
中文简介正在准备中。当前显示原文说明。
RIPDPI Appium failure triage: flaky tests, locator/session/wait issues, screenshot and element-tree debugging. Use when an Appium test fails or is flaky.
中文简介正在准备中。当前显示原文说明。
Add or modify a GitHub Actions job. Use when editing a file under .github/workflows/. Not for running a workflow locally (local-ci-act) or release signing (release-signing).
中文简介正在准备中。当前显示原文说明。