Implementing Lazy Loading in Flutter: The Ultimate Performance Hack
Lazy loading in software development refers to the process of deferring the initialization of an object until it is needed.
Have you ever wondered how the infinite scrolling of Facebook or YouTube works? That is thanks to lazy loading. As we continue to scroll, the content keeps loading. We cannot load all the items at once because what if the user never consumes that resource? Loading all items at once might be costly on both the server side and the client side. To avoid such problems, we can implement lazy loading.
Let’s get started with implementing lazy loading by creating a Flutter project.
flutter create lazy_loading
Let’s add a dependency (optional) to get beautiful log message
flutter pub add logger
Start by creating a MaterialApp
inside the main.dart
file:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(),
);
}
}
Now, create a home_page.dart
file inside the lib
folder.
First, initialize the initial data source like this (the data source can be api call):
List<int> dataSource = List.generate(20, (index) => index);
Create an instance of ScrollController
to keep track of scroll events:
ScrollController scrollController = ScrollController();
Add the listener to that scroll controller inside the initState()
method:
@Override
void initState() {
scrollController.addListener(() {
if (scrollController.position.maxScrollExtent == scrollController.offset) {
// this means user has scrolled till the last item
Logger().i("Scrolled till last item");
addMore();
}
});
super.initState();
}
Now, use the ListView.builder()
widget to consume that data source:
ListView.builder(
controller: scrollController,
itemCount: dataSource.length + 1,
itemBuilder: (context, index) {
if (index < dataSource.length) {
return ListTile(
title: Text("Item ${index + 1}"),
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
The condition scrollController.position.maxScrollExtent == scrollController.offset
is true only if the user has scrolled till the last item. If the user has scrolled till the last, add more data inside the data source. I have done that with a delay of 2 seconds in a method named addMore()
:
void addMore() async {
await Future.delayed(const Duration(seconds: 3));
List<int> additionalDataSoure = List.generate(20, (index) => index);
dataSource.addAll(additionalDataSoure);
setState(() {});
}
Here’s the complete home_page.dart
file:
import 'package:flutter/material.dart';
import 'package:logger/logger.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<int> dataSource = List.generate(20, (index) => index);
ScrollController scrollController = ScrollController();
void addMore() async {
await Future.delayed(const Duration(seconds: 3));
List<int> additionalDataSoure = List.generate(20, (index) => index);
dataSource.addAll(additionalDataSoure);
setState(() {});
}
@override
void initState() {
scrollController.addListener(() {
if (scrollController.position.maxScrollExtent ==
scrollController.offset) {
// this means user has scrolled till last item
Logger().i("Scrolled till last item"); // or can use debugPrint
addMore();
}
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text("Lazy Loading"),
),
body: ListView.builder(
controller: scrollController,
itemCount: dataSource.length + 1,
itemBuilder: (context, index) {
if (index < dataSource.length) {
return ListTile(
title: Text("Item ${index + 1}"),
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
}