Do Firebase and Flutter support one-time reads on the web? - flutter

I'm using Firebase and Flutter to read a List of Objects (EspecieModel). It's working perfect in IOS and Android, however It doesn't work on the Web (an empty List is retrieved).
I'm reading from Firebase as follows ...
Future<List<EspecieModel>> cargarTipoEspecie() async {
final List<EspecieModel> tipoEspecie = [];
Query resp = db.child('PATH/tipoespecie');
resp.onChildAdded.forEach((element) {
final temp = EspecieModel.fromJson(Map<String,dynamic>.from(element.snapshot.value));
temp.idEspecie = element.snapshot.key;
tipoEspecie.add(temp);
});
await resp.once().then((snapshot) {
print("Loaded - ${tipoEspecie.length}");
});
return tipoEspecie;
}
And I'm using a Future Builder to display the information...
FutureBuilder(
future: _tipoEspecieBloc.cargarTipoEspecie(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
// print(snapshot.connectionState);
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData{
// print(snapshot.data);
final _especies = snapshot.data;
return Stack(
children: <Widget>[
ListView.builder(
itemCount: _especies!.length,
itemBuilder: (context, i) {
return _crearItem(context, _especies[i], i);
},
),
],
);
} else if (snapshot.hasError) {
print(snapshot.error);
return Text(snapshot.error.toString());
}
else {
return //CircleProgressIndicator Code
);
}
},
),
I can't identify what I'm doing wrong
How to do a one-time Firebase Query that works well on IOS, Android, and also on the Web??

This won't work:
resp.onChildAdded.forEach((element) {
final temp = EspecieModel.fromJson(Map<String,dynamic>.from(element.snapshot.value));
temp.idEspecie = element.snapshot.key;
tipoEspecie.add(temp);
});
await resp.once().then((snapshot) {
print("Loaded - ${tipoEspecie.length}");
});
return tipoEspecie;
The onChildAdded is not part of the await, so I doubt everything waits the way you seem to want. Just adding await in one place, does not make the rest of your code synchronous.
Instead consider using just once() and then populating your tipoEspecie array by looping over snapshot.value.values (a relatively new addition to the API).
var snapshot = await resp.once();
snapshot.value.values.forEach((node) {
final temp = EspecieModel.fromJson(Map<String,dynamic>.from(node.value));
temp.idEspecie = node.key;
tipoEspecie.add(temp);
});
return tipoEspecie;
Note: I'm not completely sure of the .forEach and the code in there. So if you get errors there, check what type you get back from .values and what node is, to get the correct key and values from it.

Related

Can I Use "FutureBuilder()" under Void Method in Flutter

This is My Code.
Future<void> SendOrderDetails() async{
Row(
children: [
FutureBuilder(
future: topcart.getData(),
builder: (context, AsyncSnapshot<List<Cart>> snapshot) {
for(int i = 0; i<itemcount; i++)
{
if(itemcount>listModel2.data!.length) {
listModel2.data?.add(Model2(
ORDER_INFO_ID: 1,
PRODUCT_ED_ID: 2,
QTY: quantitycalcule.toString(),
UNIT_PRICE:'00',// snapshot.data![i].Book_initional_price!.toString(),
CHGED_BY:1,
CHGED_DATE: DateTime.now().toString(),
STATUS: 'P',
),);
}
}
return const Text('');
}
),
],
);
}
When I Call This, "FutureBuilder" did not run. I need "snapshot" in If condition. Please Help me.
I'm not sure what your code is trying to accomplish but there are a few things I can see that could be potentially causing issues:
You are calling FutureBuilder inside a Future and there is no await inside your future so it's really not going to work right.
The whole point of a FutureBuilder is to builds itself based on the latest snapshot of interaction with a Future. Maybe you can explain why this structure is the way it is.
topcart.getData() - Should not be in the future builder. You need to get something like
// This needs to be outside the build method, maybe initState()
Future<TYPE> _gd = topcart.get()
// Future Builder in the build method
FutureBuilder<TYPE>(
future: _gd,
builder: (BuildContext context, AsyncSnapshot<TYPE> snapshot) {});
Ideally, within the FutureBuilder() you want to check connection and data like:
if (snapshot.connectionState == ConnectionState.waiting) {
return // Progress indicator widget
} else if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return // Error Widget or Text
} else if (snapshot.hasData) {
return // Process data here
} else {
return // Empty set returned
}
} else {
return Text('State: ${snapshot.connectionState}');
}
this method returns nothing so you don't have way to check whether the future builder run or not, try to use print and check your debug console (or try to debug your code by using break points) if it works then do what ever you want to do
additional question: what state management you are using?
The whole point of FutureBuilder is to build (and so to return) a Widget based on a snapshot.
As it seems you don't need this Widget at all in your code, couldn't you just skip the Builder altogether ?
Something like this :
Future<void> SendOrderDetails() async {
var data = await topcart.getData();
for (int i = 0; i < itemcount; i++) {
if (itemcount > listModel2.data!.length) {
listModel2.data?.add(
Model2(
ORDER_INFO_ID: 1,
PRODUCT_ED_ID: 2,
QTY: quantitycalcule.toString(),
UNIT_PRICE: data![i].Book_initional_price!.toString(),
CHGED_BY: 1,
CHGED_DATE: DateTime.now().toString(),
STATUS: 'P',
),
);
}
}
}

Flutter FutureBuilder does not stop showing CircularProgressIndicator

I am trying to receive data using a FutureBuilder, but it hangs on the CircularProgressIndicator. I think it's remaining on ConnectionState.waiting but I'm not sure why.
#override
initState() {
_widgetList = getWidgetList();
}
Stream<List<String>> getFriendUUIDS() => Firestore.friends
.doc(gameManager.myself.uuid)
.snapshots()
.map((snapshot) => ((snapshot.data()?.keys)?.toList()) ?? []);
Future<List<MomentWidget>> getWidgetList() async{
List<MomentWidget> widgetList = [];
Set<String> momentIds = Set();
await for (final uuids in getFriendUUIDS()){
for (String uuid in uuids){
DocumentSnapshot<Map<String, dynamic>> values = await Firestore.users
.doc(uuid)
.get();
for (String momentId in values.data()?['moments'] ?? [] ){
momentIds.add(momentId);
}
}
}
for (String momentId in momentIds){
DocumentSnapshot<Map<String, dynamic>> values =
await Firestore.instance.collection('moments').doc(momentId).get();
Map<String, dynamic>? data = values.data()!;
String downloadURL = await storage.ref('moments/$momentId').getDownloadURL();
MomentWidget widget = MomentWidget(numLikes: data['liked_by'].length ,
location: data['location'],
date: data['timestamp'],
names: data['taggedFriends'].toString(),
shotBy: data['taken_by'], image: NetworkImage(downloadURL));
widgetList.add(widget);
}
return widgetList;
}
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Container(
height: size.height,
width: size.width,
child: FutureBuilder(
future: _widgetList,
builder: (context, AsyncSnapshot<List<MomentWidget>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
return ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemBuilder: (context, pos) {
return snapshot.data![pos];
},
);
}
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
default:
return Text('Unhandled State');
}
}
),
);
}
I have tried to get the Future inside of initState(), and have tried to use snapshot.hasData instead, to no avail.
I have encountered a similar problem. When building an object from json , if the types don't match , it can quietly fail. I do not think your widgetList is ever returned. In my case I had a variable "cost" that I thought would be of type int , however in the database it was of type String. It always quietly failed and never showed the markers on the map widget
So:
Check how many times that loop of yours is executed. Probably only once and then it quietly fails
If the above happens:
Makes sure the types of your variables match the ones from the database. Comment out every variable one by one to find where the problem is.
Let me know if it works

Flutter: StreamBuilder with Firebase

I have a ListView of objects from Firebase in which I would like to have it refresh using a StreamBuilder when the data changes.
I can load up my list fine & when data changes my list does refresh.
The issue I am having is instead of the ListTile that has the change just updating, I see that tile being duplicated so I see the new change & the old change.
Here's my setup:
final ref = FirebaseDatabase.instance.reference();
late DatabaseReference itemRef;
late FirebaseDatabase database = FirebaseDatabase();
late StreamSubscription _objectInfoStreamSub; // Not sure if needed?
late List<CustomObject> data = [];
#override
void initState() {
super.initState();
final keys = Global.kData.keys;
for (final key in keys) {
// Initialize this...
itemRef = database.reference().child('ABC').child(key.toString());
}
// Load the data...
_setListeners();
}
// Here is where I load my data initially...
Future<void> _setListeners() async {
// Clear out our data before we reload...
data.clear();
final keys = Global.kData.keys;
for (final key in keys) {
_objectInfoStreamSub =
ref.child("ABC").child(key.toString()).onValue.listen(
(event) {
setState(() {
// Mapped the data...
final firebaseData = Map<String, dynamic>.from(event.snapshot.value);
// Create the Room...
final room = CustomObject.fromJson(firebaseData);
// Check if blocked...
// Logic to see if user is blocked
// check if isArchived
// Logic to see if room is archvied
if (!isBlocked && !isArchived) {
if (!data.contains(room)) {
data.add(room);
}
}
// Sort by date
// Logic to sort so the most recent is at top
});
},
);
}
}
// Here is my updated StreamBuilder...
SliverToBoxAdapter(
child: StreamBuilder<dynamic>(
stream: itemRef.child('ABC').onValue,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasData &&
snapshot.connectionState == ConnectionState.active) {
return ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: data.length,
itemBuilder: (BuildContext context, int index) {
return ChatRoom(
data: data[index],
);
},
);
} else {
return Container();
}
},
),
),
Not sure if this causes your problem, but try wrapping the ListView.builder() with StreamBuilder instead of only it's items. Because in your current state of code, if you would add another item and your data.length would change, the ListView.builder() wouldn't get rebuilt and it wouldn't build new data.

Future Buider does not get the data until after multiple iterations. In Flutter

I have an asynchronous function that obtains information from my bd in firebase, when debugging this code fragment I can see that the data is obtained without any problem, this data will be used to display in widgets and I pass it through a future builder, the problem is that although when debugging I realize that the data are there, Future builder does not detect them and snapshot has null value, it is until after several iterations when snapshot finally has data and allows me to use them, I do not understand what is wrong in the construction of my Future Builder.
Here is the code of my function where I get the data and the construction of the Future Buider.
Function where data are obtained.
Future<List<Guide>> getGuidesList() async {
var guidesProvider = Provider.of<GuidesProvider>(context, listen: false);
Checkuser data = await ManagerDB().checkuser(auth.email);
List<Guide> aux = new List();
Guide guide;
List guides = await guidesProvider.setGuidesFromUser(data);
if (guides != null) {
for (var i = 0; i < guides.length; i++) {
await guides[i].get().then((DocumentSnapshot guides) {
guide = Guide.fromMap(guides.data(), guides.reference.path);
aux.add(guide);
});
}
if (this.mounted) {
setState(() {});
}
print('Guias cargadas correctamente');
return aux;
} else {
print('Lista vacia');
return aux;
}
}
Fragmento de Funcion donde creo mi FutureBuider.
return Scaffold(
resizeToAvoidBottomInset: false,
key: _scaffoldKey,
appBar: appBar,
drawer: DrawerNavigationMenu(
getContext: widget.getcontext,
),
body: FutureBuilder<List<Guide>>(
future: getGuidesList(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return ListCourses(
getContext: widget.getcontext,
items: snapshot.data,
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
),
);
if (this.mounted) {
setState(() {});
}
Delete this part. You are unnecessarily rebuilding your scaffold and re-calling FutureBuilder. Let FutureBuilder take care of processing the future and rebuilding the scaffold for you.

how to use async/await in Listview builder

I have a table in my sqflite database containing the call history of the respective users. Now on my Call history page in flutter, I am showing the complete history data, fetched from sqflite up till now its working fine. But now I want to check whether the numbers are in my history list exist in contact. If yes, then I want to show their contact name and avatar in the list. Otherwise I just want to show the number. Here's my code:
List<Map<String, dynamic>> ok =
await DatabaseHelper.instance.getAllLogs(argv);
setState(() {
queryRows = ok;
});
var historyRecords = List<HistoryRecord>.from(queryRows.map((row) => HistoryRecord.fromJson(row)));
FutureBuilder<List<HistoryRecord>>(
future: _checkContact(historyRecords),
builder: (context, snapshot) {
return ListView.builder(
itemCount: historyRecords.length,
itemBuilder: (context, index) {
print(historyRecords[index]);
},
);
},
)
Future<List<HistoryRecord>> _checkContact(List<HistoryRecord> rec)async
{
for(int i=0;i<rec.length;i++) {
var conhere=await
ContactsService.getContactsForPhone(rec[i].callHistoryNumber);
//how should i map iterable contact list to Historyrecord
}
}
To call an asynchronous call in UI, you can use FutureBuilder. You can run a check for each and every items in the list like this:
FutureBuilder<bool>(
initialData: false, // You can set initial data or check snapshot.hasData in the builder
future: _checkRecordInContact(queryRow), // Run check for a single queryRow
builder: (context, snapshot) {
if (snapshot.data) { // snapshot.data is what being return from the above async function
// True: Return your UI element with Name and Avatar here for number in Contacts
} else {
// False: Return UI element withouut Name and Avatar
}
},
);
However I don't recommended this method since there would be too many async calls that will slow down the app. What I recommend is to run a check for all items in the queryRows first, then send it to UI.
First of all you should use an Object to represent your history records instead of Map<String, dynamic> to avoid bugs when handling data. Let's say we have a list of HistoryRecord objects, parse from queryRows. Let's call this list historyRecords
var historyRecords = List<HistoryRecord>.from(queryRows.map((row) => HistoryRecord.fromJson(row)));
Each object should have a Boolean property fromContact to check if it's in the Contacts or not. We can then do this:
Widget buildListView(historyRecords) {
return FutureBuilder<List<HistoryRecord>>(
future: _checkContact(historyRecords), // Here you run the check for all queryRows items and assign the fromContact property of each item
builder: (context, snapshot) {
ListView.builder(
itemCount: historyRecords.length,
itemBuilder: (context, index) {
if (historyRecords[index].fromContact) { // Check if the record is in Contacts
// True: Return your UI element with Name and Avatar here
} else {
// False: Return UI element without Name and Avatar
}
},
);
},
);
}
You can then check the contacts with the following property of HistoryRecord and function:
class HistoryRecord {
bool fromContact;
Uint8List avatar;
String name;
//... other properties
HistoryRecord({this.fromContact, this.avatar, this.name});
}
Future<List<HistoryRecord>> _checkContact(List<HistoryRecord> rec) async {
for (int i = 0; i < rec.length; i++) {
Iterable<Contact> conhere =
await ContactsService.getContactsForPhone(rec[i].callHistoryNumber);
if (conhere != null) {
rec[i]
..name = conhere.first.displayName
..avatar = conhere.first.avatar
..fromContact = true;
}
}
return rec;
}
You can use FutureBuilder to check each number like:
ListView.builder(
itemCount: history.length,
itemBuilder: (context, index) {
FutureBuilder(
future: checkContactExists(history[0]),
builder: (context, snap){
if(snap.hasData){
if(snap.data = true){
return PersonContact();
}else{
return JustNumber();
}
}
return Loading();
}
)
},
);