Firestore types
To begin, starting with 2.0.0, all Firestore references & queries are now typed. This means that CollectionReference<T>, DocumentReference<T>, and Query<T> now all have a generic type parameter T.
Default
By default (e.g. when calling FirebaseFirestore.instance.collection()), this generic is Map<String, dynamic>. This means that (without calling withConverter), the data you pass and the data you receive is always of type Map<String, dynamic>.
withConverter
Now, you can make use of withConverter in various places in order to change this type:
final modelRef = FirebaseFirestore.instance
.collection('models')
.doc('123')
.withConverter<Model>(
fromFirestore: (snapshot, _) => Model.fromJson(snapshot.data()!),
toFirestore: (model, _) => model.toJson(),
);
final modelsRef =
FirebaseFirestore.instance.collection('models').withConverter<Model>(
fromFirestore: (snapshot, _) => Model.fromJson(snapshot.data()!),
toFirestore: (model, _) => model.toJson(),
);
final personsRef = FirebaseFirestore.instance
.collection('persons')
.where('age', isGreaterThan: 0)
.withConverter<Person>(
fromFirestore: (snapshot, _) => Person.fromJson(snapshot.data()!),
toFirestore: (model, _) => model.toJson(),
);
What happens when calling withConverter is that the generic type T is set to your custom Model (e.g. Person in the last example). This means that every subsequent call on the document ref, collection ref, or on the query will all work with that type instead of Map<String, dynamic>.
Usage
The usage of the method is straight-forward:
- You pass a
FromFirestore function that converts a snapshot (with options) to your custom model.
- You pass a
ToFirestore function that converts your model T (with options) back to a Map<String, dynamic>, i.e. Firestore-specific JSON data.
Example
Here is an example of using withConverter with a custom Movie model class (note that I am using freezed because it is more readable):
Future<void> main() async {
// Create an instance of our model.
const movie = Movie(length: 123, rating: 9.7);
// Create an instance of a collection withConverter.
final collection =
FirebaseFirestore.instance.collection('movies').withConverter(
fromFirestore: (snapshot, _) => Movie.fromJson(snapshot.data()!),
toFirestore: (movie, _) => movie.toJson(),
);
// Directly add our model to the collection.
collection.add(movie);
// Also works for subsequent calls.
collection.doc('123').set(movie);
// And also works for reads.
final Movie movie2 = (await collection.doc('2').get()).data()!;
}
@freezed
class Movie with _$Movie {
const factory Movie({
required int length,
required double rating,
}) = _Movie;
factory Movie.fromJson(Map<String, dynamic> json) => _$MovieFromJson(json);
}