This is a Kotlin Multiplatform project targeting Android, iOS.
-
/composeApp
is for code that will be shared across your Compose Multiplatform applications. It contains several subfolders:commonMain
is for code thatβs common for all targets.- Other folders are for Kotlin code that will be compiled for only the platform indicated in the folder name.
For example, if you want to use Appleβs CoreCrypto for the iOS part of your Kotlin app,
iosMain
would be the right folder for such calls.
-
/iosApp
contains iOS applications. Even if youβre sharing your UI with Compose Multiplatform, you need this entry point for your iOS app. This is also where you should add SwiftUI code for your project.
Learn more about Kotlin Multiplatform
A Jetpack Compose implementation of a contextual flow layout with overflow handling, built as a replacement for the deprecated ContextualFlowRow
component. This sample demonstrates how to create a smart, responsive layout that automatically shows a "X+ more" chip when content overflows.
This solution uses Layout
and composes all list items, irrespective of whether they'll be displayed or not.
Because this implementation does not rely on SubComposeLayout
, it becomes more performant for shorter lists, which is typically the use case for chip arrangements like the one portrayed in the example.
As the list grow the more we could've benefited from using an implementation that leverages SubComposeLayout
to avoid composing all items straight away.
π¨ Why this exists: With the deprecation of official flow components, this sample provides a production-ready alternative with enhanced overflow management.
- π― Smart Overflow Detection - Automatically detects when items don't fit
- π± Responsive Layout - Adapts to different screen sizes and orientations
- π’ Dynamic Counting - Shows exact number of hidden items
- π Bottom Sheet Integration - Expandable view for remaining items
- π¨ Customizable Styling - Flexible theming and spacing options
- π§© Decoupled Architecture - Reusable components with clean separation
Main View | Bottom Sheet |
---|---|
![]() |
![]() |
Sample showing programming languages with overflow handling
The ContextualFlowRow
uses a custom Layout
composable that implements a sophisticated measurement algorithm:
@Composable
fun <T> ContextualFlowRow(
items: List<T>,
maxLines: Int = Int.MAX_VALUE,
onMoreClick: (List<T>) -> Unit = {},
itemContent: @Composable (T) -> Unit,
)
// Create a template "99+ more" chip for width calculation
AssistChip(
onClick = { },
label = { Text("99+ more") },
// ... styling
)
The algorithm pre-measures a template chip to reserve space for the overflow indicator, ensuring consistent layout behavior.
private fun <T> calculateLayout(
items: List<T>,
placeables: List<Placeable>,
moreChipWidth: Int,
maxWidth: Int,
maxLines: Int,
spacing: Int,
verticalSpacing: Int,
): LayoutResult
The algorithm works in phases:
Phase 1: Greedy Item Placement
- Places items left-to-right, top-to-bottom
- Tracks current position
(currentX, currentY)
- Moves to next line when width exceeded
Phase 2: Overflow Detection
- When reaching
maxLines
, checks if more items exist - Calculates space needed for "X+ more" chip
- Removes items from current line until chip fits
Phase 3: Smart Backtracking
// If "more" chip doesn't fit, remove items from current line
while (currentX + moreChipWidth > maxWidth && lineItems.isNotEmpty()) {
val removedItem = lineItems.removeLast()
currentX -= (removedItem.width + spacing)
removedFromLine++
}
val remainingCount = maxOf(0, items.size - visibleItemsCountRef.intValue)
Text("$remainingCount+ more")
βββββββββββββββββββββββ βββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββ
β ContextualFlowRow βββββΆβ RemainingItemsModal βββββΆβ RemainingItemsModalBottomSheet- β
β β β BottomSheet β β Content β
β β’ Layout Logic β β β’ Modal Management β β β’ Content Display β
β β’ Overflow Detectionβ β β’ State Handling β β β’ Item Rendering β
β β’ Chip Measurement β β β’ Sheet Lifecycle β β β’ Flow Layout β
βββββββββββββββββββββββ βββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββ
- Template-Based Measurement: Pre-measures overflow chip for consistent spacing
- Ref-Based Counting: Uses
mutableIntStateOf
to avoid recomposition loops - Callback-Driven: Parent manages bottom sheet state for flexibility
- Generic Implementation: Works with any data type
<T>
@Composable
fun ProgrammingLanguageScreen() {
var showBottomSheet by remember { mutableStateOf(false) }
var remainingItems by remember { mutableStateOf<List<ProgrammingLanguage>>(emptyList()) }
ContextualFlowRow(
items = programmingLanguages,
maxLines = 2,
onMoreClick = { remaining ->
remainingItems = remaining
showBottomSheet = true
}
) { language ->
FilterChip(
onClick = { /* handle selection */ },
label = { Text(language.name) }
)
}
RemainingItemsModalBottomSheet(
items = remainingItems,
isVisible = showBottomSheet,
onDismiss = { showBottomSheet = false }
) { language ->
FilterChip(/* same chip styling */)
}
}
ContextualFlowRow(
items = items,
maxLines = 3, // Allow up to 3 rows
horizontalSpacing = 12.dp, // Space between items
verticalSpacing = 8.dp, // Space between rows
onMoreClick = { remaining -> ... }
) { item ->
// Custom item content
}
- Single-Pass Layout: Measures all items once, calculates layout in one pass
- Efficient Recomposition: Uses refs to minimize state-driven recompositions
- Memory Efficient: Only stores positions and counts, not full item lists
- Empty Lists: Gracefully handles empty item collections
- Single Item: Works correctly with just one item
- Narrow Screens: Adapts to very small widths
- Long Text: Handles varying item widths elegantly
- Semantic Actions: Proper click handling for screen readers
- Content Descriptions: Clear labeling of overflow actions
- Navigation Support: Full keyboard and D-pad navigation
- Pre-measuring Template Components prevents layout shifts
- Backtracking Algorithms ensure consistent visual balance
- State Management Outside Layout avoids infinite recomposition
- Generic Type Safety enables reuse across different data types
Layout(
modifier = modifier,
content = {
// All possible content (items + template)
}
) { measurables, constraints ->
// Measure once, place optimally
val result = calculateLayout(...)
layout(result.totalWidth, result.totalHeight) {
// Place all items at calculated positions
}
}
- Tag/Chip Lists: Skills, categories, filters
- Navigation Menus: Breadcrumbs, tabs, quick actions
- Content Previews: Image galleries, article lists
- Selection Components: Multi-select dropdowns, choice chips
This is a sample implementation. Feel free to:
- π Report issues or edge cases
- π‘ Suggest algorithmic improvements
- π¨ Contribute styling enhancements
- π Improve documentation
This project is licensed under the MIT License - see the LICENSE file for details.
Feel free to use this implementation in your projects!
π‘ Pro Tip: This implementation showcases advanced Compose Layout techniques. Study the
calculateLayout
function to understand custom layout algorithms in Jetpack Compose!