AndroidKitList renders host-owned visual-only item bodies inside a shared Kit
card surface. Kit owns the full item’s tap, context menu, highlight, swipe,
selection check, semantics and animation. Supply the item’s body without another
card wrapper or outer padding; Kit wraps the complete body and its selection indicator together.
Do not place clickable controls, pointer/key handlers, menus or selectable text
inside the body. This body slot is an explicit exception to sealed item rendering;
it does not open Kit chrome, interaction feedback or control styling to hosts.
val eligibleIds = records.filter { it.enabled && it.selectable }.map { it.id }.toSet()
val list = rememberAndroidKitListState(eligibleIds)
val selection = AndroidKitListSelection(list.selection, onActionError = onError) {
icon(deleteIcon, deleteLabel, destructive = true) { ids ->
// Await confirmation and persistence. Cancellation/failure retains selection.
deleteRecords(ids)
}
overflow {
item(shareLabel, icon = shareIcon) { ids ->
shareRecords(ids)
AndroidKitListActionResult.Success
}
}
}
AndroidKitPage(
title = title,
selection = selection,
actions = listOf(AndroidKitTextAction(selectLabel, { list.selection.activate() })),
) { clearance ->
AndroidKitList(
items = records,
key = { it.id },
state = list,
contentPadding = clearance,
normalItemPadding = PaddingValues(AndroidKitThemeTokens.dimensions.spaceMedium),
pinAction = { record -> AndroidKitListPinAction(record.isPinned, { onPinnedChange(record.id, it) }) },
pinningEnabled = pinningAvailable,
enabled = { it.enabled },
selectable = { it.selectable },
onItemClick = onOpen,
contextMenu = { record -> {
item(shareLabel, icon = shareIcon, onClick = { onShare(record.id) })
} },
deleteAction = { record -> AndroidKitListDeleteAction({ onDelete(record.id) }) },
swipeEnabled = false, // Keep menu Delete without swipe gestures.
) { record ->
Column {
Text(record.title)
Text(record.description)
}
}
}
View and scrolling
normalItemPadding sets host-owned item body padding inside the shared card in
normal mode, for both List and Grid. It defaults to zero to preserve existing
normal-mode layouts. Move the body’s outer padding into this argument; the body
must not apply another outer inset or card wrapper.
Selection mode ignores normalItemPadding. Kit applies 0 dp at the body start
beside the List check and 16 dp at the end, top and bottom. The check remains
centered in its 48 dp gutter, giving a 14 dp check-to-body gap. Grid selection
uses 16 dp on every side and keeps its check overlay at the card’s top-start.
Changes to normal padding while selecting apply when selection closes.
Internal spacing between elements inside the host body remains host-owned.
Entering and leaving selection use coordinated, non-bouncy springs for gutter, body padding and card size, with a 250 ms check fade. List opens and closes its gutter while the item content shifts. Grid fades its overlaid checks without introducing a gutter; body padding transitions from host normal padding to Kit selection padding. Springs preserve movement velocity when selection reverses mid-transition. The final padding and gutter values above remain unchanged. Selection input and semantics switch immediately, while the outgoing check keeps its visual state until it fades out. Compose applies the device’s animation duration scale to these transitions.
AndroidKitPage supplies Kit-owned start/end margins along with its measured
system/chrome clearance. Pass its padding directly to AndroidKitList.contentPadding;
the list/grid does not add a second horizontal margin. A standalone list uses
only the contentPadding supplied by its owner.
Hosts switch list.view between AndroidKitListView.List and .Grid. List is the
default. Use the complete AndroidKitList component in either mode; Kit owns the
lazy container, item spacing and adaptive grid columns. Selection
belongs to the logical list and survives a view switch. List and grid keep separate
scroll states. Grid uses Kit’s adaptive column count. Grid has no swipe deletion. Its menu
Delete and selection actions remain available.
Stable, nonblank, unique String IDs are required. Filtering/hiding is represented
by removing items from the supplied collection. Keyed Compose animateItem handles
insertions, removals and movement; selection feedback and chrome use brief transitions.
Compose honors the system animation duration scale.
The low-level lazy-list/grid item helpers are internal. Hosts cannot attach Kit list interactions to a separately configured lazy container or override grid column geometry. Supply a complete eligible ID set to the list state above the lazy viewport, including off-screen items. Match those IDs to the collection’s enabled/selectable items; deleted, hidden or filtered-out IDs must be removed from the set.
Migration from 0.1.59
Replace host-owned LazyColumn/LazyVerticalGrid containers using
androidKitListItems with AndroidKitList and rememberAndroidKitListState.
Pass the page padding directly as contentPadding, keep item bodies visual-only,
and share the state’s selection with page and navigation chrome. Remove
gridColumns; Kit determines the adaptive grid geometry. Both List and Grid
modes remain available, with their existing mode-specific interaction behavior.
Move existing outer item-body padding to normalItemPadding. Selection padding
is now applied once by Kit; remove mode-dependent padding from item bodies.
Selection and surrounding chrome
Long press enters selection with the pressed item selected when selection is
available and the item is enabled/selectable. This applies in List and Grid,
including items with no primary click or menu. Set selectionAvailable = false
in rememberAndroidKitListState to use long press for the item context menu.
Secondary mouse click and Menu/Shift+F10 still open the menu in normal mode.
Hosts can also activate selection explicitly.
activate(setOf(id)) can start with an item selected. activate() starts with zero.
Selecting none leaves the mode open. Select all toggles all/none over eligible
supplied items, including off-screen items; it does not include unloaded records.
New IDs are unselected. Removed or ineligible IDs are pruned.
Select all and the selected count share one pill. Its checkbox has two states:
checked when every eligible item is selected, unchecked otherwise. Tapping the
pill with a partial selection selects all eligible items; tapping it when all
are selected clears the selection. Items use the same circled check icon when
selected and the same empty circle when unselected. The checked circle has a
filled background with a contrasting checkmark. Theme these shared colors through
componentColors.listSelection; this also covers the Select all pill.
List items place the icon centered inside the shared card surface’s 48 dp
leading gutter; Kit pads the body separately. Grid items overlay it at the card’s
top-start corner without narrowing the body; the corner follows layout direction.
Share the same AndroidKitListSelection with Page or SearchPage and the enclosing
AndroidKitFloatingNavigation. These owners replace the header, suppress ordinary
actions/search/floating controls and hide compact or expanded navigation. Suppression
is explicit: host-owned controls outside these owners must observe state.isActive.
The selection header ignores immersive title-bar hiding. Search retains its query
and scroll position, clears focus/IME on entry and hides recent-history controls.
X, system Back and Escape clear selection. While a bulk action runs, these exits,
row toggles, Select all and other bulk actions are disabled. Callbacks receive an
immutable selected-ID snapshot. Return Success only after successful completion;
it clears and closes selection. Cancelled and Failure() retain it.
Failure(remainingIds) retains only a subset of the submitted IDs, reconciled
against current eligibility. The required onActionError callback receives unexpected
exceptions; busy state is released. Hosts own confirmation, persistence, undo and
failure messages, and should use their lifecycle-aware state holder for durable work.
The UI coroutine is cancelled when its action bar leaves composition. Cancellation
never reports success.
Declare the corresponding bulk actions in AndroidKitListSelection so the
selection action bar offers the item menu’s operations for the selected IDs.
The demo shares Pin/Unpin, Share and Delete across these two surfaces. Item menu
callbacks remain single-item operations; bulk callbacks retain their explicit
completion, cancellation and failure contract.
Selection is deliberately not saved: recreation starts in normal mode. Hoist the owner per logical page, not into a retained singleton or ViewModel. Normal navigation away disposes the page owner. Selection does not persist data.
Pinning
Supply a typed AndroidKitListPinAction(pinned, onPinnedChange, enabled) for each
item that supports pinning. Kit groups pinned items at the top beneath a localized
Pinned heading, with a divider before the remaining items. Source order is preserved
within each group. The heading and divider span the full adaptive grid width, so
remaining items start on a new row. No empty Pinned section is shown, and no item
pin icon or trailing pin space is reserved.
The Pinned heading has a 20 dp leading pin icon with an 8 dp icon-to-title gap. Its gap to the first item row is 8 dp; spacing between item rows remains 16 dp.
Kit adds a localized Pin or Unpin menu entry with the corresponding icon and a custom accessibility action. Pin state, persistence and source order remain host-owned; Kit groups the supplied items without mutating their data. Supply items in their normal source order; the demo delegates grouping to the component. Do not duplicate the section or menu entry in the body or host menu.
pinningEnabled defaults to true and independently controls the Kit section,
menu entry and accessibility action in List and Grid. Setting it to false leaves
the supplied host state untouched, renders the supplied source order and retains
unrelated menu/Delete/swipe behavior.
An item without a pin action has no pin UI. An action with enabled = false keeps
the item in the Pinned section but disables Pin/Unpin. No section rendering or
geometry overrides are exposed. Section colors follow the supported
componentColors.sectionCard secondary content and divider colors.
Selection keeps the Pinned section. Supply corresponding bulk Pin/Unpin actions
through AndroidKitListSelection, and condition those actions on the same
availability flag. The component cannot infer bulk persistence or confirmation
from a single-item callback.
Delete and accessibility
Declare a typed AndroidKitListDeleteAction once. Kit appends a localized, destructive
Delete entry to the item menu. swipeEnabled independently controls both swipe
directions in List view and defaults to true to preserve existing callers.
Setting it to false retains menu and accessibility Delete. Disabled
or missing Delete disables swipe. Kit invokes the host callback and resets a retained
row; it does not remove records or assume confirmation succeeded.
Menu highlight and selection feedback remain visible above opaque host backgrounds. Touch-and-hold and its accessibility action select an eligible item when selection is available, otherwise they open the shared context menu. Secondary mouse click and Menu/Shift+F10 open that menu in normal mode. Delete also has a custom accessibility action. Selection uses full-row checkbox semantics, with a two-state Select all/count pill and a localized selected-count description on that control. The leading gutter follows layout direction.
The demo catalog includes one interactive List showcase with List/Grid switching, long-press and host-triggered selection, menu and swipe deletion, confirmation, Pin/Unpin and bulk actions. Pinned items appear in the component’s top section while retaining their relative order; Unpin restores their source order in the remaining group and uses the crossed-out pin menu icon. Open the header’s settings icon (Interactive scenarios) to toggle Selection mode, Pin and Swipe to delete independently. With Pin off, the demo hides bulk pin actions and uses normal record order while retaining pin state; switching it on restores the Pinned section. With Selection mode off, long press opens the item menu and the explicit Select action is omitted. All switches start on and retain their values across view changes. Swipe to delete is unavailable in Grid; its preference is retained for returning to List. See component ownership.