Flutter Best Practices

Chapter 1 · Practice 3 of 5

Use late only when initialization order is truly under your control

From Dart and null safety

Why it matters

The late modifier tells Dart to enforce a variable's constraints at runtime instead of at compile time. The Dart docs warn that any use of a late field could fail, and there is nothing visible at the use site to tell you. Reading a late field before it is assigned throws, and assigning a late final field twice also throws.

late is fine for:

  • a controller created in initState() and used in build(), where Flutter calls them in a known order,
  • an expensive value you want computed lazily on first read.

It is a poor fix for "I do not know when this gets set". In that case, use a nullable type and handle the missing case, or pass the value in through the constructor.

Do

  • Initialize late controllers in initState() and dispose them in dispose()

Avoid

  • Marking a field late because a network call will fill it in "at some point"

Example

Dart
class _SearchState extends State<Search> {
  late final TextEditingController _query;

  @override
  void initState() {
    super.initState();
    _query = TextEditingController();
  }

  @override
  void dispose() {
    _query.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => TextField(controller: _query);
}