Reputation: 394
I'm experimenting with NoSQL (firebase realtime database) for the first time and I'm having some issues structuring and parsing data in a Flutter app. At first I had a simple "Person" model with some attributes and all was fine but then I had to introduce an ID as node and nesting the other attributes in order to perform CRUD operations and now I'm not able to parse my "updated" 'Person' model anymore. I don't know if that's ok but, for simplicity (I know it's not a proper ID), I decided that my ID ('personName' attribute) is the name of the person so the current structure on DB is:
I'm using freezed package and PersonDto looks like this (domains methods omitted):
@freezed
class PersonDto with _$PersonDto {
const PersonDto ._();
const factory PersonDto ({
required String personName,
required int age,
required String genre,
required double height,
required String hobby,
required double weight,
}) = _PersonDto ;
factory PersonDto.fromJson(Map<String, dynamic> json) =>
_$PersonDto FromJson(json);
In the repository there's a method responsible to receive, parse and stream data from firebase. My problem is basically that I'm not able to generate a 'Person' model using the key of the node as an attribute for the 'name'. This is the closest I get :
Stream<Either<PersonFailure, List<Person>>> watchAll() async* {
yield* _firebaseDatabase
.reference()
.child('persons')
.onValue
.map((event) {
return right<PersonFailure, List<Person>>(
(event.snapshot.value as LinkedHashMap).values.map((personMap) {
final json = Map<String, dynamic>.from(personMap as LinkedHashMap);
//
//this snippet works. I'm able to generate a proper 'Person' model but like this
//there's no way to retrieve the key from event.snapshot.value
//
json.addAll({
'personName': 'NAME OF THE PERSON',
});
return PersonDto.fromJson(json).toDomain();
}).toList());
}).onErrorReturnWith((e) {
print('WATCH ERROR ${e.toString()}');
return left(const PersonFailure.unexpected());
});
}
Should be something like this, which likewise doesn't work, unfortunately:
yield* _firebaseDatabase
.reference()
.child('persons')
.onValue
.map((event) {
(event.snapshot as LinkedHashMap).map((key, value) {
final personName = key;
final json = value as Map<String,dynamic>;
json.addAll({'personName':personName});
//
//error: The return type 'Person' isn't a 'MapEntry<_, _>', as required by the closure's context.
//
return PersonDto.fromJson(json).toDomain();
});
});
Upvotes: 3
Views: 1558
Reputation: 89
Just need to add a null check. like this;
.....
.onValue
.map((event) {
List<ListsModel> _offers = <ListsModel>[];
if(event.snapshot.value != null) {
final _resultList =
Map<String, dynamic>.from(e.snapshot.value as LinkedHashMap);
for (var key in _resultList.keys) {
Map<dynamic, dynamic> map = Map.from(_resultList[key]);
ListsModel listsModel = ListsModel.fromMap(map);
_offers.add(listModel);
}
}
.....
Upvotes: 3
Reputation: 394
Ok, I managed to make it work. It's kinda ugly, but I'm not able to found a prettier way to do it so here we go.
First, the 'PersonDto' is mapped 1:1 with firebase and freezed will take care of the fromJson/toJson methods. In order to convert the DTO to a domain model just the "personName" attribute is missing, so I will provide it manually to the "toDomain" method once extracted from the map key:
@freezed
class PersonDto with _$PersonDto {
const PersonDto ._();
const factory PersonDto ({
required int age,
required String genre,
required double height,
required String hobby,
required double weight,
}) = _PersonDto ;
Person toDomain(String personName){
return Person(
personName:personName,
age:age,
genre:genre,
height:height,
hobby:hooby,
weight:weight,
)
}
}
Now comes the nasty part. In the repository method I had to perform a lot of cast in order to generate a List of Person objects. The crucial part has been cast the LinkedHashMap received from firebase to a Map<String,dynamic> and then generate a List<MapEntry<String, dynamic>> with the entries. By this way I was able to use the .map method on the list, iterate, extrapolate the personName from the key, generate a PersonDto from the value and finally return a Person object with the PersonDto.toDomain method :
Stream<Either<PersonFailure, List<Person>>> watchAll() async* {
yield* _firebaseDatabase
.reference()
.child('persons')
.onValue
.map((event) {
final firebaseMap = Map<String, dynamic>.from(event.snapshot.value as LinkedHashMap);
final firebaseMapList = List<MapEntry<String, dynamic>>.from(firebaseMap.entries);
final personsList = firebaseMapList.map((personMap) {
final personName = personMap.key;
final json = personMap.value as LinkedHashMap<dynamic, dynamic>;
final personDto =
PersonDto.fromJson(Map<String, dynamic>.from(json));
return personDto.toDomain(personName);
}).toList();
return right<PersonFailure, List<Person>>(personsList );
}).onErrorReturnWith((e) =>
left(const PersonFailure.unexpected()),
);
}
I can now "easily" separate the persons records on firebase by hobby for example(or by genre or whatever), query with .reference().child('persons').child('hobby'), remove the hobby parameter from the PersonDto and then use the snippet above and manually provide the hobby parameter to the toDomain method.
If anyone can provide a cleaner version I'll be glad to take a look at it!
Upvotes: 0