skip to content

In Dart, how do collection if elements and the spread operator let a Flutter build method assemble a children list without a temporary list?

level: juniorimportance: must knowfreq 72%

answer

  1. control flow inside the literal
  2. zero or one element, optional else
  3. ... inserts every element in place
  4. if (isAdmin) ...[a, b] adds a group
  5. replaces add and addAll on a local

basics

~20 s

A collection if element adds its element only when the condition is true, and a spread ... inserts every element of another iterable in place, so a whole children list, conditional groups included, is one declarative literal instead of a mutable list.

solid answer

~40 s

Since Dart 2.3 a collection literal can hold control-flow elements. `if (cond) widget` contributes zero or one element (with `else`, exactly one of two), and `...other` inserts all elements of an `Iterable` at that position. Combined as `if (isAdmin) ...[divider, usersTile, reportsTile]`, one condition adds a whole group. So a `build` method can return `ListView(children: [...])` as a single expression: no `var children = <Widget>[]`, no `add`/`addAll`, no ternary returning a dummy `Container()`. The spread is eager: it copies the elements when the literal is evaluated, so later changes to the source list do not show up in the new one. These features work in list, set and map literals alike.

code

dart · 27 lines
dart
import 'package:flutter/material.dart';

class AppMenu extends StatelessWidget {
  const AppMenu({super.key, required this.isAdmin, required this.extraTiles});

  final bool isAdmin;
  final List<Widget> extraTiles;

  @override
  Widget build(BuildContext context) {
    return Drawer(
      child: ListView(
        children: [
          const DrawerHeader(child: Text('Menu')),
          const ListTile(leading: Icon(Icons.home), title: Text('Home')),
          const ListTile(leading: Icon(Icons.settings), title: Text('Settings')),
          ...extraTiles,
          if (isAdmin) ...[
            const Divider(),
            const ListTile(leading: Icon(Icons.people), title: Text('Users')),
            const ListTile(leading: Icon(Icons.flag), title: Text('Reports')),
          ],
        ],
      ),
    );
  }
}

go deeper

for a junior

Recall the two shapes: if (cond) widget adds zero or one child, and ...list inserts every element of another list. Be able to write if (isAdmin) ...[tiles] from memory.

for a middle

Explain that the spread is eager and copies at evaluation time, that if without else contributes nothing rather than null, and how the children parameter's type drives inference.

for a senior

Refactor imperative add and addAll build methods into single literals in review, and lean on prefer_spread_collections and prefer_inlined_adds so the codebase stays consistent.

for a principal

Weigh readability of deeply nested literals against extracting helper methods or widgets, and set team conventions for when a conditional group deserves its own widget.

## Why the literal needed control flow Flutter builds UI out of large nested expressions: a `Drawer` holds a `ListView`, which holds a `children` list of `ListTile` widgets. Before **Dart 2.3** a collection literal could only list expressions, so any condition forced you out of the expression into imperative code: 1. declare a local `var children = <Widget>[...]`; 2. call `children.add(...)` inside an `if` statement; 3. call `children.addAll(...)` for anything computed; 4. pass the local into the widget. The Dart 2.3 release ("UI-as-code") added three **control-flow elements** to list, set and map literals: **spread**, **collection if** and **collection for**. This question covers the first two. ## The collection if element An `if` element evaluates its condition while the literal is being built: - `if (cond) element` contributes **one element** when `cond` is true and **nothing** when it is false. It does not insert `null` or a placeholder; the list is simply shorter. - `if (cond) a else b` contributes exactly one of the two. - `else if` chains work, and since Dart 3 the condition may be a pattern: `if (value case String name) Text(name)`. - Flow analysis applies, so `if (user != null) Text(user.name)` promotes a local `user` inside the element. A ternary `cond ? a : b` is still legal inside a literal, but it always yields exactly one element, which is why older Flutter code filled the "false" side with an empty `Container()`. ## The spread element `...expression` evaluates an `Iterable` and inserts **each of its elements** at that position. In a map literal the spread takes a `Map` and inserts its entries. - The spread is **eager**: the source is iterated once when the literal runs, and the new collection is independent of it afterwards (a shallow copy: the element objects are shared, the list is not). - The operand must be non-nullable; a nullable source needs the null-aware form `...?`, covered separately. - Spreading a **nested literal** is how one condition adds several elements: `if (isAdmin) ...[a, b, c]`. ## A menu with conditional admin entries The two features compose into the idiomatic Flutter shape. Every user sees Home and Settings; admins also get a divider and two admin tiles; extra tiles passed in by the caller are spliced in the middle: ```dart ListView( children: [ const ListTile(title: Text('Home')), const ListTile(title: Text('Settings')), ...extraTiles, if (isAdmin) ...[ const Divider(), const ListTile(title: Text('Users')), const ListTile(title: Text('Reports')), ], ], ) ``` The `children` parameter is typed `List<Widget>`, so the whole literal, branches included, is inferred as `List<Widget>` with no annotation. ## How the literal is evaluated When the literal runs, Dart walks its elements **left to right, once each**: 1. a plain expression element is evaluated and appended; 2. an `if` element evaluates its condition, then only the chosen branch; 3. a spread evaluates its operand, iterates it to the end and appends each value in order. Two consequences follow. First, **order is preserved**: the spliced `extraTiles` appear exactly where the spread sits, between Settings and the admin group. Second, the branch that is not taken is **never evaluated**, so `if (isAdmin) ...[expensiveTiles()]` does not call `expensiveTiles()` for ordinary users. In a Flutter `build` method the literal is re-evaluated on every rebuild, which is the normal cost of building widgets and nothing specific to these elements. ## Imperative versus declarative, side by side | Need | Imperative (pre-2.3 style) | Literal elements | |---|---|---| | Optional single child | `if (c) children.add(w);` | `if (c) w` | | Optional group | `if (c) children.addAll([a, b]);` | `if (c) ...[a, b]` | | Splice another list | `children.addAll(extra);` | `...extra` | | Either/or | `children.add(c ? a : b);` | `if (c) a else b` | The analyzer backs the declarative style with lints in the `recommended` set that `flutter_lints` builds on: `prefer_spread_collections` flags `addAll` on a list you just created, and `prefer_inlined_adds` flags `add` calls that could be elements of the literal. ## Mistakes interviewers listen for - Believing a false `if` element inserts `null`: it inserts nothing. - Believing `if` needs an `else`, and so reaching for `cond ? w : Container()`. - Treating the spread as a live view: it copies at evaluation time. - Calling these Flutter features: they are Dart language features that work in any list, set or map literal, in Flutter or not.

  • Can a Dart collection if element have else branches or test a pattern?
    Yes. `if (a) x else if (b) y else z` is a single element that contributes one of three values. Since Dart 3 the condition can be `if (expr case pattern)`, and variables bound by the pattern are in scope in that element, for example `if (json['avatar'] case String url) Image.network(url)`.
  • If the list you spread is modified after the literal is built, does the new list change?
    No. The spread iterated the source when the literal was evaluated and inserted its elements into a fresh list. Adding to or removing from the source later has no effect. The element objects themselves are shared, so mutating one mutable element is visible through both lists.
  • When is a ternary still the right tool inside a children literal?
    When you always want exactly one child and simply choose which, a ternary and `if (c) a else b` are equivalent; pick whichever reads better. Use a plain `if` without `else` when the false case should contribute nothing, rather than inventing a placeholder widget.

saying these in an interview costs you the question

  • A false collection if inserts null into the list
  • An if element inside a literal must always have an else branch
  • Adding several widgets conditionally needs a ternary that returns a Column
  • A spread is a live view that tracks later changes to the source list
  • Collection if and spread are Flutter features rather than Dart language features