flutterdart

Wrap versus Column in Flutter


Interestingly, if I have to make a list of details with these widgets:

Row(
  mainAxisAlignment:
      MainAxisAlignment.spaceBetween,
  children: [
    Text('some label'),
    Text('some value'),
  ],
),
Row(
  mainAxisAlignment:
      MainAxisAlignment.spaceBetween,
  children: [
    Text('some label'),
    Text('some value'),
  ],
),

Every time I wrap them inside a Wrap or Column in a simpler manner.

I never achieved what I expected if I used Wrap, like this:

Wrap(
  direction: Axis.vertical,
  spacing: 8.0,
  children: [
    Row(
      mainAxisAlignment:
          MainAxisAlignment
              .spaceBetween,
      children: [
        Text('some label'),
        Text('some value'),
      ],
    ),
    Row(
      mainAxisAlignment:
          MainAxisAlignment
              .spaceBetween,
      children: [
        Text('some label'),
        Text('some value'),
      ],
    ),
  ],
),

Unexpected Output:

using_of_wrap

The issue is that the contents are placed on the left side. Even I wrap the Wrap widget with the SizedBox(width: double.infinity, child: ...)

I only achieved what I wanted (i.e., since I am using Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [...],) here) by this approach:

Using of Column() widget instead of Wrap() widget

Column(
  spacing: 8.0,
  children: [
    Row(
      mainAxisAlignment:
          MainAxisAlignment
              .spaceBetween,
      children: [
        Text('some label'),
        Text('some value'),
      ],
    ),
    Row(
      mainAxisAlignment:
          MainAxisAlignment
              .spaceBetween,
      children: [
        Text('some label'),
        Text('some value'),
      ],
    ),
  ],
),

Expected Output:

using_of_column

Take note that the SizedBox(width: double.infinity, child: Column(...)) is redundant for this case, unless there's a use case that suggests it to do so.

Could someone explain it in more detail to understand why this is happening? Understanding this better could prevent implementing the wrong setup if we tend to use this kind of layout outcome.

P.S. The behavior remains the same if executed in DartPad

Here's the minimal, reproducible example:

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(colorSchemeSeed: Colors.blue),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final String title;

  const MyHomePage({super.key, required this.title});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Padding(
        padding: EdgeInsets.symmetric(horizontal: 16.0),
        child: Column(
          spacing: 8.0,
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [Text('some label'), Text('some value')],
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [Text('some label'), Text('some value')],
            ),
          ],
        ),
      ),
    );
  }
}

Solution

  • It has to do with the purpose of the Wrap widget. The widget does not try to expand to the secondary axis, because that's where the subsequent runs will go.

    Let's look at your example: you have set the Wrap axis to be vertical. That means it will start laying widget from top to bottom. However, when it reaches the end bottom of its constraints, it will "break a line" and move to the second run/column.

    Hence, if it tried to expand the Row, it would consume all the space it would try to use on the following runs.