Components

Search pages

Search typed application content with shared chrome and page-scoped history.

In the demo catalog, open Components → SearchPage. Search the component catalog by typing or dictation, then open a demo from its result card. The demo owns those cards and persists its recent queries separately from Settings search. The SearchPage demo uses OnSubmit: press the keyboard Search action to show matches. The floating-search demo and Settings search retain live matching.

AndroidKitSearchPage owns the Search title, floating field, dictation, recent searches, empty states, scrolling, and measured page/IME clearance. Hosts own the result body through a LazyListScope content slot: different topics can use different cards, rows, grouping, and controls. Search chrome has no style or rendering override. Result rows are an intentional host-owned body surface.

Opening the page focuses the search field and requests the software keyboard. This happens once on entry; query, result, and history updates do not refocus the field or reopen a dismissed keyboard. Reopening the page requests focus again.

Search timing

searchMode accepts the same AndroidKitSearchMode.Live / OnSubmit enum as the floating search box and floating action. The page defaults to Live to preserve existing behavior. With OnSubmit, input remains a draft until the nonblank IME Search action. Before the first submission, history remains visible; after submission, the previous results remain visible while editing the next query. Result actions record the query that produced those results. Clearing resets results and returns to history. Selecting history fills the draft and requires submission in OnSubmit. The last submitted query survives saved-state restoration; switching modes resets that stored query.

This page still matches supplied items locally. The timing option does not add a remote-result API. Hosts can already execute remote searches through the floating box/action’s onSearch; fetching and remote result state stay host-owned.

val searchItems = records.map { record ->
    AndroidKitSearchItem(
        key = record.id,
        title = record.title,
        data = record,
        onClick = { onOpenRecord(record) },
        supportingText = record.description,
        searchTerms = record.aliases,
    )
}
AndroidKitSearchPage(
    items = searchItems,
    query = query,
    onQueryChange = onQueryChange,
    recentQueries = recentQueries,
    onRecentQueriesChange = onRecentQueriesChange,
    searchHistoryEnabled = searchHistoryEnabled,
    onSearchHistoryEnabledChange = onSearchHistoryEnabledChange,
    onBack = onBack,
) { matches ->
    items(matches, key = { it.key }) { match ->
        Card(onClick = match.onClick, enabled = match.enabled) {
            Text(match.data.title)
        }
    }
}

Use Compose’s androidx.compose.foundation.lazy.items extension in the result slot. The page supplies its lazy list and content padding; do not nest another vertical scroller or add IME padding. Supply listState when the host needs to control scrolling. voiceInputEnabled = false omits the microphone; dictation uses the existing floating search contract.

Matching and result actions

AndroidKitSearchItem<T> carries the host’s typed domain model in data. Keys must be unique across the page. An optional AndroidKitSearchGroup supplies a stable group key and a host-localized context title; items sharing its key must share its title. The content slot receives matched items in relevance order; the host chooses whether and how to group them.

Matching uses the title, supporting text, group title, and optional aliases in any language. It is case-, accent-, punctuation-, and whitespace-insensitive; every query token must match. Exact titles rank before prefixes, contained titles, supporting text, and aliases/context. Ties keep the input order. A nonblank query containing only punctuation or emoji has no matches. This is local in-memory search; there is no database query or network translation.

Searchable text is normalized once per dataset and reused across queries. Normalization and matching run in cancellable background coroutines. While the current query or dataset is being processed, Kit shows a progress indicator and removes outgoing results so they cannot invoke stale actions. Query or dataset changes cancel superseded work. Results always use the current host data, availability and callbacks; changing only callbacks or availability does not rebuild the text index. Ranking and Live / OnSubmit timing remain the same.

Call the matched item’s onClick from the host result action. Kit records the query before invoking the original callback. Disabled items remain searchable, but their matched callbacks do nothing; the host must also render their disabled state and accessibility semantics. Item content and aliases are host-localized; generic Search and history/empty-state vocabulary are translated by Kit.

Hosts own the query and recent-query state. Kit trims recorded queries, deduplicates them using the matching normalization, moves the newest spelling first, and keeps ten. History changes after IME submission or result activation; typing and selecting an existing recent query do not record it. Individual removal and Clear all use onRecentQueriesChange. Persist the list in the host if it must survive restarts. Save query state in the host when restoration is required; the demo uses rememberSaveable for the query and DataStore for history.

Page-scoped search history

searchHistoryEnabled and onSearchHistoryEnabledChange are required in AndroidKitSearchPage. The compact, single-line Recent searches heading retains its icon toggle beside the title and its separate Clear all action. The original eye-off icon offers turning history off; the eye icon offers turning it on. Its localized accessibility actions are Enable search history and Disable search history. There is no API option to omit this control.

When history contains entries, requesting disable opens a Material 3 confirmation dialog titled Turn off search history?, with the message Your recent searches will be cleared, and new searches won’t be saved. Its actions are Cancel and Turn off. Cancel, Back and outside dismissal preserve history and the enabled state. Confirming disables and clears; empty history disables immediately without a dialog. The confirmation message appears only in the dialog, never as a header subtitle.

After confirmation, disabling immediately removes recent rows, including outgoing animations and accessibility semantics, clears saved queries, and stops recording both IME submissions and result actions. Searching and result actions continue normally. The empty-query body says Search history is disabled. Enabling starts empty; deleted searches do not return. Clear all clears entries while leaving history enabled. Recent-query cards and individual removal retain their existing behavior.

The public page requests an empty list when disabled and calls onSearchHistoryEnabledChange. Hosts must atomically persist clearing and the new enabled state in that callback, and reject writes when their persisted state is disabled. This protects against pending writes and callbacks from other screens. The Kit persistent store enforces this inside DataStore transactions using AndroidKitPersistentSearchHistory.setEnabled; its snapshot exposes enabled.

Persist this preference per stable logical search identifier. Main Settings, About and Settings subpages share one owner and one history preference. Independent content searches retain separate preferences. The absent preference defaults to enabled. Load persisted state before rendering history; initially pass searchHistoryEnabled = false while loading.

This is a source-breaking rename from recentQueriesVisible, onRecentQueriesVisibleChange, snapshot visible, and setVisible. Kit storage version 2 migrates version 1 before exposing data: old hidden histories become disabled and empty on disk; visible histories remain enabled with their queries. Migration markers and unrelated preferences are preserved. The demo similarly clears its previously hidden independent content history before presenting it.

The recent heading, icon toggle and Clear all share a floating Kit surface. The list viewport remains edge to edge, with measured content padding keeping its rows clear of floating controls. Encryption remains an independent storage policy.

Settings integration

AndroidKitSettingsSearchPage uses the same internal page implementation, recent history logic, and matching/ranking helpers. It resolves the complete catalog and shared history from its enclosing AndroidKitSettings owner; individual Settings pages cannot supply a separate catalog or history. The Settings adapter supplies catalog results and renders their original controls through the result slot, retaining the Search settings title, Settings empty message, multilingual lexicon, section context, and in-place pickers, switches, sliders, links, and copy actions. See Settings.