After updating Flutter Firebase receive Bad State: field does not exist - QueryDocumentSnapshot - firebase

Prior to the update, document['field'] would result in null if it did not exist. Now it throws a Bad State error. The null response is needed because the field does not exist in some historical data and/or optional data in the Firebase collection. Confirmed this in the following report as well:
https://github.com/FirebaseExtended/flutterfire/issues/3826
Is there a way to capture the error and ignore it or set it to null or empty string?
static Pool dataFromDocument(QueryDocumentSnapshot document) {
return Pool()
..authUID = document.get('authUID')
..documentID = document.id
..propertyManagerID = document['propertyManagerID'] as String
}
static Stream<QuerySnapshot> getData(String authUID) {
CollectionReference poolRef = FirebaseFirestore.instance.collection(dataDB);
Query query = poolRef.where('authUID', isEqualTo: authUID.trim());
return query.snapshots();
}

With null safety, String is not nullable. However, String? is.
Change the line:
..propertyManagerID = document['propertyManagerID'] as String
To:
..propertyManagerID = document['propertyManagerID'] as String?
Your Pool will also have to have the property propertyManagerID as a String? in the class definition.

Related

Variable declared outside of if statement not retaining assignment (Flutter/Firebase Realtime Database)

I'm trying to set a variable token to the data I retrieve from Firebase Realtime database. token is assigned within a if statement and the assignment works but outside of the if statement, token becomes null. token is declared outside of the if statement so I'm a bit confused why it's not being assigned to the data from Firebase.
sendNotification(
BuildContext context, String title, String message, String userID) async {
DatabaseReference databaseReference = tokensRef.child(userID);
DataSnapshot snapshot = await databaseReference.once();
String token;
if (snapshot != null) {
databaseReference.get().then((result) {
Map<dynamic, dynamic> values = result.value;
if (values == null) {
token = null;
} else {
token = values['deviceToken'];
//print(token);
}
});
}
print(token);
}
The get() call loads data from the database, which happens asynchronously (since it may take some time). While the data is being loaded, the rest of your code continues execute so that the user can continue using the app. Then when the data is available, your then is executed.
This means that your print(token) outside of the then now runs before the token = values['deviceToken'] is ever execute, so it will never print the value you want because that value hasn't loaded yet.
For this reason, any code that needs the data from the database needs to be inside the then code block, like your commented //print(token).
If you want to return the data, you will need to return the Future that get() returns.
Alternatively you can use async/await, which make the then part a bit more recognizable:
if (snapshot != null) {
var result = await databaseReference.get()
Map<dynamic, dynamic> values = result.value;
if (values == null) {
token = null;
} else {
token = values['deviceToken'];
}
}
print(token);
Behind the scenes this still does the exact same thing. The compiler just generates some of that code for you, allowing you to look at something that's a bit closer to what you're probably used to.
Also see:
The Flutter documentation on asynchronous programming: futures, async, await.
how to check whether the dataSnapshot null on Flutter Firebase (Realtime) Database plugin
How to use await instead of .then() in Dart

Flutter: custom model returning null "List<dynamic> is not a subtype of List<String>" | "type 'Null' is not a subtype of type 'Product'"

I am calling a function that returns a future of custom model Products from Futurebuilder, and I am getting an error saying:
type 'Null' is not a subtype of type 'Product'
This is my Product model:
class Product {
#required
final String? id;
#required
final String? title;
#required
final String? discription;
#required
final double? price;
#required
final List<String>? info;
#required
final List<String>? imageUrl;
final String? category;
final offPerCent;
final String? seller;
Product({
this.id,
this.discription,
this.info,
this.imageUrl,
this.price,
this.seller,
this.title,
this.category,
this.offPerCent
});
}
And this is the function:
Future<Product> getbyId(String id) async {
var snapshot =
await firestore.collection('products').where('id', isEqualTo: id).get();
Map<String, dynamic> data = snapshot.docs[0].data();
return new Product(
id: data['id'],
title: data['title'],
price: data['price'],
seller: data['seller'],
category: data['category'],
discription: data['discription'],
imageUrl: data['imageUrl'],
info: data['info'],
offPerCent: data['offPerCent'],
);
}
I think there is some problem with the info and imageUrl field, both them are List<String> when I comment on both that line everything works fine.
Can someone help me out here?
Try
info: List<String>.from(data['info'])
Do the same for image.
Explanation:
The error that happened which displayed a message saying product is null, is actually caused by another error.
This error arises while creating your product object. And it happens in the fields mentioned above, where you specified a List<String> will come from Firebase. But Firebase SDK lists objects as List<dynamic>.
So probably there was another error in debug console saying
List<dynamic> is not a subtype of List<String> as in cast.
When you tell it to create a List from the elements inside the List, it works well, as long as those elements are actually strings. If you go to firebase, and change any of the items into anything besides string, you'll have another error saying int is not a subtype of type string for example.
Because creating a Product object from the map returned from firebase, goes through the steps in your method fromMap() in the model, it stops when it hits the error explained above, and therefore a product object never finishes creating it from a map.
Then, the widget waiting for the Product object to be returned, never receives what it was waiting for, you get the error in your original question.
type 'Null' is not a subtype of type 'Product'
You can avoid the third scenario, to account for anything coming from firebase, which is not a string, for example someone inputs an integer or double or null, by doing this:
info: List<String>.from(data['info'].map((element)=> element.toString()).toList());
This is an example:
List a = ['a', 1, '2', null]; //this is List<dynamic>
// List<String> b = List<String>.from(a); //This will give you an error when it encounters the number 1, it won't even continue.
List<String> b = List<String>.from(a.map((element) => element.toString()).toList());
print(b); // prints this => [a, 1, 2, null]
//because all the elements inside were converted to a string first.

How to store map data in array with Flutter Firestore

I try to make an app about health blog with Flutter. I want to store some data with arrays that contain map data. Although I can manually perform this on the Firestore, I'm getting some errors in coding.
Here is Firestore screenshot
Here is the code which I try to add map data to the array.
Future<bool> updateUserCases(String userId, Map newCase) async {
await _firestoreDB.collection("users").doc(userId).update({
"userCases" : FieldValue.arrayUnion([newCase])
});
return true;
}
I can add my map data to Firestore, but when I try to add it to the array, I get this error.
E/flutter (10661): [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: [cloud_firestore/unknown] Invalid data. FieldValue.serverTimestamp() can only be used with set() and update()
And this is my "Case Model" which I want to add into the array
class CaseModel {
final String caseId;
final String caseTitle;
final String caseBody;
final Map caseOwner;
Timestamp caseDate;
bool caseSolve;
List<String> casePhotos;
String caseTag;
CaseModel(
{#required this.caseOwner,
this.caseId,
this.caseTitle,
this.caseBody,
this.caseDate,
this.caseTag});
Map<String, dynamic> toMap() {
return {
"case_id": caseId,
"case_title": caseTitle,
"case_body": caseBody,
"case_owner": caseOwner,
"case_date": caseDate ?? FieldValue.serverTimestamp(),
"case_solve": caseSolve,
"case_photos": casePhotos,
"case_tag": caseTag,
};
}
Could you help if there is a way I can fix this problem? Thank you.

Querying firebase real time database using firebase package unknown input parameter Flutter

I use firebase package for web version of my app and firebase_database for device version.
When querying a specific node using firebase_database the .once() method doesn't ask for any input parameter but firebase counterpart expects a String eventType input. I looked in the API docs but I couldn't find out what should be passed in..
What string should be passed in?
As always thank you very much for the help.
The firebase_database methods:
Future<DateTime> getFirebaseSyncDate(
String cityDb, String regionDb, String countryDb) {
// TODO: implement getFirebaseSyncDate
return ref
.child('Firebase Sync Date')
.orderByChild('Sync Date')
.limitToFirst(1)
.once()
.then((DataSnapshot snap) {
DateTime syncDate = snap.value['Sync Date'];
return syncDate;
});
}
and the firebase version:
Future<DateTime> getFirebaseSyncDate(
String cityDb, String regionDb, String countryDb) {
var ref = firebase.database().ref();
return ref
.child('Firebase Sync Date')
.orderByChild('Sync Date')
.limitToFirst(1)
.once('') // <=== String eventType ??
.then((snap) {
DateTime syncDate = snap.snapshot.val()['Sync Date'];
return syncDate;
});
}
once receive an eventType, one of the following strings: value, child_added, child_changed, child_removed, or child_moved.
In your case, I think it must be value
Check this link for more details

Why is Future<String> returning "Instance of Future<String>" and not the return value in my Stream

Im trying to get the User Id of the current user signed in. This is how I;m trying to get the UserID as String, where the functions are printing the $userId and also $uid.
String userId;
Future<String> getUser() async {
final FirebaseUser user = await FirebaseAuth.instance.currentUser();
final String uid = user.uid.toString();
print("uid in getuser function $uid");
userId = uid;
return uid;
}
And this is the Stream in which im trying to get the UserID(uid) into (both the Futures and Stream are in the same class)(the return statement in the Stream can be ignored)
// get hotel stream
Stream<List<Hotel>> get userHotels {
print("This is the uid ${getUser().toString()} in Stream");
return ownerCollection.document(uid).collection("hotels").snapshots()
.map(_hotelListFromSnapshot);
}
Printing $getUser().toString() gives this in the console : "This is the uid Instance of 'Future' in Stream" and not printing the actual UserID.
And printing $userId gives this in the console : " This is the uid null in Stream"
Please help, I want to get the UserId of the current user signed in the Stream
As defined above getUser() returns a Future<String>. So toString() on that will return Instance of 'Future'.
What you probably want to do is wait for getUser() to finish first.
You can do that by using async generators, with async* and yield*.
// get hotel stream
Stream<List<Hotel>> get userHotels async* {
final uid = await getUser();
print("This is the uid $uid in Stream");
yield* ownerCollection.document(uid).collection("hotels").snapshots()
.map(_hotelListFromSnapshot);
}
Using async* means you'll return a Stream. Then after waiting for getUser() to finish, you can put values on that stream with yield. yield* specifically means you are putting the values of another Stream on the one you are returning.
Your function getUser() will work the the same as the FirebaseAuth.instance.currentUser() function you are using, and will return a future. If you want to print it, you need to either use await as you did before, or the then function.
When it comes to using it in your function, you need to use the result of the future to create the stream. Take a look at this answer for how to do that.

Resources