How to store map data in array with Flutter Firestore - firebase

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.

Related

Getting a list of friends and then displaying a friends list

I'm struggling with how I can get the data of a user's friends from Firebase's Realtime Database to build it out in Flutter. The structure of the database looks like this:
enter image description here
Essentially, in my code, I'm listening to changes in an user's friends list from the database and then getting the friend ID so that I can query the rest of the friend's metadata (image, name, etc.) to build out the friend's list in the app. The issue I'm running into is that I can't seem to get the Json data to map correct and I get the following error:
Unhandled Exception: type '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'String'
Any insight would be appreciated
class _FriendsScreenState extends State<FriendsScreen> {
#override
void initState() {
getUserFriendsList();
super.initState();
}
getUserFriendsList() async {
rtdb.child('friends').child(widget.currentUserID).onValue.listen((event) {
final data = new Map<String, dynamic>.from(event.snapshot.value);
data.keys.forEach((element) async {
DataSnapshot userInfo = await usersRef.child(element).get();
User users = User.fromJson(json.decode(userInfo.value));
});
});
}
factory User.fromJson(Map<String, dynamic> parsedJson) {
return User(
imageUrl: parsedJson['userImageUrl'],
userFirstName: parsedJson['userFirstName'],
userLastName: parsedJson['userLastName'],
userID: parsedJson['userID'],
);
}
I think you have a problem in:
User users = User.fromJson(json.decode(userInfo.value));
why u have used JSON decode (json.decode & User.fromJson) twice?
However, for further analysis, you should provide data and log it.
There's no need to decode the json as DataSnaphot.value "returns the contents of [the] data snapshot as native types."
So userInfo.value does not return a String, it returns a Map instead.
Solution:
Convert the result to a Map<String, dynamic> from the _InternalLinkedHashMap<dynamic, dynamic> type it returns.
Change this line:
User users = User.fromJson(json.decode(userInfo.value));
to this:
User users = User.fromJson(Map<String, dynamic>.from(userInfo.value));

how to read a single files in firebase

so i want to read the roles of my users, i know how to read collection and document,but how to read a field from the documents?
this what i've been trying,
#override
void readRole(){
final path = APIPath.role(uid);
final reference = FirebaseFirestore.instance.collection(path);
final snapshots = reference.snapshots();
snapshots.listen((snapshot) { snapshot.docs.forEach((snapshot) => print(snapshot.data()));
});
}
static String role (String uid) => 'users/$uid';
Firestore doc.data() has type of Map<String, dynamic> , meaning Json.
so you could try something like this:
final Map<String, dynamic> data = snapshot.data();
final String role = data['role'];
But the best way would be to create a user entity and use Json_serializable: https://pub.dev/packages/json_serializable
and convert the firestore data object to your user entity;
But by checking your role() function, i am not sure to understand it correctly, user's role is just the string 'users/$uid' ?

How do I properly cast a response from a Firebase Function call in my Flutter app

I'm teaching myself Flutter by building a simple meal planner app. Part of what I want to do is to use a Firebase function for making calls to the API. I'm using the Spoonacular API, and I do not want to store the API key on the app itself, hence the Firebase backend.
I've set up a file called cloud_functions.dart, which I plan on using to make calls to my Firebase functions. The call to get the recipes is as follows:
Future<SearchRecipesComplexResponseBody> getRecipes() async {
HttpsCallable callable = getCallable('searchRecipes');
try {
final HttpsCallableResult<SearchRecipesComplexResponseBody> results = await callable({'number': 20, 'offset': 0});
print('Results: ');
print(results);
print('Results data:');
print(results.data);
return results.data;
} catch (e) {
print('Error: ');
print(e);
return null;
}
}
HttpsCallable getCallable(String callableName) {
FirebaseFunctions functions = FirebaseFunctions.instance;
if (kDebugMode) {
print('Running in debug mode');
functions.useFunctionsEmulator(origin: 'http://localhost:5001');
}
return functions.httpsCallable(callableName);
}
The code for SearchRecipesComplexResponseBody is as follows:
import 'package:meal_planner/models/recipe.dart';
import 'package:json_annotation/json_annotation.dart';
part 'search_recipes_complex_response_body.g.dart';
#JsonSerializable()
class SearchRecipesComplexResponseBody {
final int offset;
final int number;
final List<Recipe> results;
final int totalResults;
SearchRecipesComplexResponseBody({this.offset, this.number, this.results, this.totalResults});
factory SearchRecipesComplexResponseBody.fromJson(Map<String, dynamic> json) {
return _$SearchRecipesComplexResponseBodyFromJson(json);
}
}
The code for Recipe is as follows:
#JsonSerializable()
class Recipe {
final int id;
#JsonKey(includeIfNull: false)
final int calories;
#JsonKey(includeIfNull: false)
final String carbs;
#JsonKey(includeIfNull: false)
final String fat;
final String image;
final String imageType;
#JsonKey(includeIfNull: false)
final String protein;
final String title;
Recipe({#required this.id, this.calories, this.carbs, this.fat, this.image, this.imageType, this.protein, #required this.title});
factory Recipe.fromJson(Map<String, dynamic> json) {
return _$RecipeFromJson(json);
}
}
While I do get the data back that I'm expecting, there's something going on with the casting that I get this error when running the code:
type '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'Map<String, dynamic>'
When I went to debug the code, breaking on the print(results) line in the cloud_functions.dart file, I saw that the data does seem to match the format that I'm expecting
I've attempted to use the json_serializable utility to generate the JSON serialization code, but that didn't work either. I've tried removing the extraneous fields in the Recipe class to no avail.
I think the issue is something to do with the fact that I've got a property on the SearchRecipesComplexResponseBody that's a list of Recipes, but I can't seem to figure out what I'm doing wrong here. For all I know, I could be barking up the wrong tree. Does anyone have any ideas?
RESOURCES CHECKED:
Flutterfire Cloud Functions documentation - https://firebase.flutter.dev/docs/functions/usage/
Search on HttpCallableResult - https://duckduckgo.com/?q=flutterfire+httpcallableresult&ia=web
https://medium.com/codespace69/flutter-working-with-data-json-json-and-serialization-f90165b659d0
I figured it out
I updated the getRecipes function in cloud_functions.dart to be as follows:
Future<SearchRecipesComplexResponseBody> getRecipes() async {
HttpsCallable callable = getCallable('searchRecipes');
try {
final HttpsCallableResult results = await callable({'number': 20, 'offset': 0});
var convertedResult = Map<String, dynamic>.from(results.data);
SearchRecipesComplexResponseBody data = SearchRecipesComplexResponseBody.fromJson(convertedResult);
return data;
} catch (e) {
print('Error: ');
print(e);
return null;
}
}
I saw that I already had a fromJson function defined on my SearchRecipesComplexResponseBody class, but I hadn't been taking advantage of it. I needed to convert the response I got back from Firebase from an _InternalLinkedHashMap<dynamic, dynamic> to the Map<String, dynamic> type that fromJson uses.
I also needed to add anyMap: true inside my JsonSerializer attributes to get the nested list of Recipes in order for its fromJson. I'm not sure why that worked. Anyone have any thoughts?
You may use this to convert from _InternalLinkedHashMap to Map<String, dynamic>. This will get you overcome the error type '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'Map<String, dynamic>':
HttpsCallable callable = functions.httpsCallable('your-function');
final results = await callable();
final data = Map<String, dynamic>.from(results.data);

How do I get the key of a value in FirebaseDatabase using Flutter / Dart?

I'm trying to get the key of a child that I've already returned from the database. In the example below, I have the user selected as Carlos Sainz and I'm trying to get "csainz" however the code below returns "email".
DatabaseReference keyRef = FirebaseDatabase.instance.reference();
await keyRef.child('users')
.child('email')
.equalTo(userList[0].email) // userList[0].email returns Carlos' email
.once()
.then((DataSnapshot dataSnapshot) {
String newKey = dataSnapshot.key;
print(newKey);
});
Here is how my db is setup:
Two problems:
To order/filter on a specific property of each child node, you need to use orderByChild(...) and not just child(...). Right now your code reads /users/email, which doesn't exist.
When you execute a query against the Firebase Database, there will potentially be multiple results. So the snapshot contains a list of those results. Even if there is only a single result, the snapshot will contain a list of one result.
Your code doesn't handle the list, but prints the key of the location against which the query was executed: users.
So to fix both would look something like this:
DatabaseReference keyRef = FirebaseDatabase.instance.reference();
await keyRef.child('users')
.orderByChild('email')
.equalTo(userList[0].email)
.onChildAdded.listen((Event event) {
print('${event.snapshot.key}');
}, onError: (Object o) {
final DatabaseError error = o;
print('Error: ${error.code} ${error.message}');
});
});
You can also use .once().then(...) but will then have convert dataSnapshot.value to a map, and show the key(s) from that map. Not check, but it should be something like this:
DatabaseReference keyRef = FirebaseDatabase.instance.reference();
await keyRef.child('users')
.orderByChild('email')
.equalTo(userList[0].email)
.once()
.then((DataSnapshot dataSnapshot) {
String newKey = dataSnapshot.value.keys[0];
print(newKey);
});

Flutter firebase database.set(object) issue

I have a class Product and it is in List plist
Now I need to call the firebase database.set(plist) this is working with Java but when I tried to do it with flutter dart it showing error anybody have the solution for this problem
From StackOverflow, I understand use database.set('{"a":"apple"}) but when I am dealing with List I can't use this solution
update error message
error called Invalid argument: Instance of 'Product'
My code
String table_name="order";
FirebaseAuth.instance.currentUser().then((u){
if(u!=null){
FirebaseDatabase database = FirebaseDatabase(app: app);
String push=database.reference().child(table_name).child(u.uid).push().key;
database.reference().child(table_name).child(u.uid).child(push).set( (productList)).then((r){
print("order set called");
}).catchError((onError){
print("order error called "+onError.toString());
});
}
});
}
We cannot directly set object in Firebase. Unfortunately in Flutter there is no easy solution like java json.
Data types that are allowed are String, boolean, int, double, Map, List. inside database.set().
We can have a look at the official documentation of Flutter https://pub.dev/documentation/firebase_database/latest/firebase_database/DatabaseReference/set.html
Try setting object like this
Future<bool> saveUserData(UserModel userModel) async {
await _database
.reference()
.child("Users")
.child(userModel.username)
.set(<String, Object>{
"mobileNumber": userModel.mobileNumber,
"userName": userModel.userName,
"fullName": userModel.fullName,
}).then((onValue) {
return true;
}).catchError((onError) {
return false;
});
}
I hope this code will be helpful.
Extending a little bit an answer given as a comment above
You basically have to create an auxiliary map beforehand:
Map aux = new Map<String,dynamic>();
And then iterate through the array that you have adding the corresponding map for each child that you want to add:
productList.forEach((product){
//Here you can set the key of the map to whatever you like
aux[product.id] = product.toMap();
});
Just in case, the function toMap inside the Product class should be something like:
Map toMap() {
Map toReturn = new Map();
toReturn['id'] = id;
toReturn['name'] = name;
toReturn['description'] = description;
return toReturn;
}
And then, when you are calling the set function to save to firebase you can do something like:
.set({'productList':aux,})
Hope this was helpful to someone.

Resources