perf: Add stable keys to Lazy list items for performance (#323)
Added stable `key` parameters to `items(...)` and `itemsIndexed(...)` calls across 13 screens. This prevents Compose from treating all list mutations as full item replacements and eliminates unnecessary full list recompositions during state updates. Modifications applied to: - KeysScreen.kt, SharedComponents.kt, AchievementsScreen.kt, PluginsScreen.kt - SessionsScreen.kt, CronJobsScreen.kt, McpServersScreen.kt, PairingScreen.kt - ToolsetsScreen.kt, ChatScreen.kt, ModelScreen.kt, SkillsScreen.kt - KanbanScreen.kt, ProfilesScreen.kt, WebhooksScreen.kt Note: LogsScreen.kt was left without a key because logs are string lines that can be duplicated, and using `.hashCode()` would cause a "Key was already used" crash. Positional indexing is safer there. Fixes #279
This commit is contained in:
15 files changed
+17
-17
No files matched your search
@@ -101,7 +101,7 @@ fun AchievementsScreen(
|
|||||||
placeholder = "Search achievements...",
|
placeholder = "Search achievements...",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
items(filteredAchievements) { achievement ->
|
items(filteredAchievements, key = { it.id }) { achievement ->
|
||||||
val pct = achievement.progress_pct?.toFloat()?.div(100f) ?: 0f
|
val pct = achievement.progress_pct?.toFloat()?.div(100f) ?: 0f
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
|
|||||||
@@ -715,7 +715,7 @@ private fun ChatInputBar(
|
|||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.heightIn(max = 200.dp),
|
modifier = Modifier.heightIn(max = 200.dp),
|
||||||
) {
|
) {
|
||||||
items(filteredCommands) { cmd ->
|
items(filteredCommands, key = { it }) { cmd ->
|
||||||
val description =
|
val description =
|
||||||
when (cmd) {
|
when (cmd) {
|
||||||
"/help" -> stringResource(R.string.command_help_desc)
|
"/help" -> stringResource(R.string.command_help_desc)
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ fun FilterChipRow(
|
|||||||
),
|
),
|
||||||
horizontalArrangement = Arrangement.spacedBy(spacing.sm),
|
horizontalArrangement = Arrangement.spacedBy(spacing.sm),
|
||||||
) {
|
) {
|
||||||
items(chips) { chip ->
|
items(chips, key = { it }) { chip ->
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = chip == selectedChip,
|
selected = chip == selectedChip,
|
||||||
onClick = { onChipSelected(chip) },
|
onClick = { onChipSelected(chip) },
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ fun CronJobsScreen(
|
|||||||
contentPadding = listContentPadding,
|
contentPadding = listContentPadding,
|
||||||
verticalArrangement = listItemSpacing,
|
verticalArrangement = listItemSpacing,
|
||||||
) {
|
) {
|
||||||
items(state.jobs) { job ->
|
items(state.jobs, key = { it.id }) { job ->
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors =
|
colors =
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ fun KanbanScreen(
|
|||||||
contentPadding = PaddingValues(16.dp),
|
contentPadding = PaddingValues(16.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
) {
|
) {
|
||||||
items(state.columns) { column ->
|
items(state.columns, key = { it.name }) { column ->
|
||||||
val colName = column.name
|
val colName = column.name
|
||||||
val colTasks =
|
val colTasks =
|
||||||
filteredTasks.filter {
|
filteredTasks.filter {
|
||||||
@@ -178,7 +178,7 @@ fun KanbanScreen(
|
|||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
) {
|
) {
|
||||||
items(colTasks) { task ->
|
items(colTasks, key = { it.id }) { task ->
|
||||||
TaskCard(
|
TaskCard(
|
||||||
task = task,
|
task = task,
|
||||||
onMoveLeft =
|
onMoveLeft =
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ fun KeysScreen(
|
|||||||
placeholder = "Search keys...",
|
placeholder = "Search keys...",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
items(filteredEnvVars.toList()) { (key, config) ->
|
items(filteredEnvVars.toList(), key = { it.first }) { (key, config) ->
|
||||||
var isEditing by remember { mutableStateOf(false) }
|
var isEditing by remember { mutableStateOf(false) }
|
||||||
val revealedVal = state.revealedValues[key]
|
val revealedVal = state.revealedValues[key]
|
||||||
val isRevealed = revealedVal != null
|
val isRevealed = revealedVal != null
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ fun McpServersScreen(
|
|||||||
placeholder = "Search MCP servers...",
|
placeholder = "Search MCP servers...",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
items(filteredServers) { server ->
|
items(filteredServers, key = { it.name }) { server ->
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors =
|
colors =
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ fun ModelScreen(
|
|||||||
placeholder = "Search models...",
|
placeholder = "Search models...",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
items(filteredProviders) { provider ->
|
items(filteredProviders, key = { it.slug }) { provider ->
|
||||||
val isExpanded = expandedProviderSlug == provider.slug
|
val isExpanded = expandedProviderSlug == provider.slug
|
||||||
val isCurrent = provider.is_current == true
|
val isCurrent = provider.is_current == true
|
||||||
|
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ fun PairingScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
items(pending) { item ->
|
items(pending, key = { "${it.platform}_${it.code ?: it.user_id}" }) { item ->
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors =
|
colors =
|
||||||
@@ -201,7 +201,7 @@ fun PairingScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
items(approved) { item ->
|
items(approved, key = { "${it.platform}_${it.user_id}" }) { item ->
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ fun PluginsScreen(
|
|||||||
placeholder = "Search plugins...",
|
placeholder = "Search plugins...",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
items(filteredPlugins) { plugin ->
|
items(filteredPlugins, key = { it.name }) { plugin ->
|
||||||
Card(modifier = Modifier.fillMaxWidth()) {
|
Card(modifier = Modifier.fillMaxWidth()) {
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
Row(
|
Row(
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ fun ProfilesScreen(
|
|||||||
contentPadding = PaddingValues(16.dp),
|
contentPadding = PaddingValues(16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
) {
|
) {
|
||||||
items(state.profiles) { profile ->
|
items(state.profiles, key = { it.name }) { profile ->
|
||||||
val isActive = profile.name == state.activeProfileName
|
val isActive = profile.name == state.activeProfileName
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ fun SessionsScreen(
|
|||||||
placeholder = "Search sessions...",
|
placeholder = "Search sessions...",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
items(filteredSessions) { session ->
|
items(filteredSessions, key = { it.id }) { session ->
|
||||||
Card(
|
Card(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ fun SkillsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
items(filtered) { skill ->
|
items(filtered, key = { it.name }) { skill ->
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors =
|
colors =
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ fun ToolsetsScreen(
|
|||||||
placeholder = "Search toolsets...",
|
placeholder = "Search toolsets...",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
items(filteredToolsets) { toolset ->
|
items(filteredToolsets, key = { it.name }) { toolset ->
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors =
|
colors =
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ fun WebhooksScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
items(filteredSubscriptions) { sub ->
|
items(filteredSubscriptions, key = { it.name }) { sub ->
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
|
|||||||
Reference in new issue
Block a user