In Flutter, why is onPressed: _save() wrong while onPressed: _save and onPressed: () => _save() both work?
answer
- calling versus referring
- parentheses run it now
- onPressed expects VoidCallback?
- tear-off: the name without ()
- lambda: () => or () { }
basics
~20 sonPressed expects a function to call later; _save() calls the method during build and passes its result, a void value the analyzer rejects. _save passes the method itself as a tear-off, and () => _save() passes an anonymous function that calls it.
solid answer
~40 s`onPressed` is typed `VoidCallback?`, meaning a `void Function()` or `null`. Writing `_save()` **invokes** the method while `build` runs and passes its return value; for a `void` method the analyzer rejects that argument, and for a method returning a value it is a type mismatch. Writing `_save` without parentheses creates a **tear-off**: a function object that calls `_save` when the button is pressed. Writing `() => _save()` creates an **anonymous function** (a closure) that does the same. Use the tear-off when the signatures match exactly; use a lambda when you need to pass arguments, as in `() => _delete(item)`, run several statements with `() { ... }`, or call `setState`. Passing `null` disables a Material button.
code
dart · 30 linesimport 'package:flutter/material.dart';
class SaveBar extends StatefulWidget {
const SaveBar({super.key});
@override
State<SaveBar> createState() => _SaveBarState();
}
class _SaveBarState extends State<SaveBar> {
int _saves = 0;
void _save() => setState(() => _saves++);
void _saveAs(String name) => setState(() => _saves++);
@override
Widget build(BuildContext context) {
return Row(
children: [
ElevatedButton(onPressed: _save, child: const Text('Save')), // tear-off
TextButton(
onPressed: () => _saveAs('copy'), // lambda supplies an argument
child: const Text('Save copy'),
),
Text('$_saves'),
// ElevatedButton(onPressed: _save(), ...) would not compile.
],
);
}
}go deeper
Recall the three spellings and which one runs immediately; write arrow and block lambdas correctly for button callbacks.
Explain VoidCallback's type, why the analyzer rejects a void or Future result, and when a tear-off fits versus when arguments force a lambda.
Catch the set-literal arrow slip and callbacks whose signature only accidentally matches, and keep callback style consistent in review.
Set conventions for callback style and lints, such as preferring tear-offs, so large widget trees stay readable and uniform.
## What `onPressed` wants Flutter's buttons, such as `ElevatedButton`, `TextButton` and `IconButton`, declare `onPressed` as `VoidCallback?`. `VoidCallback` is a typedef for `void Function()`: a function that takes no arguments and returns nothing. The button stores that function and calls it **later**, when the user taps. Passing `null` instead disables the button. So the question is always: are you handing the button **a function**, or **the result of calling one**? ## Three spellings, three meanings | Code | What happens during `build` | What happens on tap | |---|---|---| | `onPressed: _save()` | `_save` runs immediately; its result is passed | nothing useful | | `onPressed: _save` | a tear-off of `_save` is passed | `_save` runs | | `onPressed: () => _save()` | an anonymous function is created and passed | it calls `_save` | The first form is the bug. If `_save` returns `void`, the analyzer refuses to use a `void` result as an argument. If `_save` returns `Future<void>` because it is `async`, the error becomes a type mismatch: a `Future<void>` is not a `void Function()`. Either way the code does not compile, which is the good outcome; the dangerous version is a method whose result happens to be a function, where the code compiles and runs the wrong thing. ## Anonymous functions in Dart An **anonymous function** (also called a lambda or closure) has a parameter list and a body but no name: - **block body**: `() { _validate(); _save(); }` runs any number of statements; - **arrow body**: `() => _save()` is shorthand for a body with a single `return` of one expression. The arrow form allows only **one expression**. `() => { _save(); }` is a classic slip carried over from other languages: in Dart, braces after `=>` start a set or map **literal**, not a block, and a statement ending in `;` cannot appear inside a literal, so the code does not compile. Use `() { ... }` for statements, or `() => _save()` for a single call. Anonymous functions are **closures**: they can read and assign variables from the scope where they were written, such as fields of the `State` object or local variables in `build`. That is what lets `() => _delete(item)` remember which `item` it belongs to. ## Tear-offs Referring to a function, method or constructor **without parentheses** creates a **tear-off**: a function object with the same parameters that calls the original. Examples: - `onPressed: _save` tears off an instance method of the `State`; - `items.forEach(print)` tears off a top-level function; - `names.map(User.new)` tears off a constructor (Dart 2.15 and later). A tear-off fits only where its signature matches the expected function type. `_save` fits `VoidCallback` only if it takes no required arguments. ## Async methods as callbacks An `async` method can be passed the same way. If `_save` is declared `Future<void> _save() async { ... }`, then `onPressed: _save` compiles: a function returning `Future<void>` is assignable to `void Function()`, because a `void` return type accepts any value. Two consequences follow: - the button **ignores** the returned future, so it does not wait for the save and does not see its errors; - handle failures **inside** the method (a `try`/`catch` that shows a message), because nothing outside will. The same applies to `() => _save()`; the lambda also discards the future. ## Choosing between tear-off and lambda 1. **Signatures match exactly**: prefer the tear-off; it is shorter and it is the style Effective Dart recommends. 2. **You need to supply arguments**: use a lambda, `() => _delete(item)`. 3. **You need several statements or `setState`**: use a block lambda, `() { setState(() => _count++); }`. 4. **You want the button disabled sometimes**: pass `null` conditionally, `onPressed: _canSave ? _save : null`. ## Common mistakes - `onPressed: _save()` runs the method during `build`. - `onPressed: () => { _save(); }` does not compile, because braces after `=>` start a collection literal, not a block. - Tearing off a method whose parameters do not match `VoidCallback`, then wrapping it in a cast instead of a lambda.
- In Dart, why does onPressed: () => { _save(); } not compile?After `=>` Dart expects one expression, and an opening brace in expression position starts a set or map literal, not a block of statements. The `;` inside is not valid in a literal, so parsing fails. Write `() { _save(); }` for a block body, or `() => _save()` for a single call.
- How do you disable a Flutter button based on state while still using a tear-off?Pass `null` conditionally: `onPressed: _canSave ? _save : null`. A null `onPressed` renders the button disabled and ignores taps; a non-null callback enables it.
saying these in an interview costs you the question
- onPressed: _save() registers _save to run on tap
- A tear-off like _save calls the method once when the widget is built
- () => { _save(); } is a block body that runs _save
- An arrow function can contain several statements separated by semicolons
- A lambda is required whenever a callback touches State fields