mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 02:29:55 +02:00
docs: add compose development guidelines and skills
Added a comprehensive collection of Compose development guidelines, best practices, and skill definitions in the .codex directory. Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
161
.codex/skills/compose-skill/references/lists-grids.md
Normal file
161
.codex/skills/compose-skill/references/lists-grids.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# Lists & Grids
|
||||
|
||||
Compose patterns for lazy layouts, applied within MVI architecture.
|
||||
|
||||
## LazyColumn and LazyRow
|
||||
|
||||
Only compose visible items — use for large or dynamic lists. For small fixed lists (<10 items), prefer `Column`/`Row`.
|
||||
|
||||
```kotlin
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { HeaderSection() }
|
||||
items(items = users, key = { it.id }) { user ->
|
||||
UserRow(user = user, onOpen = onOpenUser)
|
||||
}
|
||||
item { FooterSection() }
|
||||
}
|
||||
```
|
||||
|
||||
### DSL patterns
|
||||
|
||||
- `item { }` — single composable (header, footer, divider)
|
||||
- `items(list, key) { }` — from a list with stable keys
|
||||
- `itemsIndexed(list) { index, item -> }` — when index is needed
|
||||
|
||||
## Keys
|
||||
|
||||
Always provide stable, unique keys when the list can change.
|
||||
|
||||
```kotlin
|
||||
// GOOD: stable domain ID
|
||||
items(users, key = { it.id }) { user -> UserRow(user) }
|
||||
|
||||
// BAD: index-based — state corrupts on reorder/remove
|
||||
items(users, key = { index }) { user -> UserRow(user) }
|
||||
|
||||
// BAD: no key — Compose can't distinguish items reliably
|
||||
items(users) { user -> UserRow(user) }
|
||||
```
|
||||
|
||||
**Rule:** Use domain IDs, not indices. Without stable keys, removing an item corrupts the state of remaining items.
|
||||
|
||||
## ContentType for Recycling
|
||||
|
||||
Use `contentType` when rendering different item types to enable layout reuse:
|
||||
|
||||
```kotlin
|
||||
sealed class FeedItem {
|
||||
data class Header(val title: String) : FeedItem()
|
||||
data class Post(val id: String, val content: String) : FeedItem()
|
||||
}
|
||||
|
||||
LazyColumn {
|
||||
items(
|
||||
items = feedItems,
|
||||
key = { when (it) { is FeedItem.Header -> it.title; is FeedItem.Post -> it.id } },
|
||||
contentType = { when (it) { is FeedItem.Header -> "header"; is FeedItem.Post -> "post" } }
|
||||
) { item ->
|
||||
when (item) {
|
||||
is FeedItem.Header -> SectionHeader(item.title)
|
||||
is FeedItem.Post -> PostCard(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Without `contentType`, all items compete for one reuse pool. With it, items reuse layout state efficiently within their type.
|
||||
|
||||
## Grids and Pager
|
||||
|
||||
### LazyVerticalGrid
|
||||
|
||||
```kotlin
|
||||
// Fixed columns
|
||||
LazyVerticalGrid(columns = GridCells.Fixed(3)) {
|
||||
items(items, key = { it.id }) { item -> GridItem(item) }
|
||||
}
|
||||
|
||||
// Adaptive columns (responsive) — preferred for responsive layouts
|
||||
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 120.dp)) {
|
||||
items(items, key = { it.id }) { item -> GridItem(item) }
|
||||
}
|
||||
```
|
||||
|
||||
### LazyVerticalStaggeredGrid
|
||||
|
||||
For Pinterest-style variable-height layouts:
|
||||
|
||||
```kotlin
|
||||
LazyVerticalStaggeredGrid(columns = StaggeredGridCells.Fixed(2)) {
|
||||
items(images, key = { it.id }) { image -> ImageCard(image) }
|
||||
}
|
||||
```
|
||||
|
||||
### HorizontalPager / VerticalPager
|
||||
|
||||
```kotlin
|
||||
val pagerState = rememberPagerState(pageCount = { pages.size })
|
||||
|
||||
HorizontalPager(state = pagerState) { page ->
|
||||
PageContent(pages[page])
|
||||
}
|
||||
|
||||
// Programmatic scroll
|
||||
LaunchedEffect(targetPage) { pagerState.animateScrollToPage(targetPage) }
|
||||
```
|
||||
|
||||
## Scroll State and Derived Logic
|
||||
|
||||
```kotlin
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
// GOOD: derivedStateOf for scroll-dependent UI
|
||||
val showScrollToTop by remember {
|
||||
derivedStateOf { listState.firstVisibleItemIndex > 2 }
|
||||
}
|
||||
|
||||
LazyColumn(state = listState) {
|
||||
items(items, key = { it.id }) { item -> ItemRow(item) }
|
||||
}
|
||||
|
||||
if (showScrollToTop) {
|
||||
FloatingActionButton(onClick = { scope.launch { listState.animateScrollToItem(0) } }) {
|
||||
Icon(Icons.Default.ArrowUpward, contentDescription = "Scroll to top")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Keep `LazyListState` local — do not put scroll position in the MVI ViewModel state.
|
||||
|
||||
## Nested Scrolling
|
||||
|
||||
```kotlin
|
||||
// BAD: verticalScroll inside LazyColumn — two scroll containers fight for input
|
||||
LazyColumn {
|
||||
item {
|
||||
Column(Modifier.verticalScroll(rememberScrollState())) { /* conflict */ }
|
||||
}
|
||||
}
|
||||
|
||||
// OK: nested LazyRow inside LazyColumn (different axes)
|
||||
LazyColumn {
|
||||
item { LazyRow { items(horizontalItems) { HorizontalCard(it) } } }
|
||||
items(verticalItems) { VerticalRow(it) }
|
||||
}
|
||||
```
|
||||
|
||||
For complex scenarios, use `Modifier.nestedScroll()` with a custom `NestedScrollConnection`.
|
||||
|
||||
## List Anti-Patterns
|
||||
|
||||
| Anti-pattern | Fix |
|
||||
|---|---|
|
||||
| No keys on mutable lists | Always provide stable domain ID keys |
|
||||
| Index-based keys | Use `it.id`, not position index |
|
||||
| Expensive computation inside item lambda | Compute upstream in reducer, pass pre-computed data |
|
||||
| Inline `filter`/`sort` inside `items {}` | Sort/filter in reducer or ViewModel before emitting state |
|
||||
| `LazyColumn` for 5 fixed items | Use `Column` for small fixed lists |
|
||||
| Creating new objects in `key` lambda | Use primitive stable identifiers |
|
||||
| Missing `contentType` on multi-type lists | Provide `contentType` for efficient reuse |
|
||||
|
||||
For paginated lists with network/database loading, see [Paging 3](paging.md).
|
||||
Reference in New Issue
Block a user