CPS251 Android Development by Scott Shaper

FlowRow and FlowColumn

Introduction

Have you ever tried to display a list of items that's too wide for your screen? Or maybe you've needed to show multiple columns of content that need to wrap when they run out of space? That's exactly what FlowRow and FlowColumn are designed to handle!

Think of them like text wrapping in a word processor: when text hits the edge of the page, it automatically moves to the next line. FlowRow and FlowColumn do the same thing for your UI elements, making them perfect for creating responsive layouts that adapt to different screen sizes.

Quick Reference

Component Description Common Use
FlowRow Arranges items horizontally with automatic wrapping Tag clouds, button groups, filter chips
FlowColumn Arranges items vertically with automatic column wrapping Image galleries, card layouts, content grids

When to Use FlowRow and FlowColumn

  • When you need items to wrap to new lines/columns automatically
  • For responsive layouts that adapt to different screen sizes
  • When displaying dynamic content that might grow or shrink
  • For creating tag clouds, filter chips, or button groups
  • When building image galleries or card layouts

Important Note: Experimental API

Before we dive into the examples, there's one important thing to know: FlowRow and FlowColumn are part of Jetpack Compose's experimental layout system. This means they work great but might have minor changes in future updates. To use them, you need to add this line above any composable function that uses them:

@OptIn(ExperimentalLayoutApi::class)

What this does: This annotation tells the compiler that you're okay using an experimental API. You must add it above any composable that uses FlowRow or FlowColumn, or your project may not compile.

FlowRow in Action

Let's look at a basic example of FlowRow that displays several items:

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun FlowRowExample() {
    FlowRow(
        modifier = Modifier.padding(16.dp),
        horizontalArrangement = Arrangement.spacedBy(8.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        Text("Item 1", modifier = Modifier.background(Color.LightGray).padding(8.dp))
        Text("Item 2", modifier = Modifier.background(Color.LightGray).padding(8.dp))
        Text("Item 3", modifier = Modifier.background(Color.LightGray).padding(8.dp))
        Text("Item 4", modifier = Modifier.background(Color.LightGray).padding(8.dp))
        Text("Item 5", modifier = Modifier.background(Color.LightGray).padding(8.dp))
        Text("Item 6", modifier = Modifier.background(Color.LightGray).padding(8.dp))
    }
}

What This Example Is Doing

FlowRowExample puts six items ("Item 1" through "Item 6") in a FlowRow with 8 dp of space between items horizontally and between rows. Unlike a regular Row, when the row runs out of horizontal space, the next item wraps to a new line automatically. So on a narrow screen you might see two rows of three items; on a wide screen, more items might fit on the first line. No need to compute line breaks yourself—the layout is responsive.

FlowColumn Example

Here's how FlowColumn works - it's similar to FlowRow but stacks elements vertically and wraps to new columns when needed:

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun FlowColumnExample() {
    FlowColumn(
        modifier = Modifier.padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp),
        horizontalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        Text("Item 0", modifier = Modifier.background(Color.Cyan).padding(8.dp))
        Text("Item 1", modifier = Modifier.background(Color.Cyan).padding(8.dp))
        Text("Item 2", modifier = Modifier.background(Color.Cyan).padding(8.dp))
        Text("Item 3", modifier = Modifier.background(Color.Cyan).padding(8.dp))
        Text("Item 4", modifier = Modifier.background(Color.Cyan).padding(8.dp))
        Text("Item 5", modifier = Modifier.background(Color.Cyan).padding(8.dp))
        Text("Item 6", modifier = Modifier.background(Color.Cyan).padding(8.dp))
        Text("Item 7", modifier = Modifier.background(Color.Cyan).padding(8.dp))
        Text("Item 8", modifier = Modifier.background(Color.Cyan).padding(8.dp))
        Text("Item 9", modifier = Modifier.background(Color.Cyan).padding(8.dp))
    }
}

What This Example Is Doing

FlowColumnExample puts ten items ("Item 0" through "Item 9") in a FlowColumn with 8 dp of space between items vertically and between columns. Items stack vertically first; when they run out of vertical space, the next item flows into a new column to the side. So you get a responsive grid that wraps by column instead of by row, which is useful for tall content or narrow, scrollable areas.

Interactive Example: Dynamic FlowRow

Let's make things more interesting by creating a dynamic FlowRow and FlowColumn that lets users add items at runtime:

FlowRow Part:

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun DynamicFlowRow() {
    // State to track number of items
    var itemCount by remember { mutableStateOf(1) }

    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        // Button to add more items
        Button(onClick = { itemCount++ }) {
            Text("Add Row Item")
        }

        Spacer(modifier = Modifier.height(16.dp))

        // FlowRow automatically wraps items to new rows when they exceed the width
        FlowRow(
            horizontalArrangement = Arrangement.spacedBy(8.dp),  // Space between items horizontally
            verticalArrangement = Arrangement.spacedBy(8.dp)     // Space between rows
        ) {
            // Create items based on itemCount
            for (i in 1..itemCount) {
                Text(
                    "Item $i",
                    modifier = Modifier
                        .background(Color.LightGray)  // Light gray background for visibility
                        .padding(horizontal = 12.dp, vertical = 8.dp)  // Padding inside each item
                )
            }
        }
    }
}

What This Example Is Doing

DynamicFlowRow keeps itemCount in state (starting at 1). It shows an "Add Row Item" button; each click increases itemCount. Below the button, a FlowRow draws that many items ("Item 1," "Item 2," …). So you start with one item and add more; the flow row wraps them to new lines when the width is full. This shows how flow layouts behave when the number or size of items changes at runtime.

FlowColumn Part

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun DynamicFlowColumn() {
    // State to track number of items
    var itemCount by remember { mutableStateOf(1) }

    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        // Button to add more items
        Button(onClick = { itemCount++ }) {
            Text("Add Column Item")
        }

        Spacer(modifier = Modifier.height(16.dp))

        // Box with fixed height to demonstrate flowing behavior
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .height(300.dp)  // Fixed height container
                .background(Color.LightGray.copy(alpha = 0.2f))  // Light background to show container
                .padding(16.dp)
        ) {
            // FlowColumn automatically wraps items to new columns when they exceed the height
            FlowColumn(
                modifier = Modifier.fillMaxWidth(),
                verticalArrangement = Arrangement.spacedBy(8.dp),    // Space between items vertically
                horizontalArrangement = Arrangement.spacedBy(8.dp)   // Space between columns
            ) {
                // Create items based on itemCount
                for (i in 1..itemCount) {
                    Text(
                        "Column $i",
                        modifier = Modifier
                            .background(Color.Cyan)  // Cyan background for visibility
                            .padding(horizontal = 12.dp, vertical = 8.dp)  // Padding inside each item
                    )
                }
            }
        }
    }
}

What This Example Is Doing

DynamicFlowColumn also keeps itemCount in state and has an "Add Column Item" button. The items are drawn inside a fixed-height (300 dp) box so you can see the flow: as you add items, they fill the first column vertically, then wrap into a second column when the height is used. So you get a dynamic, column-wrapping layout that responds to both the container size and the number of items.

How these examples render

The image below shows what the FlowRow and FlowColumn examples look like when you run them: items wrapping to new lines (FlowRow) or new columns (FlowColumn), and the dynamic examples with the add buttons. The snippets above are only part of the code; to see and run the full project, go to my GitHub page and open the chapter5 flow_code.kt file.

FlowRow and FlowColumn Example

Tips for Success

  • Always add the @OptIn(ExperimentalLayoutApi::class) annotation when using these components
  • Use horizontalArrangement and verticalArrangement to control spacing between items
  • Consider using Modifier.padding() to add space around your flow layouts
  • Test your layout with different screen sizes to ensure proper wrapping

Common Mistakes to Avoid

  • Forgetting to add the @OptIn annotation
  • Not considering the minimum width/height of items when they wrap
  • Using flow layouts when a simple Row or Column would suffice
  • Not testing the layout with different content sizes

Best Practices

  • Use flow layouts for dynamic content that might change size
  • Add appropriate spacing between items using arrangement parameters
  • Consider accessibility when items wrap to new lines/columns
  • Test your layout with various screen sizes and orientations