diff --git a/packages/cloud_firestore/cloud_firestore/example/integration_test/write_batch_e2e.dart b/packages/cloud_firestore/cloud_firestore/example/integration_test/write_batch_e2e.dart index 7eb49c04016b..5853c56da4bb 100644 --- a/packages/cloud_firestore/cloud_firestore/example/integration_test/write_batch_e2e.dart +++ b/packages/cloud_firestore/cloud_firestore/example/integration_test/write_batch_e2e.dart @@ -70,6 +70,94 @@ void runWriteBatchTests() { expect(snapshot.exists, false); }); + test('updates with typed data through withConverter', () async { + CollectionReference> collection = + await initializeTest('with-converter-batch-update'); + WriteBatch batch = firestore.batch(); + + DocumentReference doc = collection.doc('doc1').withConverter( + fromFirestore: (snapshot, options) { + return snapshot.data()!['value'] as int; + }, + toFirestore: (value, options) => {'value': value}, + ); + + await doc.set(42); + + batch.update(doc, 21); + + await batch.commit(); + + DocumentSnapshot snapshot = await doc.get(); + expect(snapshot.exists, isTrue); + expect(snapshot.data(), 21); + }); + + test('updates complex typed data through withConverter', () async { + CollectionReference> collection = + await initializeTest('with-converter-complex-batch-update'); + DocumentReference> rawDoc = collection.doc('doc1'); + DocumentReference<_WriteBatchProfile> doc = rawDoc.withConverter( + fromFirestore: (snapshot, options) { + return _WriteBatchProfile.fromFirestore(snapshot.data()!); + }, + toFirestore: (value, options) => value.toFirestore(), + ); + + await rawDoc.set({ + 'existing': 'preserved', + 'name': 'before', + }); + + WriteBatch batch = firestore.batch(); + batch.update<_WriteBatchProfile>( + doc, + _WriteBatchProfile( + name: 'Ada', + score: 42, + address: _WriteBatchAddress(city: 'London', postcode: 'NW1'), + tags: ['admin', 'tester'], + preferences: { + 'email': true, + 'theme': 'dark', + }, + nickname: null, + ), + ); + + await batch.commit(); + + DocumentSnapshot> rawSnapshot = await rawDoc.get(); + expect(rawSnapshot.data(), { + 'existing': 'preserved', + 'name': 'Ada', + 'score': 42, + 'address': { + 'city': 'London', + 'postcode': 'NW1', + }, + 'tags': ['admin', 'tester'], + 'preferences': { + 'email': true, + 'theme': 'dark', + }, + 'nickname': null, + }); + + DocumentSnapshot<_WriteBatchProfile> snapshot = await doc.get(); + _WriteBatchProfile profile = snapshot.data()!; + expect(profile.name, 'Ada'); + expect(profile.score, 42); + expect(profile.address.city, 'London'); + expect(profile.address.postcode, 'NW1'); + expect(profile.tags, ['admin', 'tester']); + expect(profile.preferences, { + 'email': true, + 'theme': 'dark', + }); + expect(profile.nickname, isNull); + }); + test('should update a document using FieldPath keys', () async { CollectionReference> collection = await initializeTest('write-batch-field-path'); @@ -153,3 +241,69 @@ void runWriteBatchTests() { }); }); } + +class _WriteBatchProfile { + _WriteBatchProfile({ + required this.name, + required this.score, + required this.address, + required this.tags, + required this.preferences, + required this.nickname, + }); + + factory _WriteBatchProfile.fromFirestore(Map data) { + return _WriteBatchProfile( + name: data['name'] as String, + score: data['score'] as int, + address: _WriteBatchAddress.fromFirestore( + data['address'] as Map, + ), + tags: (data['tags'] as List).cast(), + preferences: Map.from(data['preferences'] as Map), + nickname: data['nickname'] as String?, + ); + } + + final String name; + final int score; + final _WriteBatchAddress address; + final List tags; + final Map preferences; + final String? nickname; + + Map toFirestore() { + return { + 'name': name, + 'score': score, + 'address': address.toFirestore(), + 'tags': tags, + 'preferences': preferences, + 'nickname': nickname, + }; + } +} + +class _WriteBatchAddress { + _WriteBatchAddress({ + required this.city, + required this.postcode, + }); + + factory _WriteBatchAddress.fromFirestore(Map data) { + return _WriteBatchAddress( + city: data['city'] as String, + postcode: data['postcode'] as String, + ); + } + + final String city; + final String postcode; + + Map toFirestore() { + return { + 'city': city, + 'postcode': postcode, + }; + } +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/write_batch.dart b/packages/cloud_firestore/cloud_firestore/lib/src/write_batch.dart index b07a4734b4a5..c8a60f3ee5fd 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/write_batch.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/write_batch.dart @@ -68,14 +68,23 @@ class WriteBatch { /// If the document does not yet exist, an exception will be thrown. /// /// Objects key can be a String or a FieldPath. - void update(DocumentReference document, Map data) { + void update(DocumentReference document, T data) { assert( document.firestore == _firestore, 'the document provided is from a different Firestore instance', ); + + Map firestoreData; + if (data is Map) { + firestoreData = data; + } else { + final withConverterDoc = document as _WithConverterDocumentReference; + firestoreData = withConverterDoc._toFirestore(data, null); + } + return _delegate.update( document.path, - _CodecUtility.replaceValueWithDelegatesInMapFieldPath(data)!, + _CodecUtility.replaceValueWithDelegatesInMapFieldPath(firestoreData)!, ); } }