late Initialization in Dart
When you check the title of the blog post, you might have thought "Did he make a mistake?", "Shouldn't L be capital?" The answer is no. I wrote it on purpose and yes "late" was on purpose.
From Dart 2.12, Dart language started to support late initiazaliton of variables. Main use cases for the late initialization is:
Declaring a non-nullable variable that is going to be initialized in another event
Lazily initializing a variable (duh!)
Let's see them in detail with a proper use-cases.
Declaring a non-nullable variable that is going to be initialized in another event
Imagine you have an AnimationController, for your application. In the documentation of it, it is advised to create the controller in the initState
and dispose it in dispose
function.
An AnimationController should be disposed when it is no longer needed. This reduces the likelihood of leaks. When used with a StatefulWidget, it is common for an AnimationController to be created in the State.initState method and then disposed in the State.dispose method.
import 'package:flutter/material.dart';
class Foo extends StatefulWidget {
const Foo({ super.key });
@override
State<Foo> createState() => _FooState();
}
class _FooState extends State<Foo> with SingleTickerProviderStateMixin {
AnimationController? _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
);
}
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
}
In the code above, you need to make the controller
variable nullable to assign in the correct places. However, being nullable requires putting question mark before each instance function usage.
Also, if you try to follow along the immutability and try to put final
in front of the controller, you can see that it is going to start shouting that something is wrong.
For fixing this, late
is our savior. This will also help us to get rid of the nullability side of things as well.
class _FooState extends State<Foo> with SingleTickerProviderStateMixin {
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
Lazily initializing a variable (duh!)
Another usage of late
keyword is, in the scenario of a variable might not be needed, and/or initializing it is costly. One advantage of late keyword is the lazy initialization. When you assign the object with the late
keyword, it is going to do the assigning when the call happens to the object reference.
Let's see an example of how it can affect our workflow. Check out the numbered comments and explains of it below:
class MyHomePage extends StatefulWidget {
const MyHomePage({
Key? key,
}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// (1)
late final elements = loadAHugeList();
bool _isHugeDataLoaded = false;
void _triggerLoadingData() {
setState(() {
if (!_isHugeDataLoaded) {
_isHugeDataLoaded = true;
}
});
}
// (2)
List<String> loadAHugeList() {
// (3)
print('isLoaded');
return List.generate(1000000, (i) => '$i');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_isHugeDataLoaded)
ListView.builder(
// (4)
itemCount: elements.length,
itemBuilder: (context, index) {
return Text(elements[index]);
},
)
else
// (5)
const Text(
'You have not loaded data yet.',
),
],
),
),
floatingActionButton: FloatingActionButton.extended(
label: const Text('Load huge data'),
onPressed: _triggerLoadingData,
tooltip: 'Load Huge Data',
icon: const Icon(Icons.add),
),
);
}
}
(1): The late variable "elements" is defined but not assigned yet because the value is not called.
(2): The function to assign values is creating a list with one million items to it
(3): Only with a click on Floation Action Button you can see a message in your logs.
(4): The elements is only used for the first time when we use it in the list. But the list is not visible when the app is loaded for the first time.
(5): The initial phase of the app will show this text.
Pros/Cons
Pros:
late initialization was designed to prevent unnecessary initialization of objects.
Your variable will not be initialized unless you use it.
It is initialized only once. Next time when you use it, you get the value from cache memory. thanks to final and non-nullable combination.
Initializing an instance variable with access to current instance (aka
this
).
Cons
Forgotten variables can cause runtimes erros
There is no way to know programmatically if the initialization happened or not
Conclusion
Dart language provides us a lot of features for us. Taking advantage of them as developers is upto us and our decision to learn programming better. With this brand new information go ahead and share with me where you use late
keyword. If you have any further questions, drop me a DM.
Subscribe to my newsletter
Read articles from Muhammed Salih Güler directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by