Flutter - Null check operator used on a null value - flutter

I am trying to retrieve user data using a DocumentSnapshot. However I am getting a casterror because the instance that I created is pointing to null. So I tried calling the method getUserProfileData in the initState method to see if it could assign a value to my DocumentSnapshot instance but I still get that error. Please may anyone kindly help.
//DocumentSnapshot instance that is null
DocumentSnapshot? userDocument;
getUserProfileData() {
FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid)
.snapshots()
.listen((event) {
userDocument = event;
});
}
#override
void initState() {
getUserProfileData();
//This is where the castError occurs because userDocument is null
nameController.text = userDocument!.get('name');
userNameController.text = userDocument!.get('username');
try {
descriptionController.text = userDocument!.get('description');
} catch (e) {
descriptionController.text = '';
}
try {
followers = userDocument!.get('followers').length;
} catch (e) {
followers = 0;
}
try {
following = userDocument!.get('following').length;
} catch (e) {
following = 0;
}
}

It would be better to use StreamBuilder for firstore-snapshot.
late final stream = FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid)
.snapshots();
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: stream,
builder: (context, snapshot) {....},
);
}
Also error can be bypass like
getUserProfileData() {
FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid)
.snapshots()
.listen((event) {
userDocument = event;
/// all those stuff here
nameController.text = userDocument!.get('name');
userNameController.text = userDocument!.get('username');
..........

Try use async/await for getUserProfileData() function
getUserProfileData() async {
await FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid)
.snapshots()
.listen((event) {
userDocument = event;
});
}
#override
void initState() {
getUserProfileData();
// Replace with default value case userDocument is null
nameController.text = userDocument!.get('name') ?? "";
userNameController.text = userDocument!.get('username') ?? "";
try {
descriptionController.text = userDocument!.get('description') ?? "";
} catch (e) {
descriptionController.text = '';
}
try {
followers = userDocument!.get('followers').length ?? 0;
} catch (e) {
followers = 0;
}
try {
following = userDocument!.get('following').length ?? 0;
} catch (e) {
following = 0;
}
}
Async determines that a method will be asynchronous, that is, it will not return something immediately, so the application can continue executing other tasks while processing is not finished.
await serves to determine that the application must wait for a response from a function before continuing execution. This is very important because there are cases where a function depends on the return of another.
a ?? b means that if the value of a is null, the value b will be assigned

Related

Results are different when application tested on a physical phone and on simulator

I am trying to solve an issue with my application. When I test it on a virtual device (iPhone), the query is working well and I am getting the document I am supposed to get. When I test the application on my physical phone, the application does not find any record.
I have checked the filters, they are the same. it is exactly the same code. I have never have this situation before. Please, do you have any suggestion?
Future myQuery ( time, energy, urgent, important ) async{
final uid = FirebaseAuth.instance.currentUser!.uid;
final path = 'Users/$uid/allTasks';
final currentQuery = FirebaseFirestore.instance.collection(path);
Query statusQuery = currentQuery.where('status', isEqualTo: 'Inbox');
// Query contextQuery = statusQuery.where('context', isEqualTo: );
Query timeQuery = statusQuery.where('time_Needed', isEqualTo: time);
Query energyQuery = timeQuery.where('energy_Needed', isEqualTo: energy);
Query urgentQuery = energyQuery.where('urgent', isEqualTo: urgent);
Query importantQuery = urgentQuery.where('important', isEqualTo: important);
final snapshot = await importantQuery.get();
final data = snapshot.docs;
if(data.isNotEmpty) {
return snapshot;
}
}
ElevatedButton(child:
const Text('FIND'),
onPressed: () async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
EngageDisplayTasks(
time: timeSelectedPicker!, energy: energySelectedPicker!,
urgent: urgentWhere, important: importantWhere,
)));
}
),
#override
void initState() {
super.initState();
myQuery(time,energy,urgent,important).then((results) {
setState(() {
querySnapshot = results;
});
});
queryEngage(time,energy,urgent,important).then((results) {
setState(() {
querySnapshot = results;
});
});
}
Future queryEngage (time,energy,urgent,important) async {
await myQueryV2();
await myQueryV3 (statusQuery,time);
await myQueryV4 (timeQuery,energy);
await myQueryV5 (energyQuery,urgent);
await myQueryV6 (urgentQuery,important);
}
Future myQueryV2 ( ) async{
final uid = FirebaseAuth.instance.currentUser!.uid;
final path = 'Users/$uid/allTasks';
final currentQuery = FirebaseFirestore.instance.collection(path);
statusQuery = currentQuery.where('status', isEqualTo: 'Inbox');
return statusQuery;
}
Future myQueryV3 (statusQuery, time) async {
timeQuery = statusQuery.where('time_Needed', isEqualTo: time);
return timeQuery;
}
Future myQueryV4 (timeQuery, energy) async {
energyQuery = timeQuery.where('energy_Needed', isEqualTo: energy);
return energyQuery;
}
Future myQueryV5 (energyQuery, urgent) async {
urgentQuery = energyQuery.where('urgent', isEqualTo: urgent);
return urgentQuery;
}
Future myQueryV6 (urgentQuery, important) async {
importantQuery = urgentQuery.where('important', isEqualTo: important);
print ('urgent: $urgent');
print ('important: $important');
print ('time: $time');
print ('energy: $energy');
final snapshot = await importantQuery.get();
final data = snapshot.docs;
if(data.isNotEmpty) {
return snapshot;
}
}
You could write a single compound query like this. It is not necessary to get the results at each stage based on the code shown. You may need to create some composite indexes to improve optimization, but Firebase should notify you automatically if this is the case.
Future myQuery ( time, energy, urgent, important ) async{
final uid = FirebaseAuth.instance.currentUser!.uid;
final path = 'Users/$uid/allTasks';
final currentQuery = FirebaseFirestore.instance.collection(path);
try {
final snapshot = currentQuery
.where('status', isEqualTo: 'Inbox')
.where('time_Needed', isEqualTo: time)
.where('energy_Needed', isEqualTo: energy)
.where('urgent', isEqualTo: urgent)
.where('important', isEqualTo: important);
await snapshot.get().then((data) {
for (var doc in data.docs) {
print("${doc.id} => ${doc.data()}");
}
});
} catch (err) {
print(err);
}
}

How to read List of Instance of '_JsonQuerySnapshot' in Flutter

I am trying to read the data from a list that has the data of Instance of '_JsonQuerySnapshot'.
In the below code I am trying to get the data from DB
getData() async {
var db = FirebaseFirestore.instance;
var categoryList = [];
db.collection('categories')
.doc(value)
.get()
.then((DocumentSnapshot doc) {
var listSubCol = doc["categoryCollection"];
listSubCol.forEach((id) {
db.collection('categories')
.doc(value)
.collection(id)
.get()
.then((snapshot) {
categoryList.add(snapshot);
return categoryList;
});
});
});
}
Below is the code where I am printing the values.
FutureBuilder(
future: getData(),
builder: (BuildContext context, snapshot){
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.connectionState == ConnectionState.done) {
print('Data: ${snapshot.data}');
return Text("Text");
}
return Text("Text Outside");
}
)
I got a null value.
Can anyone please help me with how to get the data?
The getData is a Future<void>, so when the FutureBuilder is completed the snapshot.data is always null.
Change getData to return a List of whatever you are getting.
Future<List<Categories>> getData() async {
var db = FirebaseFirestore.instance;
final List<Categories> categoryList = [];
await db.collection('categories')
.doc(value)
.get()
.then((DocumentSnapshot doc) {
var listSubCol = doc["categoryCollection"];
listSubCol.forEach((id) {
db.collection('categories')
.doc(value)
.collection(id)
.get()
.then((snapshot) {
categoryList.add(snapshot);
return categoryList;
});
});
});
return categoryList;
}
getData() async {
var db = FirebaseFirestore.instance;
await db.collection('categories').doc(value).get().then((DocumentSnapshot doc) {
var listSubCol = doc["categoryCollection"];
listSubCol.forEach((id) {
db.collection('categories').doc(value).collection(id).get().then((snapshot) {
snapshot.docs.forEach((element) {
categoryList.add(element.data());
});
});
return categoryList;
});
});
return categoryList;
}
using the above method i got the data from Firestore.

Pagination for Flutter ListView.builder [duplicate]

I'm trying to paginate by using Firestore and I read the document and it implement like this in Swift
let first = db.collection("cities")
.order(by: "population")
.limit(to: 25)
first.addSnapshotListener { (snapshot, error) in
guard let snapshot = snapshot else {
print("Error retrieving cities: \(error.debugDescription)")
return
}
guard let lastSnapshot = snapshot.documents.last else {
// The collection is empty.
return
}
// Construct a new query starting after this document,
// retrieving the next 25 cities.
let next = db.collection("cities")
.order(by: "population")
.start(afterDocument: lastSnapshot)
// Use the query for pagination.
// ...
}
Just for practice, I tried fetched three documents and if button tapped, fetch one more document.
Firestore.instance.collection('user').where('name', isEqualTo: 'Tom').orderBy('age').limit(3).getDocuments().then((snapshot) {
_lastDocument = snapshot.documents.last;
snapshot.documents.forEach((snap) {
print(snap.data);
});
});
After button tapped tried like this.
Firestore.instance.collection('user').where('name', isEqualTo: 'Tom').orderBy('age').startAfter(_lastDocument).limit(1).getDocuments().then((snapshot) {
snapshot.documents.forEach((snap) {
print(snap.data);
});
});
But console says this.
The following assertion was thrown while handling a gesture: type
'DocumentSnapshot' is not a subtype of type 'List[dynamic]'
Why do I have to pass list?
Does anyone know how to fix this?
UPDATE
I was able to paginate like so.
class PaginationExample extends StatefulWidget {
#override
_PaginationExampleState createState() => _PaginationExampleState();
}
class _PaginationExampleState extends State<PaginationExample> {
var _restaurants = <Restaurant>[];
var _nomore = false;
var _isFetching = false;
DocumentSnapshot _lastDocument;
ScrollController _controller;
void _fetchDocuments() async {
final QuerySnapshot querySnapshot = await Firestore.instance.collection('restaurants').orderBy('likes').limit(8).getDocuments();
// your logic here
}
Future<Null> _fetchFromLast() async {
final QuerySnapshot querySnapshot = await Firestore.instance.collection('restaurants').orderBy('likes').startAfter([_lastDocument['likes']]).limit(4).getDocuments();
if (querySnapshot.documents.length < 4) {
_nomore = true;
return;
}
_lastDocument = querySnapshot.documents.last;
for (final DocumentSnapshot snapshot in querySnapshot.documents) {
final Restaurant re = Restaurant(snapshot);
_restaurants.add(re);
}
setState(() {});
}
void _scrollListener() async {
if (_nomore) return;
if (_controller.position.pixels == _controller.position.maxScrollExtent && _isFetching == false) {
_isFetching = true;
await _fetchFromLast();
_isFetching = false;
}
}
#override
void initState() {
_fetchDocuments();
_controller = new ScrollController()..addListener(_scrollListener);
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
);
}
}
There is an error here:
Firestore.instance.collection('user').where('name', isEqualTo: 'Tom').orderBy('age').startAfter(_lastDocument).limit(1).getDocuments().then((snapshot) {
snapshot.documents.forEach((snap) {
print(snap.data);
});
});
startAfter method expects a List value params and you are passing a DocumentSnapshot.
Takes a list of [values], creates and returns a new [Query] that
starts after the provided fields relative to the order of the query.
You could try something like this:
Firestore.instance.collection('user').where('name', isEqualTo: 'Tom').orderBy('age').startAfter([{'name': 'Tom'}]).limit(1).getDocuments().then((snapshot) {
snapshot.documents.forEach((snap) {
print(snap.data);
});
});
Paginate just with 2 attrubutes, itemBuilder and query using this package - paginate_firestore
For example,
PaginateFirestore(
itemBuilder: (context, documentSnapshot) => ListTile(
leading: CircleAvatar(child: Icon(Icons.person)),
title: Text(documentSnapshot.data['name']),
subtitle: Text(documentSnapshot.documentID),
),
// orderBy is compulsary to enable pagination
query: Firestore.instance.collection('users').orderBy('name'),
)
This works for me giving realtime pagination
defining functions to fetch data
import 'package:cloud_firestore/cloud_firestore.dart';
import '../../../core/constants/firebase_constants.dart';
class FirebaseProvider {
final FirebaseFirestore _firestore;
FirebaseProvider({required FirebaseFirestore firestore})
: _firestore = firestore;
CollectionReference get _posts =>
_firestore.collection(FirebaseConstants.postsCollection);
Future<List<DocumentSnapshot>> fetchFirstList(
String fromgst, String postType) async {
return (await _posts
.where("fromgst", isEqualTo: fromgst)
.where("postType", isEqualTo: postType)
.orderBy("date", descending: true)
.limit(5)
.get())
.docs;
}
Future<List<DocumentSnapshot>> fetchNextList(String fromgst, String postType,
List<DocumentSnapshot> documentList) async {
return (await _posts
.where("fromgst", isEqualTo: fromgst)
.where("postType", isEqualTo: postType)
.orderBy("date", descending: true)
.startAfterDocument(documentList[documentList.length - 1])
.limit(5)
.get())
.docs;
}
}
separate class to handle pagination
import 'dart:async';
import 'dart:io';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:growmore/features/home/repository/firebase_provider.dart';
import 'package:rxdart/rxdart.dart';
class PostListBloc {
List<DocumentSnapshot>? documentList;
bool showIndicator = false;
FirebaseProvider? firebaseProvider;
BehaviorSubject<List<DocumentSnapshot>>? postController;
BehaviorSubject<bool>? showIndicatorController;
PostListBloc() {
postController = BehaviorSubject<List<DocumentSnapshot>>();
showIndicatorController = BehaviorSubject<bool>();
firebaseProvider = FirebaseProvider(firestore: FirebaseFirestore.instance);
}
Stream get getShowIndicatorStream => showIndicatorController!.stream;
Stream<List<DocumentSnapshot>> get postStream => postController!.stream;
// This method will automatically fetch first 10 elements from the document list
Future fetchFirstList(String fromgst, String postType) async {
try {
documentList = await firebaseProvider?.fetchFirstList(fromgst, postType);
print("documentList$documentList");
postController?.sink.add(documentList!);
try {
if (documentList!.isEmpty) {
postController?.sink.addError("No Data Available");
}
} catch (e) {
print(e);
}
} on SocketException {
postController?.sink.addError(SocketException("No Internet Connection"));
} catch (e) {
print(e.toString());
postController?.sink.addError(e);
}
}
//This will automatically fetch the next 10 elements from the list
fetchNextPosts(String fromgst, String postType) async {
try {
updateIndicator(true);
List<DocumentSnapshot> newDocumentList = await firebaseProvider!
.fetchNextList(fromgst, postType, documentList!);
print('asca$newDocumentList');
documentList!.addAll(newDocumentList);
postController!.sink.add(documentList!);
try {
if (documentList!.isEmpty) {
postController!.sink.addError("No Data Available");
updateIndicator(false);
}
} catch (e) {
updateIndicator(false);
}
} on SocketException {
postController!.sink.addError(SocketException("No Internet Connection"));
updateIndicator(false);
} catch (e) {
updateIndicator(false);
print(e.toString());
postController!.sink.addError(e);
}
}
//For updating the indicator below every list and paginate*
updateIndicator(bool value) async {
showIndicator = value;
showIndicatorController!.sink.add(value);
}
void dispose() {
postController!.close();
showIndicatorController!.close();
}
}
the ui part
ScrollController controller = ScrollController();
#override
void initState() {
super.initState();
postListBloc = PostListBloc();
print("dvvfe${widget.fromgst}");
postListBloc!.fetchFirstList(widget.fromgst, widget.postType);
controller.addListener(_scrollListener);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<List<DocumentSnapshot>>(
stream: postListBloc!.postStream,
builder: (context, snapshot) {
if (snapshot.data != null) {
return ListView.builder(
itemCount: snapshot.data?.length,
shrinkWrap: true,
controller: controller,
itemBuilder: (context, index) {
return Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
title: Text(snapshot.data![index]['description']),
),
),
);
},
);
} else {
return const CircularProgressIndicator();
}
},
),
);
}
void _scrollListener() {
if (controller.offset >= controller.position.maxScrollExtent &&
!controller.position.outOfRange) {
print("Cavc$controller");
print("at the end of list");
postListBloc!.fetchNextPosts(widget.fromgst, widget.postType);
}
}
}
I found it not open source github repo

Flutter - How to complete/await function execution before build method?

I am simply trying to set an ID in this function:
_getLastWorkoutId() async {
try {
var snapshot = await usersRef
.doc(currentUser!.uid)
.collection('workouts')
.orderBy('workoutDate', descending: true)
.limit(1)
.snapshots()
.first;
//The execution moves to build method from here------and then returns
for (var element in snapshot.docs) {
workoutId = element.id;
setState(() {
_isWorkoutIdSet = true;
});
}
//return snapshot;
} catch (e) {
print(e.toString());
}
//return null;
}
I call it in the initState:
#override
void initState() {
WidgetsBinding.instance!.addObserver(this);
super.initState();
//var snapshot = _getLastWorkoutId();
_getLastWorkoutId();
}
The problem is, the for loop executes after the build function is called. I don't want that to happen.
You can use FutureBuilder like this:
Future<bool> _value;
#override
void initState() {
WidgetsBinding.instance!.addObserver(this);
super.initState();
_value = _getLastWorkoutId();
}
And in your build method you have:
FutureBuilder<bool>(
future: _value,
builder: (
BuildContext context,
AsyncSnapshot<bool> snapshot,
) {
if (snapshot.hasData) {
if (snapshot.data){
//update view
}else{
//update view
}
}
}
The method can be like this:
_getLastWorkoutId() async {
try {
var snapshot = await usersRef
.doc(currentUser!.uid)
.collection('workouts')
.orderBy('workoutDate', descending: true)
.limit(1)
.snapshots()
.first;
for (var element in snapshot.docs) {
workoutId = element.id;
return true;
}
} catch (e) {
print(e.toString());
}
}
Here you can find more about FutureBuilder.
I believe this should solve the issue:
First, on build method:
return FutureBuilder(
future: _getLastWorkoutId(),
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) return CircularProgressIndicator();
return Container(); // here goes whatever it is you had before.
}
);
then on _getLastWorkoutId():
Future<void> _getLastWorkoutId() async {
...
}
That way the function returns a future of void instead of void, allowing FutureBuilder to do its thing.
You should declare a variable to save state. For example
var isLoading = true;
At the end of try block in func _getLastWorkoutId reset isLoading to false.
Then call setState or update state by better way used state management likes provider, bloc, get.
In build widget add check isLoading like this
return isLoading ? YourLoadingIndicatorWidget() : DisplayDataWidget();

Flutter Firestore pagination

I'm trying to paginate by using Firestore and I read the document and it implement like this in Swift
let first = db.collection("cities")
.order(by: "population")
.limit(to: 25)
first.addSnapshotListener { (snapshot, error) in
guard let snapshot = snapshot else {
print("Error retrieving cities: \(error.debugDescription)")
return
}
guard let lastSnapshot = snapshot.documents.last else {
// The collection is empty.
return
}
// Construct a new query starting after this document,
// retrieving the next 25 cities.
let next = db.collection("cities")
.order(by: "population")
.start(afterDocument: lastSnapshot)
// Use the query for pagination.
// ...
}
Just for practice, I tried fetched three documents and if button tapped, fetch one more document.
Firestore.instance.collection('user').where('name', isEqualTo: 'Tom').orderBy('age').limit(3).getDocuments().then((snapshot) {
_lastDocument = snapshot.documents.last;
snapshot.documents.forEach((snap) {
print(snap.data);
});
});
After button tapped tried like this.
Firestore.instance.collection('user').where('name', isEqualTo: 'Tom').orderBy('age').startAfter(_lastDocument).limit(1).getDocuments().then((snapshot) {
snapshot.documents.forEach((snap) {
print(snap.data);
});
});
But console says this.
The following assertion was thrown while handling a gesture: type
'DocumentSnapshot' is not a subtype of type 'List[dynamic]'
Why do I have to pass list?
Does anyone know how to fix this?
UPDATE
I was able to paginate like so.
class PaginationExample extends StatefulWidget {
#override
_PaginationExampleState createState() => _PaginationExampleState();
}
class _PaginationExampleState extends State<PaginationExample> {
var _restaurants = <Restaurant>[];
var _nomore = false;
var _isFetching = false;
DocumentSnapshot _lastDocument;
ScrollController _controller;
void _fetchDocuments() async {
final QuerySnapshot querySnapshot = await Firestore.instance.collection('restaurants').orderBy('likes').limit(8).getDocuments();
// your logic here
}
Future<Null> _fetchFromLast() async {
final QuerySnapshot querySnapshot = await Firestore.instance.collection('restaurants').orderBy('likes').startAfter([_lastDocument['likes']]).limit(4).getDocuments();
if (querySnapshot.documents.length < 4) {
_nomore = true;
return;
}
_lastDocument = querySnapshot.documents.last;
for (final DocumentSnapshot snapshot in querySnapshot.documents) {
final Restaurant re = Restaurant(snapshot);
_restaurants.add(re);
}
setState(() {});
}
void _scrollListener() async {
if (_nomore) return;
if (_controller.position.pixels == _controller.position.maxScrollExtent && _isFetching == false) {
_isFetching = true;
await _fetchFromLast();
_isFetching = false;
}
}
#override
void initState() {
_fetchDocuments();
_controller = new ScrollController()..addListener(_scrollListener);
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
);
}
}
There is an error here:
Firestore.instance.collection('user').where('name', isEqualTo: 'Tom').orderBy('age').startAfter(_lastDocument).limit(1).getDocuments().then((snapshot) {
snapshot.documents.forEach((snap) {
print(snap.data);
});
});
startAfter method expects a List value params and you are passing a DocumentSnapshot.
Takes a list of [values], creates and returns a new [Query] that
starts after the provided fields relative to the order of the query.
You could try something like this:
Firestore.instance.collection('user').where('name', isEqualTo: 'Tom').orderBy('age').startAfter([{'name': 'Tom'}]).limit(1).getDocuments().then((snapshot) {
snapshot.documents.forEach((snap) {
print(snap.data);
});
});
Paginate just with 2 attrubutes, itemBuilder and query using this package - paginate_firestore
For example,
PaginateFirestore(
itemBuilder: (context, documentSnapshot) => ListTile(
leading: CircleAvatar(child: Icon(Icons.person)),
title: Text(documentSnapshot.data['name']),
subtitle: Text(documentSnapshot.documentID),
),
// orderBy is compulsary to enable pagination
query: Firestore.instance.collection('users').orderBy('name'),
)
This works for me giving realtime pagination
defining functions to fetch data
import 'package:cloud_firestore/cloud_firestore.dart';
import '../../../core/constants/firebase_constants.dart';
class FirebaseProvider {
final FirebaseFirestore _firestore;
FirebaseProvider({required FirebaseFirestore firestore})
: _firestore = firestore;
CollectionReference get _posts =>
_firestore.collection(FirebaseConstants.postsCollection);
Future<List<DocumentSnapshot>> fetchFirstList(
String fromgst, String postType) async {
return (await _posts
.where("fromgst", isEqualTo: fromgst)
.where("postType", isEqualTo: postType)
.orderBy("date", descending: true)
.limit(5)
.get())
.docs;
}
Future<List<DocumentSnapshot>> fetchNextList(String fromgst, String postType,
List<DocumentSnapshot> documentList) async {
return (await _posts
.where("fromgst", isEqualTo: fromgst)
.where("postType", isEqualTo: postType)
.orderBy("date", descending: true)
.startAfterDocument(documentList[documentList.length - 1])
.limit(5)
.get())
.docs;
}
}
separate class to handle pagination
import 'dart:async';
import 'dart:io';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:growmore/features/home/repository/firebase_provider.dart';
import 'package:rxdart/rxdart.dart';
class PostListBloc {
List<DocumentSnapshot>? documentList;
bool showIndicator = false;
FirebaseProvider? firebaseProvider;
BehaviorSubject<List<DocumentSnapshot>>? postController;
BehaviorSubject<bool>? showIndicatorController;
PostListBloc() {
postController = BehaviorSubject<List<DocumentSnapshot>>();
showIndicatorController = BehaviorSubject<bool>();
firebaseProvider = FirebaseProvider(firestore: FirebaseFirestore.instance);
}
Stream get getShowIndicatorStream => showIndicatorController!.stream;
Stream<List<DocumentSnapshot>> get postStream => postController!.stream;
// This method will automatically fetch first 10 elements from the document list
Future fetchFirstList(String fromgst, String postType) async {
try {
documentList = await firebaseProvider?.fetchFirstList(fromgst, postType);
print("documentList$documentList");
postController?.sink.add(documentList!);
try {
if (documentList!.isEmpty) {
postController?.sink.addError("No Data Available");
}
} catch (e) {
print(e);
}
} on SocketException {
postController?.sink.addError(SocketException("No Internet Connection"));
} catch (e) {
print(e.toString());
postController?.sink.addError(e);
}
}
//This will automatically fetch the next 10 elements from the list
fetchNextPosts(String fromgst, String postType) async {
try {
updateIndicator(true);
List<DocumentSnapshot> newDocumentList = await firebaseProvider!
.fetchNextList(fromgst, postType, documentList!);
print('asca$newDocumentList');
documentList!.addAll(newDocumentList);
postController!.sink.add(documentList!);
try {
if (documentList!.isEmpty) {
postController!.sink.addError("No Data Available");
updateIndicator(false);
}
} catch (e) {
updateIndicator(false);
}
} on SocketException {
postController!.sink.addError(SocketException("No Internet Connection"));
updateIndicator(false);
} catch (e) {
updateIndicator(false);
print(e.toString());
postController!.sink.addError(e);
}
}
//For updating the indicator below every list and paginate*
updateIndicator(bool value) async {
showIndicator = value;
showIndicatorController!.sink.add(value);
}
void dispose() {
postController!.close();
showIndicatorController!.close();
}
}
the ui part
ScrollController controller = ScrollController();
#override
void initState() {
super.initState();
postListBloc = PostListBloc();
print("dvvfe${widget.fromgst}");
postListBloc!.fetchFirstList(widget.fromgst, widget.postType);
controller.addListener(_scrollListener);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<List<DocumentSnapshot>>(
stream: postListBloc!.postStream,
builder: (context, snapshot) {
if (snapshot.data != null) {
return ListView.builder(
itemCount: snapshot.data?.length,
shrinkWrap: true,
controller: controller,
itemBuilder: (context, index) {
return Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
title: Text(snapshot.data![index]['description']),
),
),
);
},
);
} else {
return const CircularProgressIndicator();
}
},
),
);
}
void _scrollListener() {
if (controller.offset >= controller.position.maxScrollExtent &&
!controller.position.outOfRange) {
print("Cavc$controller");
print("at the end of list");
postListBloc!.fetchNextPosts(widget.fromgst, widget.postType);
}
}
}
I found it not open source github repo