Flutter Firebase Database, DataSnapshot? how to get values and keys, null safe - flutter

Using firebase_database: ^7.1.1
Before null safety i used to do like this to get the data from firebase database:
DataSnapshot dataSnapshot = await References.getReferenceYears().once();
Map<dynamic, dynamic> values = dataSnapshot.value;
values.forEach((key, value) {...}
Now when i try do like this, i get the error as bellow:
DataSnapshot? dataSnapshot = await References.getReferenceYears().once();
Map<dynamic, dynamic> values = dataSnapshot.value;
[ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception: type 'List<Object?>' is not a subtype of type 'Map<dynamic, dynamic>'
I think this means that the keys are not being returned in the dataSnapshot.value, how can i use this now to get the (key, value) as before?
If I print the snapshot.value I get like this, so looks like the keys are no longer present in the snapshot.value as before, i don't know where are the keys now:
[{name: 2016-2017}, {name: 2017-2018}, {name: 2018-2019}]
Thanks in advance.

You probably update Flutterfire cloud_firestore too, becouse as you can see from the error, in the newest versione, the data returned from the snapshot is an Object or a List<Object> and not a map.
You can solve like this:
snapshot.value as Map<String, dynamic>
EDIT:
I saw you where referring to firebase realtime database.
So the problem is not caused by flutter_fire.
The error tells you that you are trying to put a List into a Map<dynamic,dynamic>.
One solution could be:
Map<dynamic, dynamic> values = dataSnapshot.value as Map<dynamic, dynamic>;

Try this
DataSnapshot dataSnapshot = await References.getReferenceYears().once();
(dataSnapshot as Map<dynamic, dynamic>).forEach((key, value) {...}

It sounds like you have sequential, numeric keys under References.getReferenceYears(), which the SDK converts to an array. I don't think this changed in a recent SDK, as Firebase always tries to coerce such keys into an array.
If you don't want the values to be stored into an array, the easiest way to prevent that is to prefix the keys with a short non-numeric value, like:
"key_0": "value 0",
"key_1": "value 1",
"key_2": "value 2",
...

Use version 8.x.x for now as output type is dynamic instead of Object? in version 9.x.x
Map<dynamic, dynamic> mapData;
snapshot.values.forEach((key, value) {
mapData[key][value];
})

Related

Convert data Type into different data Type in dart

I am working in backend using dart. when i get a request i check the data type of all the values in key value pair. some times i receive data type.
_InternalLinkedHashMap<String, dynamic>
I need a where i can type cast a data-type
Type xyz = sumFunction(_InternalLinkedHashMap<String, dynamic>);
print(xyz);
output :
Map<String,dynamic>
Beacause when i am comparing
print(_InternalLinkedHashMap<String, dynamic> == Map<String,dynamic>);
output:
false
If anyone have any kind of solution please provide.

Unhandled Exception: type 'List<dynamic>' is not a subtype of type 'List<Map<dynamic, dynamic>>' Flutter Firebase

I am trying to get a list from firebase document and then set to another empty List of maps then add a map to this list of maps and pass it back to Firebase.
This is My method:
Future logFinalWorkout(user, finalWorkout) async{
List<Map<dynamic, dynamic>> result = List<Map<dynamic, dynamic>>();
await users.document(user.uid).get().then((doc){
result = doc.data['finalWorkouts'];
result.addAll(finalWorkout);
});
return await users.document(user.uid).updateData({
'finalWorkouts' : result,
});
}
finalWorkout parameter is of type Map<dynamic,dynamic>
final workouts represents the list coming from Firebase. I keep getting the above error
Remove List<Map<dynamic, dynamic>> result and add List< dynamic> result instead of that. Give it a try.

Get index of list of map from map

How do you get index of list of maps from a map. I tried to use indexOf which works but by the time the position of values are mixed up then it returns -1.
UPDATE: It actually doesn't work even in right order
List<Map<String, dynamic>> list = [{'id':1, 'name':'a'}, {'id':2, 'name':'b'}];
Map<String, dynamic> item = {'name':'a','id':1}; //<- position name and id are in different places
print(list.indexOf(item)); // so it return -1
The best way would be to get index of list where item contains same id ... if you know what I mean... How to do it?
You can use indexWhere instead indexOf.
Map<String, dynamic> item = {'name':'a','id':1};
print(list.indexWhere((i) => i['id'] == item['id'])); // prints 0
Map<String, dynamic> item = {'name':'b','id':2};
print(list.indexWhere((i) => i['id'] == item['id'])); // prints 1

Flutter: Parsing Firebase Realtime Database snapshot to list

I tried searching other questions but the only similar question to this had answers in JavaScript instead of Dart/Flutter. I'm trying to get a list from my Firebase Realtime Database into my app as a List<BaseModel>
So far from what I've searched in the net, I think the result of the DataSnapshot is a map that I could parse so I tried it out but got this error: List<dynamic>' is not a subtype of type 'Map<dynamic, dynamic>
My Code:
Future<List<BaseModel>> getList(
{DatabaseReference query, Models modelType}) async {
List<BaseModel> list = new List();
DataSnapshot snap = await query.once();
Map<String, dynamic> json = Map.from(snap.value);
json.forEach((key, value) {
list.add(BaseModel(model: modelType, key: key, snapshot: value));
});
return list;
}
The weird thing is, even if I try to parse a non-list model I also get the same error.
My database structure looks like this:
Update:
BaseModel:
abstract class BaseModel {
factory BaseModel({Models model, String key, Map map}) {
switch (model) {
case Models.MyModel:
return MyMod.fromSnapshot(key: key, map: map);
break;
default:
throw ("Not a valid model.");
}
}
}
MyModel:
MyModel.fromSnapshot({String key, Map map}) {
_id = key;
_title = map['title'];
}
My Firebase query is just the database reference with .child("Root")
I found a solution!
My new code:
Future<List<BaseModel>> getList({DatabaseReference query, Models modelType}) async {
List<BaseModel> list = new List();
DataSnapshot snap = await query.once();
List<dynamic> resultList = snap.value;
for(var i = 0; i < resultList.length; i++) {
Map<dynamic, dynamic> map = Map.from(resultList[i]);
list.add(BaseModel(model: modelType, key: i.toString(), snapshot: map));
}
return list;
}
This should work assuming you parse the values from your model's .fromMap(yourMap) constructor method. Something like _title = yourMap['key'];
I had a similar experience where the snapshot.value sometimes returned a List and sometimes returned a Map. I searched for a long time to get an answer with no luck but I came up with a workaround.
I suspected that the problem was being caused by using a record key with a value of zero so I added 100 to each key before I wrote it to the db and then subtracted it when I had read and was processing the records. The problem went away in that I then always got a Map returned.
I have since seen a reason given for this behaviour and it confirmed that the zero key value was the culprit but unfortunately I didn't save the link. I think it was on one of the Firebase blogs.
I think the 0 record returns a List and the ones with positive values return a Map.
Anyway, try the adding 100 trick and see it that helps. if it helps, upvote me....I don't think you need code to add or delete 100. :-)
Found the article, Firebase is deciding if it should render an array or a map based on the snapshot content: https://firebase.googleblog.com/2014/04/best-practices-arrays-in-firebase.html?m=1
UPDATE:
My 'starting at 0' theory was a red herring, sorry.
The key to this behaviour (bits in bold) is in the part of the Firebase blog (link above) that states:
However, to help people that are storing arrays in Firebase, when you
call .val() or use the REST api to read data, if the data looks like
an array, Firebase will render it as an array.
In particular, if all of the keys are integers, and more than half of
the keys between 0 and the maximum key in the object have non-empty
values, then Firebase will render it as an array. This latter part is
important to keep in mind.
// we send this ['a', 'b', 'c', 'd', 'e'] // Firebase stores this {0:
'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e'}
// since the keys are numeric and sequential, // if we query the data,
we get this ['a', 'b', 'c', 'd', 'e']
// however, if we then delete a, b, and d, // they are no longer
mostly sequential, so // we do not get back an array {2: 'c', 4: 'e'}
You can't currently change or prevent this behavior.
I have now tested this by setting up a db node that looks like the below image. I tested what was being returned in snapshot.value by using snapshot.value is List and snapshot.value is Map, both return true or false.
When ONLY nodes 0, 1 and 3 were there, Firebase RTDB was happily returning a List in snapshot.value. When I added nodes 4 and 6, which have inconsistent data, it decided it was time to return a Map. :-)
So, the quick fix is to test the contents of snapshot.value with is List and/or is Map and then process the contents accordingly, otherwise rethink your keys...the fact that they are sequential or close-to-sequential (with gaps) but have the same children structure is the issue.

Handle null value in Dart / Flutter

I have this User class and then a Firestore Document, that may or may not contain a photoURL or some other values. The problem is, that when I try to create an user I get this exception because some of the values are Null.
I've tried to handle it like this:
var photoURL = snapshot.data['photoURL'] ??= '';
but it seems it doesn't work.
Can anyone explain best practices handling Null values in Dart/Flutter respectively?
EDIT: I'm checking if snapshot.exists before and if I create the user omitting the values I know are Null, it creates it properly.
EDIT 2: I've found the problem appears when I try to handle empty List like this:
var favorites = snapshot.data['favorites'] ?? [''];
It seems I was initialized the value the wrong way when I converted it to Json.
I handle the empty Array like this
Map<String, dynamic> toJson() => {
'favorites' : favorites ?? '',
}
when it should be:
Map<String, dynamic> toJson() => {
'favorites' : favorites ?? [''],
}
So it was throwing when I tried to assign an empty Array to String.