Feature Request: Support for null returns in widget trees to properly handle spacing in layouts
- 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"),
],
)
```
```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
Assessment
This issue has not been assessed yet.