Note: I have already seen answers on Lazy Load of ListView but those are for custom API not firestore database!
I have a books summary app, App fetches data from my Firebase/Firestore database and then display it using a ListView.builder which is wrapped inside a StreamBuilder.
Now, I want to fetch data lazily, I mean as the user scrolls through the List the required data gets loaded rather than loading data at once and then displaying it lazily.
//The Widget used to display data:
Widget feed() {
return Container(
width: deviceWidth,
height: deviceHeight / 3,
child: StreamBuilder(
stream: Firestore.instance
.collection('feedItem')
.orderBy('feedId', descending: true)
.snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
int totalLength = snapshot.data.documents.length;
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: totalLength > 10 ? 10 : totalLength,
itemBuilder: (BuildContext context, int index) {
return Container(
width: deviceWidth / 2.5,
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => FeedIntro(
snapshot.data.documents[
((totalLength - 1) - index)]['feedId'])));
},
child: Card(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
// width: 150,
height: 150,
foregroundDecoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(
snapshot.data.documents[index]['feedImage'],
),
fit: BoxFit.fill)),
),
Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(snapshot.data.documents[index]['title']),
)),
],
)),
),
);
},
);
} else if (snapshot.hasError) {
return Center(child: Text('Sorry Something went wrong!'));
} else {
return Center(
child: SizedBox(
child: CircularProgressIndicator(),
width: 50,
height: 50,
),
);
}
}),
);
}
The description of your lazy loading seems to match what pagination is. Here's a simple demo using Firestore with pagination in a ListView.builder
This sample implements snippets from Firebase official doc for Firestore pagination.
There are two ways to load the data on the view in this demo.
ListView using
RefreshIndicator
ListView.
ScrollController
is used to determine if the user has hit the bottom part of the list.import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'DocObj.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Firebase
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var scrollController = ScrollController();
@override
void initState() {
super.initState();
getDocuments();
scrollController.addListener(() {
if (scrollController.position.atEdge) {
if (scrollController.position.pixels == 0)
print('ListView scroll at top');
else {
print('ListView scroll at bottom');
getDocumentsNext(); // Load next documents
}
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: listDocument.length != 0
? RefreshIndicator(
child: ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
controller: scrollController,
itemCount: listDocument.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('${listDocument[index].documentName}'),
);
},
),
onRefresh: getDocuments, // Refresh entire list
)
: CircularProgressIndicator(),
),
);
}
List<DocObj> listDocument;
QuerySnapshot collectionState;
// Fetch first 15 documents
Future<void> getDocuments() async {
listDocument = List();
var collection = FirebaseFirestore.instance
.collection('sample_pagination')
.orderBy("name")
.limit(15);
print('getDocuments');
fetchDocuments(collection);
}
// Fetch next 5 documents starting from the last document fetched earlier
Future<void> getDocumentsNext() async {
// Get the last visible document
var lastVisible = collectionState.docs[collectionState.docs.length-1];
print('listDocument legnth: ${collectionState.size} last: $lastVisible');
var collection = FirebaseFirestore.instance
.collection('sample_pagination')
.orderBy("name").startAfterDocument(lastVisible).limit(5);
fetchDocuments(collection);
}
fetchDocuments(Query collection){
collection.get().then((value) {
collectionState = value; // store collection state to set where to start next
value.docs.forEach((element) {
print('getDocuments ${element.data()}');
setState(() {
listDocument.add(DocObj(DocObj.setDocDetails(element.data())));
});
});
});
}
}
To parse the data inside the document, you can create a model for your object.
class DocObj {
var documentName;
DocObj(DocObj doc) {
this.documentName = doc.getDocName();
}
dynamic getDocName() => documentName;
DocObj.setDocDetails(Map<dynamic, dynamic> doc)
: documentName = doc['name'];
}
The sample handles this data from Firestore.

Here's how the app looks when running.

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With