I would like to combine built_value
with cloud_firestore
in my Flutter project. I believe it makes sense and that is how it should do it in a clean world? (see https://firebase.flutter.dev/docs/firestore/usage/#typing-collectionreference-and-documentreference).
This is where I am so far.
The Place
model:
// ../models/place.dart
import 'dart:convert';
import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import 'package:sitback/models/serializers.dart';
part 'place.g.dart';
abstract class Place implements Built<Place, PlaceBuilder> {
// Fields
String? get id;
String get name;
String get address;
BuiltList<String> get methods;
Place._();
factory Place([void Function(PlaceBuilder) updates]) = _$Place;
String toJson() {
return json
.encode(standardSerializers.serializeWith(Place.serializer, this));
}
// https://github.com/google/built_value.dart/issues/964#issuecomment-850419921
static Place? fromJson(String jsonString) {
return standardSerializers.deserializeWith(
Place.serializer, json.decode(jsonString));
}
Map<String, Object?> toFirestore() {
return serializers.serializeWith(Place.serializer, this);
}
// TODO: check if something can be improved
// https://github.com/google/built_value.dart/issues/964#issuecomment-850419921
static Place? fromFirestore(Map<String, dynamic> json) {
return serializers.deserializeWith(Place.serializer, json);
}
static Serializer<Place> get serializer => _$placeSerializer;
}
In the above code, I have two issues:
A value of type 'Object?' can't be returned from the method 'toFirestore' because it has a return type of 'Map<String, Object?>'
However, this is a solution that is proposed at multiple locartions (SO questions, github issues), so I don't understand why I get that error.
- In the same reference tutorials/videos, they use
Place
instead ofPlace?
, as a return value forfromJson
but then I have the following error:
A value of type 'Place?' can't be returned from the method 'fromJson' because it has a return type of 'Place'
Could it be related to the null-safety recent changes?
Below are the serializers
(following the doc/tutorials):
// ../models/serializers.dart
library serializers;
import 'package:built_collection/built_collection.dart';
import 'package:built_value/json_object.dart';
import 'package:built_value/serializer.dart';
import 'package:built_value/standard_json_plugin.dart';
import 'package:sitback/models/place.dart';
part 'serializers.g.dart';
@SerializersFor([
Place,
])
final Serializers serializers = _$serializers;
final Serializers standardSerializers =
(_$serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();
Finally, here is the place where I want to consume a Firestore collection while using the Place
"data class" defined via the built_value
package:
class IndexPage extends StatelessWidget {
IndexPage({
Key? key,
}) : super(key: key);
final placesRef = FirebaseFirestore.instance
.collection('places')
.withConverter<Place>(
// TODO: ! at the end due to Place? vs Place in class definition
// would require a try-catch or something else?
fromFirestore: (snapshot, _) => Place.fromFirestore(snapshot.data()!)!,
toFirestore: (place, _) => place.toFirestore(),
);
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: StreamBuilder<QuerySnapshot<Place>>(
stream: placesRef.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return CustomScrollView(
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final itemData = snapshot.data!.docs[index];
return PlaceCard(itemData: itemData);
},
childCount: snapshot.data!.docs.length,
),
),
],
);
}
},
),
),
);
}
}
It is not working currently because of this line:
toFirestore: (place, _) => place.toFirestore(),
. If I return {}
from toFirestore()
, then it compiles and works
BUT I suspect fromFirestore()
is not even called: if I place assert(false);
inside the function, nothing happen.. I'd except an exception.
You can't use a serialiser for Firestore. You need to manually create a map from your built value object to upload it. Once you want to fetch from Firebase you need to create a built object from a map. Haven't done it recently but it would be something like it: