From b2090fe1bf0d61c45799a96b8d8c1d29da79dd50 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Wed, 16 Sep 2026 09:14:44 +0000 Subject: [PATCH] feat(firestore): add FieldValue.minimum and FieldValue.maximum Expose native min/max write sentinels on iOS, Android, and web. Windows is unsupported until the C++ SDK adds the API. --- .../FlutterFirebaseFirestoreMessageCodec.java | 16 +++++ .../integration_test/field_value_e2e.dart | 70 +++++++++++++++++++ .../FLTFirebaseFirestoreReader.m | 8 +++ .../Private/FLTFirebaseFirestoreUtils.h | 4 ++ .../cloud_firestore/lib/src/field_value.dart | 14 ++++ .../test/field_value_test.dart | 5 ++ .../windows/firestore_codec.cpp | 12 ++++ .../cloud_firestore/windows/firestore_codec.h | 4 ++ .../method_channel_field_value.dart | 12 ++++ .../method_channel_field_value_factory.dart | 32 +++++++-- .../utils/firestore_message_codec.dart | 26 +++++-- ...latform_interface_field_value_factory.dart | 18 +++++ .../lib/src/field_value_factory_web.dart | 8 +++ .../lib/src/interop/firestore.dart | 28 ++++++++ .../lib/src/interop/firestore_interop.dart | 8 +++ .../test/field_value_factory_web_test.dart | 16 +++++ 16 files changed, 273 insertions(+), 8 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestoreMessageCodec.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestoreMessageCodec.java index a446a11f364e..d993657600a8 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestoreMessageCodec.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestoreMessageCodec.java @@ -57,6 +57,10 @@ class FlutterFirebaseFirestoreMessageCodec extends StandardMessageCodec { private static final byte DATA_TYPE_FIRESTORE_QUERY = (byte) 197; private static final byte DATA_TYPE_FIRESTORE_SETTINGS = (byte) 198; private static final byte DATA_TYPE_VECTOR_VALUE = (byte) 199; + private static final byte DATA_TYPE_MINIMUM_DOUBLE = (byte) 200; + private static final byte DATA_TYPE_MINIMUM_INTEGER = (byte) 201; + private static final byte DATA_TYPE_MAXIMUM_DOUBLE = (byte) 202; + private static final byte DATA_TYPE_MAXIMUM_INTEGER = (byte) 203; @Override protected void writeValue(ByteArrayOutputStream stream, Object value) { @@ -273,6 +277,18 @@ protected Object readValueOfType(byte type, ByteBuffer buffer) { case DATA_TYPE_INCREMENT_DOUBLE: final Number doubleIncrementValue = (Number) readValue(buffer); return FieldValue.increment(doubleIncrementValue.doubleValue()); + case DATA_TYPE_MINIMUM_INTEGER: + final Number integerMinimumValue = (Number) readValue(buffer); + return FieldValue.minimum(integerMinimumValue.intValue()); + case DATA_TYPE_MINIMUM_DOUBLE: + final Number doubleMinimumValue = (Number) readValue(buffer); + return FieldValue.minimum(doubleMinimumValue.doubleValue()); + case DATA_TYPE_MAXIMUM_INTEGER: + final Number integerMaximumValue = (Number) readValue(buffer); + return FieldValue.maximum(integerMaximumValue.intValue()); + case DATA_TYPE_MAXIMUM_DOUBLE: + final Number doubleMaximumValue = (Number) readValue(buffer); + return FieldValue.maximum(doubleMaximumValue.doubleValue()); case DATA_TYPE_DOCUMENT_ID: return FieldPath.documentId(); case DATA_TYPE_FIRESTORE_INSTANCE: diff --git a/packages/cloud_firestore/cloud_firestore/example/integration_test/field_value_e2e.dart b/packages/cloud_firestore/cloud_firestore/example/integration_test/field_value_e2e.dart index 71de05f5c456..c9f42905a683 100644 --- a/packages/cloud_firestore/cloud_firestore/example/integration_test/field_value_e2e.dart +++ b/packages/cloud_firestore/cloud_firestore/example/integration_test/field_value_e2e.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; void runFieldValueTests() { @@ -62,6 +63,75 @@ void runFieldValueTests() { }); }); + group('FieldValue.minimum() / FieldValue.maximum()', () { + test( + 'minimum sets the smaller of the current value and the operand', + () async { + DocumentReference> doc = + await initializeTest('field-value-minimum-existing'); + await doc.set({'foo': 80}); + await doc.update({'foo': FieldValue.minimum(50)}); + DocumentSnapshot> snapshot = await doc.get(); + expect(snapshot.data()!['foo'], equals(50)); + + await doc.update({'foo': FieldValue.minimum(100)}); + snapshot = await doc.get(); + expect(snapshot.data()!['foo'], equals(50)); + }, + skip: defaultTargetPlatform == TargetPlatform.windows + ? 'The Firebase C++ SDK does not expose FieldValue.minimum/maximum.' + : null, + ); + + test( + 'maximum sets the larger of the current value and the operand', + () async { + DocumentReference> doc = + await initializeTest('field-value-maximum-existing'); + await doc.set({'foo': 80}); + await doc.update({'foo': FieldValue.maximum(100)}); + DocumentSnapshot> snapshot = await doc.get(); + expect(snapshot.data()!['foo'], equals(100)); + + await doc.update({'foo': FieldValue.maximum(50)}); + snapshot = await doc.get(); + expect(snapshot.data()!['foo'], equals(100)); + }, + skip: defaultTargetPlatform == TargetPlatform.windows + ? 'The Firebase C++ SDK does not expose FieldValue.minimum/maximum.' + : null, + ); + + test( + 'minimum sets the operand when the field does not exist', + () async { + DocumentReference> doc = + await initializeTest('field-value-minimum-not-exists'); + await doc.set({'foo': FieldValue.minimum(50)}); + DocumentSnapshot> snapshot = await doc.get(); + expect(snapshot.data()!['foo'], equals(50)); + }, + skip: defaultTargetPlatform == TargetPlatform.windows + ? 'The Firebase C++ SDK does not expose FieldValue.minimum/maximum.' + : null, + ); + + test( + 'maximum replaces a non-numeric field with the operand', + () async { + DocumentReference> doc = + await initializeTest('field-value-maximum-non-numeric'); + await doc.set({'foo': 'bar'}); + await doc.update({'foo': FieldValue.maximum(7)}); + DocumentSnapshot> snapshot = await doc.get(); + expect(snapshot.data()!['foo'], equals(7)); + }, + skip: defaultTargetPlatform == TargetPlatform.windows + ? 'The Firebase C++ SDK does not expose FieldValue.minimum/maximum.' + : null, + ); + }); + group('FieldValue.serverTimestamp()', () { test('sets a new server time value', () async { DocumentReference> doc = diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTFirebaseFirestoreReader.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTFirebaseFirestoreReader.m index edfa4cba17ae..969123af8f37 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTFirebaseFirestoreReader.m +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTFirebaseFirestoreReader.m @@ -69,6 +69,14 @@ - (id)readValueOfType:(UInt8)type { [FIRFieldValue fieldValueForDoubleIncrement:((NSNumber *)[self readValue]).doubleValue]; case FirestoreDataTypeIncrementInteger: return [FIRFieldValue fieldValueForIntegerIncrement:((NSNumber *)[self readValue]).intValue]; + case FirestoreDataTypeMinimumDouble: + return [FIRFieldValue fieldValueForDoubleMinimum:((NSNumber *)[self readValue]).doubleValue]; + case FirestoreDataTypeMinimumInteger: + return [FIRFieldValue fieldValueForIntegerMinimum:((NSNumber *)[self readValue]).intValue]; + case FirestoreDataTypeMaximumDouble: + return [FIRFieldValue fieldValueForDoubleMaximum:((NSNumber *)[self readValue]).doubleValue]; + case FirestoreDataTypeMaximumInteger: + return [FIRFieldValue fieldValueForIntegerMaximum:((NSNumber *)[self readValue]).intValue]; case FirestoreDataTypeDocumentId: return [FIRFieldPath documentID]; case FirestoreDataTypeFirestoreInstance: diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Private/FLTFirebaseFirestoreUtils.h b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Private/FLTFirebaseFirestoreUtils.h index e04b30b5483e..e85bc649f254 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Private/FLTFirebaseFirestoreUtils.h +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Private/FLTFirebaseFirestoreUtils.h @@ -43,6 +43,10 @@ typedef NS_ENUM(UInt8, FirestoreDataType) { FirestoreDataTypeFirestoreQuery = 197, FirestoreDataTypeFirestoreSettings = 198, FirestoreDataTypeVectorValue = 199, + FirestoreDataTypeMinimumDouble = 200, + FirestoreDataTypeMinimumInteger = 201, + FirestoreDataTypeMaximumDouble = 202, + FirestoreDataTypeMaximumInteger = 203, }; @interface FLTFirebaseFirestoreReaderWriter : FlutterStandardReaderWriter diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/field_value.dart b/packages/cloud_firestore/cloud_firestore/lib/src/field_value.dart index 7fc511d1ee3d..da07151cb495 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/field_value.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/field_value.dart @@ -50,6 +50,20 @@ class FieldValue extends FieldValuePlatform { static FieldValue increment(num value) => FieldValue._(_factory.increment(value)); + /// Returns a special value for use with set() or update() that tells the + /// server to set the field to the minimum of its current value and [value]. + /// + /// If the current field value is not an integer or double, or if the field + /// does not yet exist, the transformation sets the field to [value]. + static FieldValue minimum(num value) => FieldValue._(_factory.minimum(value)); + + /// Returns a special value for use with set() or update() that tells the + /// server to set the field to the maximum of its current value and [value]. + /// + /// If the current field value is not an integer or double, or if the field + /// does not yet exist, the transformation sets the field to [value]. + static FieldValue maximum(num value) => FieldValue._(_factory.maximum(value)); + dynamic _delegate; @override diff --git a/packages/cloud_firestore/cloud_firestore/test/field_value_test.dart b/packages/cloud_firestore/cloud_firestore/test/field_value_test.dart index 486223c2703c..83601e1b7a85 100644 --- a/packages/cloud_firestore/cloud_firestore/test/field_value_test.dart +++ b/packages/cloud_firestore/cloud_firestore/test/field_value_test.dart @@ -14,6 +14,11 @@ void main() { isTrue, ); expect(FieldValue.delete() == FieldValue.serverTimestamp(), isFalse); + expect(FieldValue.minimum(1) == FieldValue.minimum(1), isTrue); + expect(FieldValue.maximum(1) == FieldValue.maximum(1), isTrue); + expect(FieldValue.minimum(1) == FieldValue.maximum(1), isFalse); + expect(FieldValue.minimum(1) == FieldValue.increment(1), isFalse); + expect(FieldValue.minimum(1) == FieldValue.minimum(2), isFalse); }); }); } diff --git a/packages/cloud_firestore/cloud_firestore/windows/firestore_codec.cpp b/packages/cloud_firestore/cloud_firestore/windows/firestore_codec.cpp index 820de87ed734..c14b927687b6 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/firestore_codec.cpp +++ b/packages/cloud_firestore/cloud_firestore/windows/firestore_codec.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -207,6 +208,17 @@ cloud_firestore_windows::FirestoreCodec::ReadValueOfType( return CustomEncodableValue(FieldValue::Increment(incrementValue)); } + case DATA_TYPE_MINIMUM_DOUBLE: + case DATA_TYPE_MINIMUM_INTEGER: + case DATA_TYPE_MAXIMUM_DOUBLE: + case DATA_TYPE_MAXIMUM_INTEGER: { + // Consume the encoded operand so the codec stays in sync. + FirestoreCodec::ReadValue(stream); + throw std::runtime_error( + "FieldValue.minimum() and FieldValue.maximum() are not supported on " + "Windows until the Firebase C++ SDK exposes them."); + } + case DATA_TYPE_DOCUMENT_ID: { return CustomEncodableValue(FieldPath::DocumentId()); } diff --git a/packages/cloud_firestore/cloud_firestore/windows/firestore_codec.h b/packages/cloud_firestore/cloud_firestore/windows/firestore_codec.h index fbbf3b6639f2..b58cb3dc1d48 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/firestore_codec.h +++ b/packages/cloud_firestore/cloud_firestore/windows/firestore_codec.h @@ -41,6 +41,10 @@ class FirestoreCodec : public flutter::StandardCodecSerializer { static const uint8_t DATA_TYPE_FIRESTORE_INSTANCE = 196; static const uint8_t DATA_TYPE_FIRESTORE_QUERY = 197; static const uint8_t DATA_TYPE_FIRESTORE_SETTINGS = 198; + static const uint8_t DATA_TYPE_MINIMUM_DOUBLE = 200; + static const uint8_t DATA_TYPE_MINIMUM_INTEGER = 201; + static const uint8_t DATA_TYPE_MAXIMUM_DOUBLE = 202; + static const uint8_t DATA_TYPE_MAXIMUM_INTEGER = 203; FirestoreCodec(); inline static FirestoreCodec& GetInstance() { diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_field_value.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_field_value.dart index a9e413073a72..5b7ed73ad47a 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_field_value.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_field_value.dart @@ -28,6 +28,18 @@ enum FieldValueType { /// increment or decrement a numeric field value using an integer value incrementInteger, + + /// set a numeric field to the minimum of its current value and a double + minimumDouble, + + /// set a numeric field to the minimum of its current value and an integer + minimumInteger, + + /// set a numeric field to the maximum of its current value and a double + maximumDouble, + + /// set a numeric field to the maximum of its current value and an integer + maximumInteger, } /// Default, `MethodChannel`-based delegate for a [FieldValuePlatform]. diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_field_value_factory.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_field_value_factory.dart index 7145fedd0545..4bd481e04b4c 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_field_value_factory.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_field_value_factory.dart @@ -25,19 +25,43 @@ class MethodChannelFieldValueFactory extends FieldValueFactoryPlatform { MethodChannelFieldValue(FieldValueType.delete, null); @override - MethodChannelFieldValue increment(num value) { + MethodChannelFieldValue increment(num value) => _fromNum( + value, + FieldValueType.incrementDouble, + FieldValueType.incrementInteger, + ); + + @override + MethodChannelFieldValue minimum(num value) => _fromNum( + value, + FieldValueType.minimumDouble, + FieldValueType.minimumInteger, + ); + + @override + MethodChannelFieldValue maximum(num value) => _fromNum( + value, + FieldValueType.maximumDouble, + FieldValueType.maximumInteger, + ); + + MethodChannelFieldValue _fromNum( + num value, + FieldValueType doubleType, + FieldValueType integerType, + ) { // It is a compile-time error for any type other than `int` or `double` to // attempt to extend or implement `num`. assert(value is int || value is double); if (value is double) { - return MethodChannelFieldValue(FieldValueType.incrementDouble, value); + return MethodChannelFieldValue(doubleType, value); // ignore: avoid_double_and_int_checks } else if (value is int) { - return MethodChannelFieldValue(FieldValueType.incrementInteger, value); + return MethodChannelFieldValue(integerType, value); } throw StateError( - 'MethodChannelFieldValue().increment() expects a "num" value'); + 'MethodChannelFieldValue() numeric factories expect a "num" value'); } @override diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/utils/firestore_message_codec.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/utils/firestore_message_codec.dart index 5644e75333df..425bc12cce39 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/utils/firestore_message_codec.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/utils/firestore_message_codec.dart @@ -44,6 +44,10 @@ class FirestoreMessageCodec extends StandardMessageCodec { static const int _kFirestoreQuery = 197; static const int _kFirestoreSettings = 198; static const int _kVectorValue = 199; + static const int _kMinimumDouble = 200; + static const int _kMinimumInteger = 201; + static const int _kMaximumDouble = 202; + static const int _kMaximumInteger = 203; static const Map _kFieldValueCodes = { @@ -53,6 +57,17 @@ class FirestoreMessageCodec extends StandardMessageCodec { FieldValueType.serverTimestamp: _kServerTimestamp, FieldValueType.incrementDouble: _kIncrementDouble, FieldValueType.incrementInteger: _kIncrementInteger, + FieldValueType.minimumDouble: _kMinimumDouble, + FieldValueType.minimumInteger: _kMinimumInteger, + FieldValueType.maximumDouble: _kMaximumDouble, + FieldValueType.maximumInteger: _kMaximumInteger, + }; + + static const Map _kIntegerToDoubleCodes = + { + FieldValueType.incrementInteger: _kIncrementDouble, + FieldValueType.minimumInteger: _kMinimumDouble, + FieldValueType.maximumInteger: _kMaximumDouble, }; static const Map _kFieldPathCodes = { @@ -84,14 +99,13 @@ class FirestoreMessageCodec extends StandardMessageCodec { buffer.putUint8List(value.bytes); } else if (value is FieldValuePlatform) { MethodChannelFieldValue delegate = FieldValuePlatform.getDelegate(value); - final int code = _kFieldValueCodes[delegate.type]!; // We turn int superior to 2^32 into double here to avoid precision loss. - if (delegate.type == FieldValueType.incrementInteger && + if (_kIntegerToDoubleCodes.containsKey(delegate.type) && (delegate.value > 2147483647 || delegate.value < -2147483648)) { - buffer.putUint8(_kIncrementDouble); + buffer.putUint8(_kIntegerToDoubleCodes[delegate.type]!); writeValue(buffer, (delegate.value as int).toDouble()); } else { - buffer.putUint8(code); + buffer.putUint8(_kFieldValueCodes[delegate.type]!); if (delegate.value != null) writeValue(buffer, delegate.value); } } else if (value is FieldPathType) { @@ -181,6 +195,10 @@ class FirestoreMessageCodec extends StandardMessageCodec { case _kServerTimestamp: case _kIncrementDouble: case _kIncrementInteger: + case _kMinimumDouble: + case _kMinimumInteger: + case _kMaximumDouble: + case _kMaximumInteger: default: return super.readValueOfType(type, buffer); } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_field_value_factory.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_field_value_factory.dart index eb519592d749..5e977231bc40 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_field_value_factory.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_field_value_factory.dart @@ -76,4 +76,22 @@ abstract class FieldValueFactoryPlatform extends PlatformInterface { dynamic increment(num value) { throw UnimplementedError('increment() is not implemented'); } + + /// Returns a special value for use with set() or update() that tells the + /// server to set the field to the minimum of its current value and [value]. + /// + /// If the current field value is not an integer or double, or if the field + /// does not yet exist, the transformation sets the field to [value]. + dynamic minimum(num value) { + throw UnimplementedError('minimum() is not implemented'); + } + + /// Returns a special value for use with set() or update() that tells the + /// server to set the field to the maximum of its current value and [value]. + /// + /// If the current field value is not an integer or double, or if the field + /// does not yet exist, the transformation sets the field to [value]. + dynamic maximum(num value) { + throw UnimplementedError('maximum() is not implemented'); + } } diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/field_value_factory_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/field_value_factory_web.dart index 4bc3104d5ea5..c2b1ac6750ec 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/field_value_factory_web.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/field_value_factory_web.dart @@ -30,6 +30,14 @@ class FieldValueFactoryWeb extends FieldValueFactoryPlatform { FieldValueWeb increment(num value) => FieldValueWeb(firestore_interop.FieldValue.increment(value)); + @override + FieldValueWeb minimum(num value) => + FieldValueWeb(firestore_interop.FieldValue.minimum(value)); + + @override + FieldValueWeb maximum(num value) => + FieldValueWeb(firestore_interop.FieldValue.maximum(value)); + @override FieldValueWeb serverTimestamp() => FieldValueWeb(firestore_interop.FieldValue.serverTimestamp()); diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore.dart index 9ed2cf332707..db1bbd531afb 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore.dart @@ -988,6 +988,30 @@ class _FieldValueIncrement implements FieldValue { String toString() => 'FieldValue.increment($n)'; } +class _FieldValueMinimum implements FieldValue { + final num n; + + _FieldValueMinimum(this.n); + + @override + firestore_interop.FieldValue _jsify() => firestore_interop.minimum(n.toJS); + + @override + String toString() => 'FieldValue.minimum($n)'; +} + +class _FieldValueMaximum implements FieldValue { + final num n; + + _FieldValueMaximum(this.n); + + @override + firestore_interop.FieldValue _jsify() => firestore_interop.maximum(n.toJS); + + @override + String toString() => 'FieldValue.maximum($n)'; +} + JSAny? jsifyFieldValue(FieldValue fieldValue) => fieldValue._jsify() as JSAny?; /// Sentinel values that can be used when writing document fields with set() @@ -1015,6 +1039,10 @@ abstract class FieldValue { // all integer operations are capped between -2^63 and 2^63-1. static FieldValue increment(num n) => _FieldValueIncrement(n); + static FieldValue minimum(num n) => _FieldValueMinimum(n); + + static FieldValue maximum(num n) => _FieldValueMaximum(n); + FieldValue._(); static final FieldValue _serverTimestamp = _FieldValueServerTimestamp(); diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore_interop.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore_interop.dart index 53a2284bf6d0..ae58c2e31aa1 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore_interop.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore_interop.dart @@ -161,6 +161,14 @@ external JSPromise getDocsFromServer( @staticInterop external FieldValue increment(JSNumber n); +@JS() +@staticInterop +external FieldValue minimum(JSNumber n); + +@JS() +@staticInterop +external FieldValue maximum(JSNumber n); + @JS() @staticInterop external QueryConstraintJsImpl limit(JSNumber limit); diff --git a/packages/cloud_firestore/cloud_firestore_web/test/field_value_factory_web_test.dart b/packages/cloud_firestore/cloud_firestore_web/test/field_value_factory_web_test.dart index 06970075ec9b..f3075adc6c4f 100644 --- a/packages/cloud_firestore/cloud_firestore_web/test/field_value_factory_web_test.dart +++ b/packages/cloud_firestore/cloud_firestore_web/test/field_value_factory_web_test.dart @@ -36,6 +36,22 @@ void main() { expect(actualDouble.data, isInstanceOf()); }); + test('minimum', () { + final FieldValueWeb actualInt = factory.minimum(1); + expect(actualInt.data, isInstanceOf()); + + final FieldValueWeb actualDouble = factory.minimum(1.25); + expect(actualDouble.data, isInstanceOf()); + }); + + test('maximum', () { + final FieldValueWeb actualInt = factory.maximum(1); + expect(actualInt.data, isInstanceOf()); + + final FieldValueWeb actualDouble = factory.maximum(1.25); + expect(actualDouble.data, isInstanceOf()); + }); + test('serverTimestamp', () { final FieldValueWeb actual = factory.serverTimestamp(); expect(actual.data, isInstanceOf());