flutter / flutter/flutter

Feature Request: Support for null returns in widget trees to properly handle spacing in layouts

Open
#165,566 22 comments 7 reactions 0 assignees View on GitHub
a: layout c: new feature c: proposal framework P3 team-framework triaged-framework
Dominant language
Dart
Stars
179k
Forks
31.1k
PR merge metrics
PR metrics pending

Description

## Summary
Request to support `null` as a valid return value from `build()` methods, allowing widgets to be completely removed from layout calculations rather than rendering zero-sized boxes that still affect spacing in layouts.

## Use Case
With the introduction of the `spacing` parameter in widgets like `Row` and `Column`, there's an issue with conditionally rendered widgets. Currently, when trying to conditionally hide a widget, developers use `SizedBox.shrink()` or empty `Container()`, but these still participate in spacing calculations even though they have zero size.

### Example problems:

1. When using `Column` with `spacing: 32.0`, even if some children are `SizedBox.shrink()`, the spacing will still be applied:

```dart

Column(
spacing: 32.0,
children: [
Text("First"),
// on this usecase we have more control and the user can handle by just filtering nonNulls
showSecond ? Text("Second") : SizedBox.shrink(), // Still creates spacing gap!
Text("Third"),
],
)
```

2. When using conditional logic with `StatelessWidget` or `StatefulWidget` that might return zero-sized widgets:

```dart
class ConditionalWidget extends StatelessWidget {
final bool condition;

@override
Widget build(BuildContext context) {
if (!condition) {
return SizedBox.shrink(); // Will still affect spacing in parent widgets
}
return MyActualWidget();
}
}
```

## Proposed Solution

Allow `null` to be returned from `build()` methods and support `null` values in widget children lists to completely remove elements from layout calculations.

```dart
class ConditionalWidget extends StatelessWidget {
final bool condition;

@override
Widget? build(BuildContext context) {
if (!condition) {
return null; // Remove from parent layout completely
}
return MyActualWidget();
}
}
```

And in widget collections:

```dart
Column(
spacing: 32.0,
children: [
Text("First"),
ConditionalWidget(), // No spacing applied when null
Text("Third"),
],
)
```

Image

```dart
import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

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

// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Example'),
);
}
}

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

final String title;

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

class _MyHomePageState extends State {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: SingleChildScrollView(
child: Row(
spacing: 100,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
children: [
Text('Spacing 32'),
Column(
spacing: 32,
children: [
for (int i = 1; i <= 10; i++) ...[
// 32 spacing still applies, even if the widget does not want to render anything
_ShowEvenOnly(i),

// 32 spacing still applies, even if the widget does not want to render anything
_ShowOddOnly(i),
],
],
),
],
),
Column(
children: [
Text('Spacing 0'),
Column(
spacing: 0,
children: [
for (int i = 1; i <= 10; i++) ...[
// 0 spacing still applies, even if the widget does not want to render anything
_ShowEvenOnly(i),

// 0 spacing still applies, even if the widget does not want to render anything
_ShowOddOnly(i),
],
],
),
],
),
],
),
),
);
}
}

class _ShowEvenOnly extends StatelessWidget {
const _ShowEvenOnly(this.value);

final int value;

@override
Widget build(BuildContext context) {
return value % 2 == 0
? Text('Even number: $value')
: const SizedBox.shrink();
}
}

class _ShowOddOnly extends StatelessWidget {
const _ShowOddOnly(this.value);

final int value;

@override
Widget build(BuildContext context) {
return value % 2 != 0
? Text('Odd number: $value')
: const SizedBox.shrink();
}
}

```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.