Skip to content

Instantly share code, notes, and snippets.

@epatel
Last active March 28, 2024 11:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save epatel/c74f136c5c9e09df917ba53064024b2b to your computer and use it in GitHub Desktop.
Save epatel/c74f136c5c9e09df917ba53064024b2b to your computer and use it in GitHub Desktop.
ListView.builder with delayed loading
import 'package:flutter/material.dart';
import 'dart:math' as math;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: FutureBuilder(
future: Api.fetchPage(), // Load first page
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
// Track pageloading with this list
List<PageLoader?> pageLoaders = [
// Pre-build loader for first page just loaded
PageLoader(
pageIndex: 1,
pageFuture: Future(() => snapshot.data!),
),
// Prepare rest with empty pageloader slots
...List.generate(
snapshot.data!.totalPages - 1,
(_) => null,
),
];
return ListView.builder(
itemCount: snapshot.data!.totalItems,
prototypeItem: ListTile(
title: Text('Abc'),
),
itemBuilder: (context, index) {
int pageIndex = index ~/ Api.numItemsPerPage;
PageLoader? pageLoader = pageLoaders[pageIndex];
if (pageLoader == null ||
pageLoader.state == PageLoaderState.canceled) {
pageLoader = PageLoader(pageIndex: pageIndex + 1);
pageLoaders[pageIndex] = pageLoader;
}
return ListTile(
tileColor: Color(
(math.Random(pageIndex + 42).nextDouble() * 0xFFFFFF)
.toInt())
.withOpacity(0.5),
title: Item(
pageLoader: pageLoader,
index: index,
),
);
},
);
},
),
),
);
}
}
class Item extends StatefulWidget {
final PageLoader pageLoader;
final int index;
const Item({
required this.pageLoader,
required this.index,
Key? key,
}) : super(key: key);
@override
State<Item> createState() => _ItemState();
}
class _ItemState extends State<Item> {
@override
void initState() {
super.initState();
widget.pageLoader.activate();
}
@override
void dispose() {
widget.pageLoader.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: widget.pageLoader.pageFuture,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text('Page: __, Item: ____');
}
final page = snapshot.data!;
final item =
page.items[widget.index - (page.page - 1) * Api.numItemsPerPage];
return Text(item);
});
}
}
// --------------------
enum PageLoaderState {
idle,
fetching,
fetched,
canceled,
}
class PageLoader {
late PageLoaderState state;
final int pageIndex;
late Future<Page?> pageFuture;
int tiles = 0;
PageLoader({
required this.pageIndex,
Future<Page?>? pageFuture,
}) {
if (pageFuture == null) {
state = PageLoaderState.idle;
// Add 1s delay before loading, not loading pages scrolled passed
this.pageFuture = Future.delayed(Duration(seconds: 1)).then((_) {
if (state != PageLoaderState.canceled) {
state = PageLoaderState.fetching;
final pageFuture = Api.fetchPage(page: pageIndex);
pageFuture.then((_) {
state = PageLoaderState.fetched;
});
return pageFuture;
}
return null;
});
} else {
state = PageLoaderState.fetched;
this.pageFuture = pageFuture;
}
}
void activate() {
tiles++;
}
void cancel() {
if (--tiles == 0 && state == PageLoaderState.idle) {
state = PageLoaderState.canceled;
}
}
}
// --------------------
class Page {
final int totalItems;
final int page;
final int totalPages;
final List<String> items;
Page({
required this.totalItems,
required this.page,
required this.totalPages,
required this.items,
});
String toString() {
return '{totalItems: $totalItems, page: $page, totalPages: $totalPages, items: $items}';
}
}
// --------------------
class Api {
static final numItems = 2000;
static final numItemsPerPage = 25;
static Future<Page> fetchPage({
int page = 1,
}) async {
// Simulate delay from API call...
await Future.delayed(Duration(seconds: 1));
print('fetchPage: $page');
return Page(
totalItems: numItems,
page: page,
totalPages: numItems ~/ numItemsPerPage +
((numItems % numItemsPerPage > 0) ? 1 : 0),
items: List.generate(
numItemsPerPage,
(index) =>
'Page: $page, Item: ${numItemsPerPage * (page - 1) + index + 1}',
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment