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();
Related
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
i made a future function which return bool value, then i used it in the build widget on if condition but the code is always returning the sign in.
can any one help me solving this issue.
Future isExist(uid) async{
var isExist = await Database().checkIfUserExist(uid);
if(isExist == true){
return true;
}
else{
return false;
}
}
#override
Widget build(BuildContext context) {
final user = Provider.of<UserDashboard?>(context);
// return either Home or Authenticate widget
if(user == null){
print("user null");
return SignIn();
}else{
print("user not null");
if(isExist(user.uid) == Future.value(true)){
return Home();
}
}
return SignIn();
}
user is not null and is exist but not taking the correct result.
Future<bool> checkIfUserExist(String adminID) async{
bool isExist = false;
try{
await admin.doc(adminID).get()
.then((doc) => isExist = doc.exists);
return isExist;
}catch(e){
print(e);
return isExist;
}
}
the above function is safe and good because i used it in another screen and worked.
Try using FutureBuilder for future method.
FutureBuilder<bool>(
future: isExist(uid), // use a state variable for statefulwidget
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
if(snapshot.data) return Home();
else return SignIn();
}
return CircularProgressIndicator();
}
More about using FutureBuilder
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
I am trying to get the value from firestore and use it on my Switch widget. The indicator that I am trying to read on this switch is bool (true or false only). However it is not working, getting this error: type 'Null' is not a subtype of 'bool'
Here is my FetureBuilder code wrapped on my Switch:
FutureBuilder<bool>(
future: getShipValue(),
builder: (context, snapshot) {
if (snapshot.hasData == true) {
setState(() {
isSwitch = true;
});
}
return CupertinoSwitch(
value: isSwitch,
onChanged: (val) {
handleSwitch(val);
},
);
}),
Here is my Function that I am referencing:
Future<bool> getShipValue() async {
return await FirebaseFirestore.instance
.collection('OrderDetails')
.doc(order.orderId)
.get()
.then((value) {
return value.data()!['ShippedInd'];
});
}
Please let me know what am I doing wrong. Many thanks!
if (snapshot.hasData) {
if(snapshot.data){
setState(() {
isSwitch = true;
});
}
} else{
// Do sth else
}
The issue is solved now. As many pointed, I needed to handle the bool(true/false) and such is my FutureBuilder code that worked.
Thank you all!
Future<bool> getSwitchState() async {
isSwitch = await FirebaseFirestore.instance
.collection('OrderDetails')
.doc(order.docid)
.get()
.then((value) {
//return value.data()!['ShippedInd'] == true;
// print(value.data()!['ShippedInd']);
return (value.data()!['ShippedInd'] == true) ? true : false;
});
return isSwitch;
}
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