StreamBuilder updating the stream only after hot restart - flutter

I am building an app where it collects all the orders and order details placed from Firebase. I have to get 2 things
Salon details from contactnumber which I saved using singleton method once the user logs in
Customer details from CustID
What happens right now is that during debugging I created this button, on pressing it fetches the salon details from database. But now, the details will only get fetched when I
Click the button first
Hot restart the app
Only then the streambuilder fetched the data
Here are my code snippets causing the problem :
Future<void> getSalonFromContact(String saloonContact) async {
await for (var docs in firestore.collection('Saloon').snapshots()) {
// final loop = snap.data!.docs;
for (var variable in docs.docs) {
if (variable.get(FieldPath(['Contact'])) == saloonContact) {
aadhar = variable.get(FieldPath(['Aadhar']));
getOrdersList(aadhar);
}
}
}
}
Future<void> getOrdersList(String aadhar) async {
ordersList.clear();
await for (var docs in firestore
.collection('orders')
.where('SalonID', isEqualTo: aadhar)
.snapshots()) {
for (var variable in docs.docs) {
if (variable.get('SalonID') == aadhar) {
ordersList.add(variable.data());
print('My orderlist is $ordersList');
} else {
continue;
}
}
}
}
Future<void> getCustomerDetails(String custID) async {
await for (var docs in firestore
.collection('Customers')
.where('Customer_Uid', isEqualTo: custID)
.snapshots()) {
// final loop = snap.data!.docs;
for (var variable in docs.docs) {
print(variable.data());
if (variable.get(FieldPath(['Customer_Uid'])) == custID) {
customerDetails.add(variable.data());
print('My customer details are ${customerDetails}');
}
}
}
}
#override
void didChangeDependencies() async {
await getSalonFromContact(contactNumber);
for (int i = 0; i < ordersList.length; i++) {
await getCustomerDetails(ordersList[i]['CustomerID']);
}
// TODO: implement didChangeDependencies
super.didChangeDependencies();
}
These codes are for finding out the details.
And this is my StreamBuilder code :
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('orders')
.where('SalonID', isEqualTo: aadhar)
.snapshots(),
builder: (context, snapshot) {
didChangeDependencies();
if (snapshot.connectionState ==
ConnectionState.waiting) {
return Text('Loading...');
} else {
List<AppointmentCard> listitems = [];
return ListView(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
children: snapshot.data!.docs
.asMap()
.map((index, DocumentSnapshot document) {
getCustomerDetails(document['CustomerID']);
return MapEntry(
index,
AppointmentCard(
isCompleted: document['Status'],
name: customerDetails[index]['Name'],
contact: customerDetails[index]
['Contact'],
services: Flexible(
child: ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.all(8),
itemCount:
document['Requested_Service']
.length,
itemBuilder: (BuildContext context,
int index) {
return Text(
document['Requested_Service']
[index]['name']);
}),
// child: Text(
// // "Text",
// " ${ordersList[i]['Requested_Service']} ",
//// .join(' '),
//
// softWrap: true,
// ),
),
),
);
})
.values
.toList(),
);
}
}),
Any idea what is going wrong and how I can fetch the data without the button and hot restart?

You use getCustomerDetails(document['CustomerID']); in before MapEntry and it is an asynchronous function. It will return probably after the MapEntry is built. You have to await getCustomerDetails function before put your variables which is updating in getCustomerDetails function.

Related

Future Builder with for loop in flutter

In my application, I have two future builders:
CollectionReference stream = Firestore.instance.collection('users');
List<String> myIDs =[];
List <dynamic> mylist =[];
List<String> myNames =[];
String? userName;
Widget userValues() {
return FutureBuilder(
future: getrecords(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData &&
snapshot.connectionState == ConnectionState.done) {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return Text(snapshot.data? [index] ?? "got null");
},
);
}
else {
return CircularProgressIndicator();
}
},
);
}
..................
Future getrecords() async{
final data = await stream.get();
mylist.addAll(data);
mylist.forEach((element) {
final String firstPartString = element.toString().split('{').first;
final String id = firstPartString.split('/').last;
myIDs.add(id.trim());
});
return(myIDs);
}
....................
Widget Names() {
return FutureBuilder(
future: getNames(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData &&
snapshot.connectionState == ConnectionState.done) {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return Text(snapshot.data?[index] ?? "got null");
},
);
}
else {
return CircularProgressIndicator();
}
},
);
}
............................
Future getNames() async{
for (var id in myIDs ){
var names = stream.document(id).collection('userName').document('userName');
var document = await names.get();
userName = document['name'];
myNames.add(userName!);
}
return(myNames);
}
The first future (userValues) works fine, and I get the result just fine, but the other one with the for loop is not working properly and is not returning values until I hot reload, then a name will be added to the list, and so on with each hot reload.
What I want to achieve is to keep the loading indicator until the for loop is over, then build the screen.
UPDATE:
If I could manage to make it so that the "Names" futurebuilder awaits for the userValues to complete before starting, then my problem would be solved, but what I realized is that it's taking the initial value of the return from "userValues," which is non, and using it to build.
Future getNames() async{
await Future.delayed(const Duration(seconds: 2));
for (var id in myIDs ){
var names = stream.document(id).collection('userName').document('userName');
var document = await names.get();
userName = document['name'];
myNames.add(userName!);
}
return(myNames);
}
When I added this 2 seconds delay, it worked properly but is there any other way to make it wait for the first future to complete then start the second one?
You can use the await keyword on the future returned from getrecords() to wait for the completion of getrecords() before starting the getNames() function:
Future getNames() async{
await getrecords();
for (var id in myIDs ){
var names = stream.document(id).collection('userName').document('userName');
var document = await names.get();
userName = document['name'];
myNames.add(userName!);
}
return(myNames);
}

Firebase Realtime Database and Flutter - Snapshot has no data

I try to implement the Firebase Realtime Database in Flutter and I want to display updated values in realtime. I try to achieve this with a StreamBuilder.
StreamBuilder Code
StreamBuilder(
stream: GuestbooksDatabase().getAllGuestbooksSync().asStream(),
builder: (context, snapshot) {
if (!snapshot.hasData || !snapshot.data.length) {
return CircularProgressIndicator();
} else {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Text(snapshot.data[index].title);
});
}
}),
The stream function
Future<List<Guestbook>> getAllGuestbooksSync() async {
List<Guestbook> guestbooks = [];
databaseRef.onValue.listen((event) async {
var dataSnapshot = event.snapshot;
if (dataSnapshot.value != null) {
dataSnapshot.value.forEach((key, value) async {
Guestbook guestbook = await Guestbook.fromJson(value);
guestbook.setId(key);
guestbooks.add(guestbook);
});
await Future.delayed(Duration.zero);
print(guestbooks); // Result: All Instances of Guestbook
return guestbooks;
}
});
}
I only see the CircularProgressIndicator() what means that the snapshot has no data.
What's the issue there?
You can use StreamController for this.
Create a new controller -
final StreamController streamController = StreamController<List>.broadcast();
Convert Future<List> to void type for your getAllGuestbooksSync() function and return nothing.
It can and will be called in initState() -
void getAllGuestbooksSync() {
List<Guestbook> guestbooks = [];
databaseRef.onValue.listen((event) async {
var dataSnapshot = event.snapshot;
if (dataSnapshot.value != null) {
dataSnapshot.value.forEach((key, value) async {
Guestbook guestbook = await Guestbook.fromJson(value);
guestbook.setId(key);
guestbooks.add(guestbook);
});
print(guestbooks); // Result: All Instances of Guestbook
streamController.add(guestbooks); // Adding list to the stream
}
});
}
In your StreamBuilder use -
stream: streamController.stream,

How to NOT show the current user in a Grid View?

I have a function called getAllUsers() that returns all users from a database. The problem is that I want GridView.builder() to display all the users except the current user, but despite all the research I did, nothing seems to work out.
If i use the if condition like if(snapshot.data.documents[i].data["username"] != currentUserId within itemBuilder:, it returns a blank tile which represents the current user which creates a gap within the grid view. Thus, it makes the grid view look really bad.
I believe this problem could have been solved if I knew how to include the inequality query in the getAllUsers() method. But my understanding is that Firestore has yet to provide this function/argument.
HomeFragment class
Database _database = Database();
Stream _stream;
String currentUserId;
#override
void initState() {
getCurrentUserId();
getAllUsers();
super.initState();
}
getAllUsers() async {
return await _database.getAllUsers().then((val) {
if (mounted)
setState(() => _stream = val);
});
}
getCurrentUserId() async {
FirebaseUser currentUser = await FirebaseAuth.instance.currentUser();
currentUserId = currentUser.uid;
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _stream,
builder: (context, snapshot) {
return snapshot.data == null ? Center(child: CircularProgressIndicator())
: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child:
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8.0,
mainAxisSpacing: 8.0,
),
itemCount: snapshot.data.documents.length,
itemBuilder: (context, i) {
return Container(
child: Text(snapshot.data.documents[i].data["username"])
);
}
// etc etc..
Database class
getAllUsers() async {
return await _firestore.collection("users").snapshots();
}
I tried to use this, but _stream2 returns null
Stream _stream, _stream2;
getAllUsers() async {
return await _database.getAllUsers().then((val) {
if (mounted) {
List<String> list;
setState(() {
_stream = val;
_stream2 = _stream.where((snapshot) {
_querySnapshot = snapshot;
for (int i = 0; i < _querySnapshot.documents.length; i++)
list.add(_querySnapshot.documents[i].data["userId"]);
return list.contains(currentUserId) == false;
});
});
}
});
}
I also tried this, it is not working
getAllUsers() async {
Stream<QuerySnapshot> snapshots = await _database.getAllUsers();
_stream = snapshots.map((snapshot) {
snapshot.documents.where((documentSnapshot) {
return documentSnapshot.data["userId"] != currentUserId;
});
});
}
Maybe you can try something like this. You filter the query result:
getAllUsers() async {
final Stream<QuerySnapshot> snapshots = await _firestore.collection("users").snapshots();
return snapshots.map((snapshot) {
final result = snapshot.documents
.map((snapshot) => User.fromMap(snapshot.data)
.where((user) => user.id != currentUser.id)
.toList();
return result;
}
}
If you do not have an User class, you can replace some lines with this. But the result will be a list of Map<String, dynamic> instead of a list of User objects.
return snapshots.map((snapshot) {
final result = snapshot.documents
.map((snapshot) => snapshot.data
.where((user) => user['id'] != currentUser.id)
.toList();
return result;
This solution worked well for me.
firestore.collection('your collection').where('x', isNotEqualTo: auth.currentUser!.uid).snapshots();

Flutter retrieve data from firestore as a list

I have been struggling for hours now to retrieve data from firestore as a list so I can show them in a search bar suggestion.
This below function will retrieve data from firestore and return some selected fields as a list.
Future<List> getNewsOnSearchBar() async {
final String _collection = 'news';
final Firestore _fireStore = Firestore.instance;
var newsList = [];
print("1");
Future<QuerySnapshot> getData() async {
print("2");
return await _fireStore.collection(_collection).getDocuments();
}
QuerySnapshot val = await getData();
if (val.documents.length > 0) {
print("3");
for (int i = 0; i < val.documents.length; i++) {
newsList.add(val.documents[i].data["headline"]);
}
} else {
print("Not Found");
}
print("4");
return newsList;
}
And below is my Search bar widget. It has an attribute searchList which is of type List<dynamic>. It accept values such as:
var list = ["a", "b", "c"];
searchList: list
So I want to call that above function getNewsOnSearchBar() and set the list to the attribute searchList. I tried below and it doesn't work.
Widget _showSearchBar(BuildContext context) {
return FutureBuilder(
future: getNewsOnSearchBar(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError || !snapshot.hasData) {
return _progressIndicator();
} else {
return GFSearchBar(
searchList: [], //how can I assign the list return from `getNewsOnSearchBar()` here?
searchQueryBuilder: (query, list) {
return list
.where((item) =>
item.toLowerCase().contains(query.toLowerCase()))
.toList();
},
overlaySearchListItemBuilder: (item) {
return Container(
padding: const EdgeInsets.all(3),
child: Text(
item,
style: const TextStyle(fontSize: 18),
),
);
},
onItemSelected: (item) {},
);
}
});
}
Could you help me, Please?
Since your function getNewsOnSearchBar() is returning a list, you can use snapshot.data.
so your function becomes something like this
return GFSearchBar(
searchList: snapshot.data,
searchQueryBuilder: (query, list) {
return list
.where((item) =>
item.toLowerCase().contains(query.toLowerCase()))
.toList();
},
You can do it in two ways.
1-you can retrieve the documents for firebase and then you can use the Map function to create a list.
2-You can create a Firebase Functions to retrieve the information as you expect.

How to return Future List from DataSnapshot

I want to return a Future List from Firebase Database snapshot and this is my code but I cant get it work properly:
Future<List<CocheDetailItem>> getCoches(ids) async {
List<CocheDetailItem> coches = [];
final dbRef = FirebaseDatabase.instance.reference().child('17082019');
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
dbRef.child(id).once().then((DataSnapshot snapshot) {
if (snapshot.value != null) {
Map<dynamic, dynamic> jsres = snapshot.value;
CocheDetailItem coche = CocheDetailItem.fromJson(jsres);
coches.add(coche);
}
});
print('here is i ${ids[i]} ');
}
return coches;
}
The return I get is empty Area. Can anyone help me with this, please?
Note, dbRef.child(id).once(); is a async function, so you must wait it ends to get your data. Use await keyword to do it.
Future<List<CocheDetailItem>> getCoches(ids) async {
List<CocheDetailItem> coches = [];
final dbRef = FirebaseDatabase.instance.reference().child('17082019');
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
var dataSnapshot = await dbRef.child(id).once();
if (dataSnapshot.value != null) {
Map<dynamic, dynamic> jsres = dataSnapshot.value;
CocheDetailItem coche = CocheDetailItem.fromJson(jsres);
coches.add(coche);
}
print('here is i ${ids[i]} ');
}
return coches;
}
well.. I don't use firebase but I send a request to my database with this (you have to use async and await)
Future<List<PlaceModel>> getPlaces(String ciudad, String tipo) async {
Uri request = Uri.http('domain.com', '/getPlaces/$ciudad/$tipo');
ResponseModel response = ResponseModel.fromJsonMap(json.decode((await http.get(request)).body));
List<PlaceModel> items = [];
if(response.res) {
if(response.value != null) {
for(var item in response.value) {
final place = PlaceModel.fromJsonMap(item);
items.add(place);
}
}
}
print("Places Loaded: ${items.length}");
return items;
}
I use my ResponseModel to convert the json answer in an object.
Then I show it with the future builder:
class PlacesListPage extends StatelessWidget{
final _selectedLocation, _selectedList;
PlacesListPage(this._selectedLocation, this._selectedList);
final _provider = PlaceProvider();
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(8.0),
child: FutureBuilder(
future: _provider.getPlaces(_selectedLocation, _selectedList), // async request to database
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) { // check when your request is done
if(snapshot.data.length != 0) { // check if any data has been downloaded
return ListView.builder( // build a listview of any widget with snapshot data
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
// i just return containers but you can use any custom widget, it's like a forEach and use the index var
return Container(
child: Text(snapshot.data[index]),
);
},
);
} else {
// If you don't have anything in your response shows a message
return Text('No data');
}
} else {
// shows a charge indicator while the request is made
return Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
}