From 90bd9b5ddba24a290bd3d1c2ba9377776777051b Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Fri, 6 Feb 2026 11:19:09 +0000 Subject: [PATCH 01/72] feat(firestore): add support for Pipeline APIs --- .../cloud_firestore/lib/cloud_firestore.dart | 9 + .../lib/pipeline_snapshot.dart | 26 + .../cloud_firestore/lib/src/firestore.dart | 19 + .../cloud_firestore/lib/src/pipeline.dart | 477 ++++++++++++++++++ .../lib/src/pipeline_aggregate.dart | 58 +++ .../lib/src/pipeline_distance.dart | 17 + .../lib/src/pipeline_expression.dart | 394 +++++++++++++++ .../lib/src/pipeline_ordering.dart | 30 ++ .../lib/src/pipeline_sample.dart | 43 ++ .../lib/src/pipeline_source.dart | 66 +++ .../lib/src/pipeline_stage.dart | 387 ++++++++++++++ .../cloud_firestore_platform_interface.dart | 1 + .../platform_interface_firestore.dart | 8 + .../platform_interface_pipeline_snapshot.dart | 40 ++ 14 files changed, 1575 insertions(+) create mode 100644 packages/cloud_firestore/cloud_firestore/lib/pipeline_snapshot.dart create mode 100644 packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart create mode 100644 packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart create mode 100644 packages/cloud_firestore/cloud_firestore/lib/src/pipeline_distance.dart create mode 100644 packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart create mode 100644 packages/cloud_firestore/cloud_firestore/lib/src/pipeline_ordering.dart create mode 100644 packages/cloud_firestore/cloud_firestore/lib/src/pipeline_sample.dart create mode 100644 packages/cloud_firestore/cloud_firestore/lib/src/pipeline_source.dart create mode 100644 packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart create mode 100644 packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline_snapshot.dart diff --git a/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart b/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart index 6efbb19345ce..c84108ac3686 100755 --- a/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart @@ -53,7 +53,16 @@ part 'src/filters.dart'; part 'src/firestore.dart'; part 'src/load_bundle_task.dart'; part 'src/load_bundle_task_snapshot.dart'; +part 'pipeline_snapshot.dart'; part 'src/persistent_cache_index_manager.dart'; +part 'src/pipeline.dart'; +part 'src/pipeline_aggregate.dart'; +part 'src/pipeline_distance.dart'; +part 'src/pipeline_expression.dart'; +part 'src/pipeline_ordering.dart'; +part 'src/pipeline_sample.dart'; +part 'src/pipeline_source.dart'; +part 'src/pipeline_stage.dart'; part 'src/query.dart'; part 'src/query_document_snapshot.dart'; part 'src/query_snapshot.dart'; diff --git a/packages/cloud_firestore/cloud_firestore/lib/pipeline_snapshot.dart b/packages/cloud_firestore/cloud_firestore/lib/pipeline_snapshot.dart new file mode 100644 index 000000000000..f08cb6735987 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/pipeline_snapshot.dart @@ -0,0 +1,26 @@ +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of 'cloud_firestore.dart'; + +/// Result of executing a pipeline +class PipelineResult { + final DocumentReference> document; + final DateTime createTime; + final DateTime updateTime; + + PipelineResult({ + required this.document, + required this.createTime, + required this.updateTime, + }); +} + +/// Snapshot containing pipeline execution results +class PipelineSnapshot { + final List result; + final DateTime executionTime; + + PipelineSnapshot._(this.result, this.executionTime); +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart b/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart index 4f735629aee1..3e583c3f3331 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart @@ -339,6 +339,25 @@ class FirebaseFirestore extends FirebasePluginPlatform { return null; } + /// Returns a [PipelineSource] for creating and executing pipelines. + /// + /// Pipelines allow you to perform complex queries and transformations on + /// Firestore data using a fluent API. + /// + /// Example: + /// ```dart + /// final snapshot = await FirebaseFirestore.instance + /// .pipeline() + /// .collection('users') + /// .where(PipelineFilter(Field('age'), isGreaterThan: 18)) + /// .sort(Field('name').ascending()) + /// .limit(10) + /// .execute(); + /// ``` + PipelineSource pipeline() { + return PipelineSource._(this); + } + /// Configures indexing for local query execution. Any previous index configuration is overridden. /// /// The index entries themselves are created asynchronously. You can continue to use queries that diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart new file mode 100644 index 000000000000..e7849f43afd7 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart @@ -0,0 +1,477 @@ +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of '../cloud_firestore.dart'; + +/// A pipeline for querying and transforming Firestore data +class Pipeline { + final List _stages; + final FirebaseFirestore _firestore; + + Pipeline._(this._firestore, this._stages); + + /// Executes the pipeline and returns a snapshot of the results + Future execute() async { + final platformSnapshot = + await _firestore._delegate.executePipeline(_toSerializableStages()); + return _convertPlatformSnapshot(platformSnapshot); + } + + /// Converts platform snapshot to public snapshot + PipelineSnapshot _convertPlatformSnapshot( + PipelineSnapshotPlatform platformSnapshot) { + final results = platformSnapshot.results.map((platformResult) { + return PipelineResult( + document: _JsonDocumentReference( + _firestore, + platformResult.document, + ), + createTime: platformResult.createTime, + updateTime: platformResult.updateTime, + ); + }).toList(); + + return PipelineSnapshot._(results, platformSnapshot.executionTime); + } + + /// Converts stages to serializable format for platform communication + List> _toSerializableStages() { + return _stages.map((stage) => stage.toMap()).toList(); + } + + /// Public method to get serializable stages (used by union stage) + List> getSerializableStages() { + return _toSerializableStages(); + } + + // Pipeline Actions + + /// Adds fields to documents using expressions + Pipeline addFields( + PipelineExpression expression1, [ + PipelineExpression? expression2, + PipelineExpression? expression3, + PipelineExpression? expression4, + PipelineExpression? expression5, + PipelineExpression? expression6, + PipelineExpression? expression7, + PipelineExpression? expression8, + PipelineExpression? expression9, + PipelineExpression? expression10, + PipelineExpression? expression11, + PipelineExpression? expression12, + PipelineExpression? expression13, + PipelineExpression? expression14, + PipelineExpression? expression15, + PipelineExpression? expression16, + PipelineExpression? expression17, + PipelineExpression? expression18, + PipelineExpression? expression19, + PipelineExpression? expression20, + PipelineExpression? expression21, + PipelineExpression? expression22, + PipelineExpression? expression23, + PipelineExpression? expression24, + PipelineExpression? expression25, + PipelineExpression? expression26, + PipelineExpression? expression27, + PipelineExpression? expression28, + PipelineExpression? expression29, + PipelineExpression? expression30, + ]) { + final expressions = [expression1]; + if (expression2 != null) expressions.add(expression2); + if (expression3 != null) expressions.add(expression3); + if (expression4 != null) expressions.add(expression4); + if (expression5 != null) expressions.add(expression5); + if (expression6 != null) expressions.add(expression6); + if (expression7 != null) expressions.add(expression7); + if (expression8 != null) expressions.add(expression8); + if (expression9 != null) expressions.add(expression9); + if (expression10 != null) expressions.add(expression10); + if (expression11 != null) expressions.add(expression11); + if (expression12 != null) expressions.add(expression12); + if (expression13 != null) expressions.add(expression13); + if (expression14 != null) expressions.add(expression14); + if (expression15 != null) expressions.add(expression15); + if (expression16 != null) expressions.add(expression16); + if (expression17 != null) expressions.add(expression17); + if (expression18 != null) expressions.add(expression18); + if (expression19 != null) expressions.add(expression19); + if (expression20 != null) expressions.add(expression20); + if (expression21 != null) expressions.add(expression21); + if (expression22 != null) expressions.add(expression22); + if (expression23 != null) expressions.add(expression23); + if (expression24 != null) expressions.add(expression24); + if (expression25 != null) expressions.add(expression25); + if (expression26 != null) expressions.add(expression26); + if (expression27 != null) expressions.add(expression27); + if (expression28 != null) expressions.add(expression28); + if (expression29 != null) expressions.add(expression29); + if (expression30 != null) expressions.add(expression30); + + return Pipeline._(_firestore, [ + ..._stages, + _AddFieldsStage(expressions), + ]); + } + + /// Aggregates data using aggregate functions + Pipeline aggregate( + PipelineAggregateFunction aggregateFunction1, [ + PipelineAggregateFunction? aggregateFunction2, + PipelineAggregateFunction? aggregateFunction3, + PipelineAggregateFunction? aggregateFunction4, + PipelineAggregateFunction? aggregateFunction5, + PipelineAggregateFunction? aggregateFunction6, + PipelineAggregateFunction? aggregateFunction7, + PipelineAggregateFunction? aggregateFunction8, + PipelineAggregateFunction? aggregateFunction9, + PipelineAggregateFunction? aggregateFunction10, + PipelineAggregateFunction? aggregateFunction11, + PipelineAggregateFunction? aggregateFunction12, + PipelineAggregateFunction? aggregateFunction13, + PipelineAggregateFunction? aggregateFunction14, + PipelineAggregateFunction? aggregateFunction15, + PipelineAggregateFunction? aggregateFunction16, + PipelineAggregateFunction? aggregateFunction17, + PipelineAggregateFunction? aggregateFunction18, + PipelineAggregateFunction? aggregateFunction19, + PipelineAggregateFunction? aggregateFunction20, + PipelineAggregateFunction? aggregateFunction21, + PipelineAggregateFunction? aggregateFunction22, + PipelineAggregateFunction? aggregateFunction23, + PipelineAggregateFunction? aggregateFunction24, + PipelineAggregateFunction? aggregateFunction25, + PipelineAggregateFunction? aggregateFunction26, + PipelineAggregateFunction? aggregateFunction27, + PipelineAggregateFunction? aggregateFunction28, + PipelineAggregateFunction? aggregateFunction29, + PipelineAggregateFunction? aggregateFunction30, + ]) { + final functions = [aggregateFunction1]; + if (aggregateFunction2 != null) functions.add(aggregateFunction2); + if (aggregateFunction3 != null) functions.add(aggregateFunction3); + if (aggregateFunction4 != null) functions.add(aggregateFunction4); + if (aggregateFunction5 != null) functions.add(aggregateFunction5); + if (aggregateFunction6 != null) functions.add(aggregateFunction6); + if (aggregateFunction7 != null) functions.add(aggregateFunction7); + if (aggregateFunction8 != null) functions.add(aggregateFunction8); + if (aggregateFunction9 != null) functions.add(aggregateFunction9); + if (aggregateFunction10 != null) functions.add(aggregateFunction10); + if (aggregateFunction11 != null) functions.add(aggregateFunction11); + if (aggregateFunction12 != null) functions.add(aggregateFunction12); + if (aggregateFunction13 != null) functions.add(aggregateFunction13); + if (aggregateFunction14 != null) functions.add(aggregateFunction14); + if (aggregateFunction15 != null) functions.add(aggregateFunction15); + if (aggregateFunction16 != null) functions.add(aggregateFunction16); + if (aggregateFunction17 != null) functions.add(aggregateFunction17); + if (aggregateFunction18 != null) functions.add(aggregateFunction18); + if (aggregateFunction19 != null) functions.add(aggregateFunction19); + if (aggregateFunction20 != null) functions.add(aggregateFunction20); + if (aggregateFunction21 != null) functions.add(aggregateFunction21); + if (aggregateFunction22 != null) functions.add(aggregateFunction22); + if (aggregateFunction23 != null) functions.add(aggregateFunction23); + if (aggregateFunction24 != null) functions.add(aggregateFunction24); + if (aggregateFunction25 != null) functions.add(aggregateFunction25); + if (aggregateFunction26 != null) functions.add(aggregateFunction26); + if (aggregateFunction27 != null) functions.add(aggregateFunction27); + if (aggregateFunction28 != null) functions.add(aggregateFunction28); + if (aggregateFunction29 != null) functions.add(aggregateFunction29); + if (aggregateFunction30 != null) functions.add(aggregateFunction30); + + return Pipeline._(_firestore, [ + ..._stages, + _AggregateStage(functions), + ]); + } + + /// Gets distinct values based on expressions + Pipeline distinct( + PipelineExpression expression1, [ + PipelineExpression? expression2, + PipelineExpression? expression3, + PipelineExpression? expression4, + PipelineExpression? expression5, + PipelineExpression? expression6, + PipelineExpression? expression7, + PipelineExpression? expression8, + PipelineExpression? expression9, + PipelineExpression? expression10, + PipelineExpression? expression11, + PipelineExpression? expression12, + PipelineExpression? expression13, + PipelineExpression? expression14, + PipelineExpression? expression15, + PipelineExpression? expression16, + PipelineExpression? expression17, + PipelineExpression? expression18, + PipelineExpression? expression19, + PipelineExpression? expression20, + PipelineExpression? expression21, + PipelineExpression? expression22, + PipelineExpression? expression23, + PipelineExpression? expression24, + PipelineExpression? expression25, + PipelineExpression? expression26, + PipelineExpression? expression27, + PipelineExpression? expression28, + PipelineExpression? expression29, + PipelineExpression? expression30, + ]) { + final expressions = [expression1]; + if (expression2 != null) expressions.add(expression2); + if (expression3 != null) expressions.add(expression3); + if (expression4 != null) expressions.add(expression4); + if (expression5 != null) expressions.add(expression5); + if (expression6 != null) expressions.add(expression6); + if (expression7 != null) expressions.add(expression7); + if (expression8 != null) expressions.add(expression8); + if (expression9 != null) expressions.add(expression9); + if (expression10 != null) expressions.add(expression10); + if (expression11 != null) expressions.add(expression11); + if (expression12 != null) expressions.add(expression12); + if (expression13 != null) expressions.add(expression13); + if (expression14 != null) expressions.add(expression14); + if (expression15 != null) expressions.add(expression15); + if (expression16 != null) expressions.add(expression16); + if (expression17 != null) expressions.add(expression17); + if (expression18 != null) expressions.add(expression18); + if (expression19 != null) expressions.add(expression19); + if (expression20 != null) expressions.add(expression20); + if (expression21 != null) expressions.add(expression21); + if (expression22 != null) expressions.add(expression22); + if (expression23 != null) expressions.add(expression23); + if (expression24 != null) expressions.add(expression24); + if (expression25 != null) expressions.add(expression25); + if (expression26 != null) expressions.add(expression26); + if (expression27 != null) expressions.add(expression27); + if (expression28 != null) expressions.add(expression28); + if (expression29 != null) expressions.add(expression29); + if (expression30 != null) expressions.add(expression30); + + return Pipeline._(_firestore, [ + ..._stages, + _DistinctStage(expressions), + ]); + } + + /// Finds nearest vectors using vector similarity search + Pipeline findNearest( + Field vectorField, + List vectorValue, + DistanceMeasure distanceMeasure, { + int? limit, + }) { + return Pipeline._(_firestore, [ + ..._stages, + _FindNearestStage(vectorField, vectorValue, distanceMeasure, + limit: limit), + ]); + } + + /// Limits the number of results + Pipeline limit(int limit) { + return Pipeline._(_firestore, [ + ..._stages, + _LimitStage(limit), + ]); + } + + /// Offsets the results + Pipeline offset(int offset) { + return Pipeline._(_firestore, [ + ..._stages, + _OffsetStage(offset), + ]); + } + + /// Removes specified fields from documents + Pipeline removeFields( + String fieldPath1, [ + String? fieldPath2, + String? fieldPath3, + String? fieldPath4, + String? fieldPath5, + String? fieldPath6, + String? fieldPath7, + String? fieldPath8, + String? fieldPath9, + String? fieldPath10, + String? fieldPath11, + String? fieldPath12, + String? fieldPath13, + String? fieldPath14, + String? fieldPath15, + String? fieldPath16, + String? fieldPath17, + String? fieldPath18, + String? fieldPath19, + String? fieldPath20, + String? fieldPath21, + String? fieldPath22, + String? fieldPath23, + String? fieldPath24, + String? fieldPath25, + String? fieldPath26, + String? fieldPath27, + String? fieldPath28, + String? fieldPath29, + String? fieldPath30, + ]) { + final fieldPaths = [fieldPath1]; + if (fieldPath2 != null) fieldPaths.add(fieldPath2); + if (fieldPath3 != null) fieldPaths.add(fieldPath3); + if (fieldPath4 != null) fieldPaths.add(fieldPath4); + if (fieldPath5 != null) fieldPaths.add(fieldPath5); + if (fieldPath6 != null) fieldPaths.add(fieldPath6); + if (fieldPath7 != null) fieldPaths.add(fieldPath7); + if (fieldPath8 != null) fieldPaths.add(fieldPath8); + if (fieldPath9 != null) fieldPaths.add(fieldPath9); + if (fieldPath10 != null) fieldPaths.add(fieldPath10); + if (fieldPath11 != null) fieldPaths.add(fieldPath11); + if (fieldPath12 != null) fieldPaths.add(fieldPath12); + if (fieldPath13 != null) fieldPaths.add(fieldPath13); + if (fieldPath14 != null) fieldPaths.add(fieldPath14); + if (fieldPath15 != null) fieldPaths.add(fieldPath15); + if (fieldPath16 != null) fieldPaths.add(fieldPath16); + if (fieldPath17 != null) fieldPaths.add(fieldPath17); + if (fieldPath18 != null) fieldPaths.add(fieldPath18); + if (fieldPath19 != null) fieldPaths.add(fieldPath19); + if (fieldPath20 != null) fieldPaths.add(fieldPath20); + if (fieldPath21 != null) fieldPaths.add(fieldPath21); + if (fieldPath22 != null) fieldPaths.add(fieldPath22); + if (fieldPath23 != null) fieldPaths.add(fieldPath23); + if (fieldPath24 != null) fieldPaths.add(fieldPath24); + if (fieldPath25 != null) fieldPaths.add(fieldPath25); + if (fieldPath26 != null) fieldPaths.add(fieldPath26); + if (fieldPath27 != null) fieldPaths.add(fieldPath27); + if (fieldPath28 != null) fieldPaths.add(fieldPath28); + if (fieldPath29 != null) fieldPaths.add(fieldPath29); + if (fieldPath30 != null) fieldPaths.add(fieldPath30); + + return Pipeline._(_firestore, [ + ..._stages, + _RemoveFieldsStage(fieldPaths), + ]); + } + + /// Replaces documents with the result of an expression + Pipeline replaceWith(PipelineExpression expression) { + return Pipeline._(_firestore, [ + ..._stages, + _ReplaceWithStage(expression), + ]); + } + + /// Samples documents using a sampling strategy + Pipeline sample(PipelineSample sample) { + return Pipeline._(_firestore, [ + ..._stages, + _SampleStage(sample), + ]); + } + + /// Selects specific fields using expressions + Pipeline select( + PipelineExpression expression1, [ + PipelineExpression? expression2, + PipelineExpression? expression3, + PipelineExpression? expression4, + PipelineExpression? expression5, + PipelineExpression? expression6, + PipelineExpression? expression7, + PipelineExpression? expression8, + PipelineExpression? expression9, + PipelineExpression? expression10, + PipelineExpression? expression11, + PipelineExpression? expression12, + PipelineExpression? expression13, + PipelineExpression? expression14, + PipelineExpression? expression15, + PipelineExpression? expression16, + PipelineExpression? expression17, + PipelineExpression? expression18, + PipelineExpression? expression19, + PipelineExpression? expression20, + PipelineExpression? expression21, + PipelineExpression? expression22, + PipelineExpression? expression23, + PipelineExpression? expression24, + PipelineExpression? expression25, + PipelineExpression? expression26, + PipelineExpression? expression27, + PipelineExpression? expression28, + PipelineExpression? expression29, + PipelineExpression? expression30, + ]) { + final expressions = [expression1]; + if (expression2 != null) expressions.add(expression2); + if (expression3 != null) expressions.add(expression3); + if (expression4 != null) expressions.add(expression4); + if (expression5 != null) expressions.add(expression5); + if (expression6 != null) expressions.add(expression6); + if (expression7 != null) expressions.add(expression7); + if (expression8 != null) expressions.add(expression8); + if (expression9 != null) expressions.add(expression9); + if (expression10 != null) expressions.add(expression10); + if (expression11 != null) expressions.add(expression11); + if (expression12 != null) expressions.add(expression12); + if (expression13 != null) expressions.add(expression13); + if (expression14 != null) expressions.add(expression14); + if (expression15 != null) expressions.add(expression15); + if (expression16 != null) expressions.add(expression16); + if (expression17 != null) expressions.add(expression17); + if (expression18 != null) expressions.add(expression18); + if (expression19 != null) expressions.add(expression19); + if (expression20 != null) expressions.add(expression20); + if (expression21 != null) expressions.add(expression21); + if (expression22 != null) expressions.add(expression22); + if (expression23 != null) expressions.add(expression23); + if (expression24 != null) expressions.add(expression24); + if (expression25 != null) expressions.add(expression25); + if (expression26 != null) expressions.add(expression26); + if (expression27 != null) expressions.add(expression27); + if (expression28 != null) expressions.add(expression28); + if (expression29 != null) expressions.add(expression29); + if (expression30 != null) expressions.add(expression30); + + return Pipeline._(_firestore, [ + ..._stages, + _SelectStage(expressions), + ]); + } + + /// Sorts results using an ordering specification + Pipeline sort(Ordering ordering) { + return Pipeline._(_firestore, [ + ..._stages, + _SortStage(ordering), + ]); + } + + /// Unnests arrays into separate documents + Pipeline unnest(PipelineExpression expression, [String? indexField]) { + return Pipeline._(_firestore, [ + ..._stages, + _UnnestStage(expression, indexField), + ]); + } + + /// Unions results with another pipeline + Pipeline union(Pipeline pipeline) { + return Pipeline._(_firestore, [ + ..._stages, + _UnionStage(pipeline), + ]); + } + + /// Filters documents using a boolean expression + Pipeline where(BooleanExpression expression) { + return Pipeline._(_firestore, [ + ..._stages, + _WhereStage(expression), + ]); + } +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart new file mode 100644 index 000000000000..1dd2fff39d52 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart @@ -0,0 +1,58 @@ +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of '../cloud_firestore.dart'; + +/// Base class for aggregate functions used in pipelines +abstract class PipelineAggregateFunction implements PipelineSerializable { + String? _alias; + + /// Assigns an alias to this aggregate function + PipelineAggregateFunction as(String alias) { + _alias = alias; + return this; + } + + String? get alias => _alias; + + String get name; + + @override + Map toMap() { + final map = { + 'name': name, + }; + if (_alias != null) { + map['alias'] = _alias; + } + return map; + } +} + +/// Counts all documents in the pipeline result +class CountAll extends PipelineAggregateFunction { + CountAll(); + + @override + String get name => 'count_all'; +} + +/// Counts non-null values of the specified expression +class Count extends PipelineAggregateFunction { + final PipelineExpression expression; + + Count(this.expression); + + @override + String get name => 'count'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_distance.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_distance.dart new file mode 100644 index 000000000000..8f01b16de52e --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_distance.dart @@ -0,0 +1,17 @@ +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of '../cloud_firestore.dart'; + +/// Distance measure algorithms for vector similarity search +enum DistanceMeasure { + /// Cosine similarity + cosine, + + /// Euclidean distance + euclidean, + + /// Dot product + dotProduct, +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart new file mode 100644 index 000000000000..415764350786 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart @@ -0,0 +1,394 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of '../cloud_firestore.dart'; + +/// Base interface for pipeline serialization +abstract class PipelineSerializable { + Map toMap(); +} + +/// Base class for all pipeline expressions +abstract class PipelineExpression implements PipelineSerializable { + String? _alias; + + /// Assigns an alias to this expression + PipelineExpression as(String alias) { + _alias = alias; + return this; + } + + /// Creates a descending ordering for this expression + Ordering descending() { + return Ordering(this, OrderDirection.desc); + } + + /// Creates an ascending ordering for this expression + Ordering ascending() { + return Ordering(this, OrderDirection.asc); + } + + String? get alias => _alias; + + String get name; + + @override + Map toMap() { + final map = { + 'name': name, + }; + if (_alias != null) { + map['alias'] = _alias; + } + return map; + } +} + +/// Base class for function expressions +abstract class FunctionExpression extends PipelineExpression {} + +/// Represents a field reference in a pipeline expression +class Field extends PipelineExpression { + final String fieldName; + + Field(this.fieldName); + + @override + String get name => 'field'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'field': fieldName, + }; + return map; + } +} + +/// Represents a constant literal value in a pipeline expression +class Constant extends PipelineExpression { + final dynamic literal; + + Constant(this.literal); + + @override + String get name => 'constant'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'literal': literal, + }; + return map; + } +} + +/// Represents a concatenation function expression +class Concat extends FunctionExpression { + final List expressions; + + Concat(this.expressions); + + @override + String get name => 'concat'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expressions': expressions.map((expr) => expr.toMap()).toList(), + }; + return map; + } +} + +/// Represents an aliased expression wrapper +class AliasedExpression extends PipelineExpression { + final PipelineExpression expression; + + AliasedExpression(this.expression); + + @override + String get name => 'alias'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} + +/// Base class for boolean expressions used in filtering +abstract class BooleanExpression extends PipelineExpression {} + +/// Represents a filter expression for pipeline where clauses +class PipelineFilter extends BooleanExpression { + final Object field; + final Object? isEqualTo; + final Object? isNotEqualTo; + final Object? isLessThan; + final Object? isLessThanOrEqualTo; + final Object? isGreaterThan; + final Object? isGreaterThanOrEqualTo; + final Object? arrayContains; + final List? arrayContainsAny; + final List? whereIn; + final List? whereNotIn; + final bool? isNull; + final bool? isNotNull; + final BooleanExpression? _andExpression; + final BooleanExpression? _orExpression; + + PipelineFilter( + this.field, { + this.isEqualTo, + this.isNotEqualTo, + this.isLessThan, + this.isLessThanOrEqualTo, + this.isGreaterThan, + this.isGreaterThanOrEqualTo, + this.arrayContains, + this.arrayContainsAny, + this.whereIn, + this.whereNotIn, + this.isNull, + this.isNotNull, + }) : _andExpression = null, + _orExpression = null; + + PipelineFilter._internal({ + required BooleanExpression? andExpression, + required BooleanExpression? orExpression, + }) : field = '', + isEqualTo = null, + isNotEqualTo = null, + isLessThan = null, + isLessThanOrEqualTo = null, + isGreaterThan = null, + isGreaterThanOrEqualTo = null, + arrayContains = null, + arrayContainsAny = null, + whereIn = null, + whereNotIn = null, + isNull = null, + isNotNull = null, + _andExpression = andExpression, + _orExpression = orExpression; + + /// Creates an OR filter combining multiple boolean expressions + static PipelineFilter or( + BooleanExpression expression1, [ + BooleanExpression? expression2, + BooleanExpression? expression3, + BooleanExpression? expression4, + BooleanExpression? expression5, + BooleanExpression? expression6, + BooleanExpression? expression7, + BooleanExpression? expression8, + BooleanExpression? expression9, + BooleanExpression? expression10, + BooleanExpression? expression11, + BooleanExpression? expression12, + BooleanExpression? expression13, + BooleanExpression? expression14, + BooleanExpression? expression15, + BooleanExpression? expression16, + BooleanExpression? expression17, + BooleanExpression? expression18, + BooleanExpression? expression19, + BooleanExpression? expression20, + BooleanExpression? expression21, + BooleanExpression? expression22, + BooleanExpression? expression23, + BooleanExpression? expression24, + BooleanExpression? expression25, + BooleanExpression? expression26, + BooleanExpression? expression27, + BooleanExpression? expression28, + BooleanExpression? expression29, + BooleanExpression? expression30, + ]) { + final expressions = [expression1]; + if (expression2 != null) expressions.add(expression2); + if (expression3 != null) expressions.add(expression3); + if (expression4 != null) expressions.add(expression4); + if (expression5 != null) expressions.add(expression5); + if (expression6 != null) expressions.add(expression6); + if (expression7 != null) expressions.add(expression7); + if (expression8 != null) expressions.add(expression8); + if (expression9 != null) expressions.add(expression9); + if (expression10 != null) expressions.add(expression10); + if (expression11 != null) expressions.add(expression11); + if (expression12 != null) expressions.add(expression12); + if (expression13 != null) expressions.add(expression13); + if (expression14 != null) expressions.add(expression14); + if (expression15 != null) expressions.add(expression15); + if (expression16 != null) expressions.add(expression16); + if (expression17 != null) expressions.add(expression17); + if (expression18 != null) expressions.add(expression18); + if (expression19 != null) expressions.add(expression19); + if (expression20 != null) expressions.add(expression20); + if (expression21 != null) expressions.add(expression21); + if (expression22 != null) expressions.add(expression22); + if (expression23 != null) expressions.add(expression23); + if (expression24 != null) expressions.add(expression24); + if (expression25 != null) expressions.add(expression25); + if (expression26 != null) expressions.add(expression26); + if (expression27 != null) expressions.add(expression27); + if (expression28 != null) expressions.add(expression28); + if (expression29 != null) expressions.add(expression29); + if (expression30 != null) expressions.add(expression30); + + return PipelineFilter._internal( + orExpression: _combineExpressions(expressions, 'or'), + andExpression: null, + ); + } + + /// Creates an AND filter combining multiple boolean expressions + static PipelineFilter and( + BooleanExpression expression1, [ + BooleanExpression? expression2, + BooleanExpression? expression3, + BooleanExpression? expression4, + BooleanExpression? expression5, + BooleanExpression? expression6, + BooleanExpression? expression7, + BooleanExpression? expression8, + BooleanExpression? expression9, + BooleanExpression? expression10, + BooleanExpression? expression11, + BooleanExpression? expression12, + BooleanExpression? expression13, + BooleanExpression? expression14, + BooleanExpression? expression15, + BooleanExpression? expression16, + BooleanExpression? expression17, + BooleanExpression? expression18, + BooleanExpression? expression19, + BooleanExpression? expression20, + BooleanExpression? expression21, + BooleanExpression? expression22, + BooleanExpression? expression23, + BooleanExpression? expression24, + BooleanExpression? expression25, + BooleanExpression? expression26, + BooleanExpression? expression27, + BooleanExpression? expression28, + BooleanExpression? expression29, + BooleanExpression? expression30, + ]) { + final expressions = [expression1]; + if (expression2 != null) expressions.add(expression2); + if (expression3 != null) expressions.add(expression3); + if (expression4 != null) expressions.add(expression4); + if (expression5 != null) expressions.add(expression5); + if (expression6 != null) expressions.add(expression6); + if (expression7 != null) expressions.add(expression7); + if (expression8 != null) expressions.add(expression8); + if (expression9 != null) expressions.add(expression9); + if (expression10 != null) expressions.add(expression10); + if (expression11 != null) expressions.add(expression11); + if (expression12 != null) expressions.add(expression12); + if (expression13 != null) expressions.add(expression13); + if (expression14 != null) expressions.add(expression14); + if (expression15 != null) expressions.add(expression15); + if (expression16 != null) expressions.add(expression16); + if (expression17 != null) expressions.add(expression17); + if (expression18 != null) expressions.add(expression18); + if (expression19 != null) expressions.add(expression19); + if (expression20 != null) expressions.add(expression20); + if (expression21 != null) expressions.add(expression21); + if (expression22 != null) expressions.add(expression22); + if (expression23 != null) expressions.add(expression23); + if (expression24 != null) expressions.add(expression24); + if (expression25 != null) expressions.add(expression25); + if (expression26 != null) expressions.add(expression26); + if (expression27 != null) expressions.add(expression27); + if (expression28 != null) expressions.add(expression28); + if (expression29 != null) expressions.add(expression29); + if (expression30 != null) expressions.add(expression30); + + return PipelineFilter._internal( + andExpression: _combineExpressions(expressions, 'and'), + orExpression: null, + ); + } + + static BooleanExpression _combineExpressions( + List expressions, + String operator, + ) { + if (expressions.length == 1) return expressions.first; + + // Create a nested structure for multiple expressions + BooleanExpression result = expressions.first; + for (int i = 1; i < expressions.length; i++) { + if (operator == 'and') { + result = PipelineFilter.and(result, expressions[i]); + } else { + result = PipelineFilter.or(result, expressions[i]); + } + } + return result; + } + + @override + String get name => 'filter'; + + @override + Map toMap() { + final map = super.toMap(); + + if (_andExpression != null) { + map['args'] = { + 'operator': 'and', + 'expressions': [_andExpression.toMap()], + }; + return map; + } + + if (_orExpression != null) { + map['args'] = { + 'operator': 'or', + 'expressions': [_orExpression.toMap()], + }; + return map; + } + + final args = {}; + if (field is String) { + args['field'] = field; + } else if (field is Field) { + args['field'] = (field as Field).fieldName; + } + + if (isEqualTo != null) args['isEqualTo'] = isEqualTo; + if (isNotEqualTo != null) args['isNotEqualTo'] = isNotEqualTo; + if (isLessThan != null) args['isLessThan'] = isLessThan; + if (isLessThanOrEqualTo != null) { + args['isLessThanOrEqualTo'] = isLessThanOrEqualTo; + } + if (isGreaterThan != null) args['isGreaterThan'] = isGreaterThan; + if (isGreaterThanOrEqualTo != null) { + args['isGreaterThanOrEqualTo'] = isGreaterThanOrEqualTo; + } + if (arrayContains != null) args['arrayContains'] = arrayContains; + if (arrayContainsAny != null) { + args['arrayContainsAny'] = arrayContainsAny; + } + if (whereIn != null) args['whereIn'] = whereIn; + if (whereNotIn != null) args['whereNotIn'] = whereNotIn; + if (isNull != null) args['isNull'] = isNull; + if (isNotNull != null) args['isNotNull'] = isNotNull; + + map['args'] = args; + return map; + } +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_ordering.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_ordering.dart new file mode 100644 index 000000000000..b9832c32a105 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_ordering.dart @@ -0,0 +1,30 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of '../cloud_firestore.dart'; + +/// Direction for ordering results +enum OrderDirection { + /// Ascending order + asc, + + /// Descending order + desc, +} + +/// Represents an ordering specification for pipeline sorting +class Ordering implements PipelineSerializable { + final PipelineExpression expression; + final OrderDirection direction; + + Ordering(this.expression, this.direction); + + @override + Map toMap() { + return { + 'expression': expression.toMap(), + 'order_direction': direction == OrderDirection.asc ? 'asc' : 'desc', + }; + } +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_sample.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_sample.dart new file mode 100644 index 000000000000..4136c9922872 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_sample.dart @@ -0,0 +1,43 @@ +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of '../cloud_firestore.dart'; + +/// Base class for pipeline sampling strategies +abstract class PipelineSample implements PipelineSerializable { + const PipelineSample(); + + /// Creates a sample with a fixed size + factory PipelineSample.withSize(int size) = _PipelineSampleSize; + + /// Creates a sample with a percentage + factory PipelineSample.withPercentage(double percentage) = + _PipelineSamplePercentage; +} + +/// Sample stage with a fixed size +class _PipelineSampleSize extends PipelineSample { + final int size; + + const _PipelineSampleSize(this.size); + + @override + Map toMap() => { + 'type': 'size', + 'value': size, + }; +} + +/// Sample stage with a percentage +class _PipelineSamplePercentage extends PipelineSample { + final double percentage; + + const _PipelineSamplePercentage(this.percentage); + + @override + Map toMap() => { + 'type': 'percentage', + 'value': percentage, + }; +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_source.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_source.dart new file mode 100644 index 000000000000..12e4f1ba6cb7 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_source.dart @@ -0,0 +1,66 @@ +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of '../cloud_firestore.dart'; + +/// Provides methods to create pipelines from different sources +class PipelineSource { + final FirebaseFirestore _firestore; + + PipelineSource._(this._firestore); + + /// Creates a pipeline from a collection path + Pipeline collection(String collectionPath) { + if (collectionPath.isEmpty) { + throw ArgumentError('A collection path must be a non-empty string.'); + } else if (collectionPath.contains('//')) { + throw ArgumentError('A collection path must not contain "//".'); + } + + return Pipeline._(_firestore, [ + _CollectionPipelineStage(collectionPath), + ]); + } + + /// Creates a pipeline from a collection reference + Pipeline collectionReference( + CollectionReference> collectionReference) { + return Pipeline._(_firestore, [ + _CollectionPipelineStage(collectionReference.path), + ]); + } + + /// Creates a pipeline from a collection group + Pipeline collectionGroup(String collectionId) { + if (collectionId.isEmpty) { + throw ArgumentError('A collection ID must be a non-empty string.'); + } else if (collectionId.contains('/')) { + throw ArgumentError( + 'A collection ID passed to collectionGroup() cannot contain "/".', + ); + } + + return Pipeline._(_firestore, [ + _CollectionGroupPipelineStage(collectionId), + ]); + } + + /// Creates a pipeline from a list of document references + Pipeline documents(List>> documents) { + if (documents.isEmpty) { + throw ArgumentError('Documents list must not be empty.'); + } + + return Pipeline._(_firestore, [ + _DocumentsPipelineStage(documents), + ]); + } + + /// Creates a pipeline from the entire database + Pipeline database() { + return Pipeline._(_firestore, [ + _DatabasePipelineStage(), + ]); + } +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart new file mode 100644 index 000000000000..9207533abbe8 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart @@ -0,0 +1,387 @@ +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of '../cloud_firestore.dart'; + +/// Base sealed class for all pipeline stages +sealed class PipelineStage implements PipelineSerializable { + String get name; +} + +/// Stage representing a collection source +final class _CollectionPipelineStage extends PipelineStage { + final String collectionPath; + + _CollectionPipelineStage(this.collectionPath); + + @override + String get name => 'collection'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'path': collectionPath, + }, + }; + } +} + +/// Stage representing a documents source +final class _DocumentsPipelineStage extends PipelineStage { + final List>> documents; + + _DocumentsPipelineStage(this.documents); + + @override + String get name => 'documents'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': documents + .map((doc) => { + 'path': doc.path, + }) + .toList(), + }; + } +} + +/// Stage representing a database source +final class _DatabasePipelineStage extends PipelineStage { + _DatabasePipelineStage(); + + @override + String get name => 'database'; + + @override + Map toMap() { + return { + 'stage': name, + }; + } +} + +/// Stage representing a collection group source +final class _CollectionGroupPipelineStage extends PipelineStage { + final String collectionPath; + + _CollectionGroupPipelineStage(this.collectionPath); + + @override + String get name => 'collection_group'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'path': collectionPath, + }, + }; + } +} + +/// Stage for adding fields to documents +final class _AddFieldsStage extends PipelineStage { + final List expressions; + + _AddFieldsStage(this.expressions); + + @override + String get name => 'add_fields'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'expressions': expressions.map((expr) => expr.toMap()).toList(), + }, + }; + } +} + +/// Stage for aggregating data +final class _AggregateStage extends PipelineStage { + final List aggregateFunctions; + + _AggregateStage(this.aggregateFunctions); + + @override + String get name => 'aggregate'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'aggregate_functions': + aggregateFunctions.map((func) => func.toMap()).toList(), + }, + }; + } +} + +/// Stage for getting distinct values +final class _DistinctStage extends PipelineStage { + final List expressions; + + _DistinctStage(this.expressions); + + @override + String get name => 'distinct'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'expressions': expressions.map((expr) => expr.toMap()).toList(), + }, + }; + } +} + +/// Stage for finding nearest vectors +final class _FindNearestStage extends PipelineStage { + final Field vectorField; + final List vectorValue; + final DistanceMeasure distanceMeasure; + final int? limit; + + _FindNearestStage( + this.vectorField, + this.vectorValue, + this.distanceMeasure, { + this.limit, + }); + + @override + String get name => 'find_nearest'; + + @override + Map toMap() { + final map = { + 'stage': name, + 'args': { + 'vector_field': vectorField.fieldName, + 'vector_value': vectorValue, + 'distance_measure': distanceMeasure.name, + }, + }; + if (limit != null) { + map['args']['limit'] = limit; + } + return map; + } +} + +/// Stage for limiting results +final class _LimitStage extends PipelineStage { + final int limit; + + _LimitStage(this.limit); + + @override + String get name => 'limit'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'limit': limit, + }, + }; + } +} + +/// Stage for offsetting results +final class _OffsetStage extends PipelineStage { + final int offset; + + _OffsetStage(this.offset); + + @override + String get name => 'offset'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'offset': offset, + }, + }; + } +} + +/// Stage for removing fields +final class _RemoveFieldsStage extends PipelineStage { + final List fieldPaths; + + _RemoveFieldsStage(this.fieldPaths); + + @override + String get name => 'remove_fields'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'field_paths': fieldPaths, + }, + }; + } +} + +/// Stage for replacing documents +final class _ReplaceWithStage extends PipelineStage { + final PipelineExpression expression; + + _ReplaceWithStage(this.expression); + + @override + String get name => 'replace_with'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Stage for sampling documents +final class _SampleStage extends PipelineStage { + final PipelineSample sample; + + _SampleStage(this.sample); + + @override + String get name => 'sample'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': sample.toMap(), + }; + } +} + +/// Stage for selecting specific fields +final class _SelectStage extends PipelineStage { + final List expressions; + + _SelectStage(this.expressions); + + @override + String get name => 'select'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'expressions': expressions.map((expr) => expr.toMap()).toList(), + }, + }; + } +} + +/// Stage for sorting results +final class _SortStage extends PipelineStage { + final Ordering ordering; + + _SortStage(this.ordering); + + @override + String get name => 'sort'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'expression': ordering.expression.toMap(), + 'order_direction': + ordering.direction == OrderDirection.asc ? 'asc' : 'desc', + }, + }; + } +} + +/// Stage for unnesting arrays +final class _UnnestStage extends PipelineStage { + final PipelineExpression expression; + final String? indexField; + + _UnnestStage(this.expression, this.indexField); + + @override + String get name => 'unnest'; + + @override + Map toMap() { + final map = { + 'stage': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + if (indexField != null) { + map['args']['index_field'] = indexField; + } + return map; + } +} + +/// Stage for union with another pipeline +final class _UnionStage extends PipelineStage { + final Pipeline pipeline; + + _UnionStage(this.pipeline); + + @override + String get name => 'union'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'pipeline': pipeline.getSerializableStages(), + }, + }; + } +} + +/// Stage for filtering documents +final class _WhereStage extends PipelineStage { + final BooleanExpression expression; + + _WhereStage(this.expression); + + @override + String get name => 'where'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart index 9f9b8e7866e0..58a894053a76 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart @@ -29,6 +29,7 @@ export 'src/platform_interface/platform_interface_index_definitions.dart'; export 'src/platform_interface/platform_interface_load_bundle_task.dart'; export 'src/platform_interface/platform_interface_load_bundle_task_snapshot.dart'; export 'src/platform_interface/platform_interface_persistent_cache_index_manager.dart'; +export 'src/platform_interface/platform_interface_pipeline_snapshot.dart'; export 'src/platform_interface/platform_interface_query.dart'; export 'src/platform_interface/platform_interface_query_snapshot.dart'; export 'src/platform_interface/platform_interface_transaction.dart'; diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_firestore.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_firestore.dart index 3faffc0a3778..1e76c5825670 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_firestore.dart @@ -243,6 +243,14 @@ abstract class FirebaseFirestorePlatform extends PlatformInterface { throw UnimplementedError('setLoggingEnabled() is not implemented'); } + /// Executes a pipeline and returns the results. + /// + /// The [stages] parameter contains the serialized pipeline stages. + Future executePipeline( + List> stages) { + throw UnimplementedError('executePipeline() is not implemented'); + } + @override //ignore: avoid_equals_and_hash_code_on_mutable_classes bool operator ==(Object other) => diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline_snapshot.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline_snapshot.dart new file mode 100644 index 000000000000..8b8d3b6fda30 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline_snapshot.dart @@ -0,0 +1,40 @@ +// ignore_for_file: require_trailing_commas +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import '../../cloud_firestore_platform_interface.dart'; +import 'platform_interface_document_reference.dart'; + +/// Platform interface for [PipelineSnapshot]. +abstract class PipelineSnapshotPlatform extends PlatformInterface { + /// Create an instance of [PipelineSnapshotPlatform]. + PipelineSnapshotPlatform() : super(token: _token); + + static final Object _token = Object(); + + /// The results of the pipeline execution + List get results; + + /// The execution time of the pipeline + DateTime get executionTime; +} + +/// Platform interface for [PipelineResult]. +abstract class PipelineResultPlatform extends PlatformInterface { + /// Create an instance of [PipelineResultPlatform]. + PipelineResultPlatform() : super(token: _token); + + static final Object _token = Object(); + + /// The document reference + DocumentReferencePlatform get document; + + /// The creation time of the document + DateTime get createTime; + + /// The update time of the document + DateTime get updateTime; +} From 1c69d01467363a9db930420130ad891beab58fcd Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Wed, 11 Feb 2026 15:06:06 +0000 Subject: [PATCH 02/72] feat(firestore): implement pipeline execution and stages with options --- .../cloud_firestore/lib/cloud_firestore.dart | 1 + .../cloud_firestore/lib/src/pipeline.dart | 356 +++++++++--------- .../lib/src/pipeline_aggregate.dart | 2 +- .../lib/src/pipeline_execute_options.dart | 20 + .../lib/src/pipeline_expression.dart | 121 +++--- .../lib/src/pipeline_ordering.dart | 4 +- .../lib/src/pipeline_source.dart | 30 +- .../lib/src/pipeline_stage.dart | 12 +- .../cloud_firestore_platform_interface.dart | 1 + .../method_channel_firestore.dart | 31 ++ .../method_channel_pipeline.dart | 51 +++ .../method_channel_pipeline_snapshot.dart | 69 ++++ .../platform_interface_firestore.dart | 9 +- .../platform_interface_pipeline.dart | 54 +++ 14 files changed, 516 insertions(+), 245 deletions(-) create mode 100644 packages/cloud_firestore/cloud_firestore/lib/src/pipeline_execute_options.dart create mode 100644 packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline.dart create mode 100644 packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart create mode 100644 packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline.dart diff --git a/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart b/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart index c84108ac3686..b0febde386b2 100755 --- a/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart @@ -58,6 +58,7 @@ part 'src/persistent_cache_index_manager.dart'; part 'src/pipeline.dart'; part 'src/pipeline_aggregate.dart'; part 'src/pipeline_distance.dart'; +part 'src/pipeline_execute_options.dart'; part 'src/pipeline_expression.dart'; part 'src/pipeline_ordering.dart'; part 'src/pipeline_sample.dart'; diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart index e7849f43afd7..956685e3e8a7 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart @@ -6,15 +6,31 @@ part of '../cloud_firestore.dart'; /// A pipeline for querying and transforming Firestore data class Pipeline { - final List _stages; final FirebaseFirestore _firestore; + final PipelinePlatform _delegate; - Pipeline._(this._firestore, this._stages); + Pipeline._(this._firestore, this._delegate) { + PipelinePlatform.verify(_delegate); + } + + /// Exposes the [stages] on the pipeline delegate. + /// + /// This should only be used for testing to ensure that all + /// pipeline stages are correctly set on the underlying delegate + /// when being tested from a different package. + @visibleForTesting + List> get stages { + return _delegate.stages; + } /// Executes the pipeline and returns a snapshot of the results - Future execute() async { - final platformSnapshot = - await _firestore._delegate.executePipeline(_toSerializableStages()); + Future execute({ExecuteOptions? options}) async { + final optionsMap = options != null + ? { + 'indexMode': options.indexMode.name, + } + : null; + final platformSnapshot = await _delegate.execute(options: optionsMap); return _convertPlatformSnapshot(platformSnapshot); } @@ -35,52 +51,42 @@ class Pipeline { return PipelineSnapshot._(results, platformSnapshot.executionTime); } - /// Converts stages to serializable format for platform communication - List> _toSerializableStages() { - return _stages.map((stage) => stage.toMap()).toList(); - } - - /// Public method to get serializable stages (used by union stage) - List> getSerializableStages() { - return _toSerializableStages(); - } - // Pipeline Actions /// Adds fields to documents using expressions Pipeline addFields( - PipelineExpression expression1, [ - PipelineExpression? expression2, - PipelineExpression? expression3, - PipelineExpression? expression4, - PipelineExpression? expression5, - PipelineExpression? expression6, - PipelineExpression? expression7, - PipelineExpression? expression8, - PipelineExpression? expression9, - PipelineExpression? expression10, - PipelineExpression? expression11, - PipelineExpression? expression12, - PipelineExpression? expression13, - PipelineExpression? expression14, - PipelineExpression? expression15, - PipelineExpression? expression16, - PipelineExpression? expression17, - PipelineExpression? expression18, - PipelineExpression? expression19, - PipelineExpression? expression20, - PipelineExpression? expression21, - PipelineExpression? expression22, - PipelineExpression? expression23, - PipelineExpression? expression24, - PipelineExpression? expression25, - PipelineExpression? expression26, - PipelineExpression? expression27, - PipelineExpression? expression28, - PipelineExpression? expression29, - PipelineExpression? expression30, + Expression expression1, [ + Expression? expression2, + Expression? expression3, + Expression? expression4, + Expression? expression5, + Expression? expression6, + Expression? expression7, + Expression? expression8, + Expression? expression9, + Expression? expression10, + Expression? expression11, + Expression? expression12, + Expression? expression13, + Expression? expression14, + Expression? expression15, + Expression? expression16, + Expression? expression17, + Expression? expression18, + Expression? expression19, + Expression? expression20, + Expression? expression21, + Expression? expression22, + Expression? expression23, + Expression? expression24, + Expression? expression25, + Expression? expression26, + Expression? expression27, + Expression? expression28, + Expression? expression29, + Expression? expression30, ]) { - final expressions = [expression1]; + final expressions = [expression1]; if (expression2 != null) expressions.add(expression2); if (expression3 != null) expressions.add(expression3); if (expression4 != null) expressions.add(expression4); @@ -111,10 +117,11 @@ class Pipeline { if (expression29 != null) expressions.add(expression29); if (expression30 != null) expressions.add(expression30); - return Pipeline._(_firestore, [ - ..._stages, - _AddFieldsStage(expressions), - ]); + final stage = _AddFieldsStage(expressions); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Aggregates data using aggregate functions @@ -181,46 +188,47 @@ class Pipeline { if (aggregateFunction29 != null) functions.add(aggregateFunction29); if (aggregateFunction30 != null) functions.add(aggregateFunction30); - return Pipeline._(_firestore, [ - ..._stages, - _AggregateStage(functions), - ]); + final stage = _AggregateStage(functions); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Gets distinct values based on expressions Pipeline distinct( - PipelineExpression expression1, [ - PipelineExpression? expression2, - PipelineExpression? expression3, - PipelineExpression? expression4, - PipelineExpression? expression5, - PipelineExpression? expression6, - PipelineExpression? expression7, - PipelineExpression? expression8, - PipelineExpression? expression9, - PipelineExpression? expression10, - PipelineExpression? expression11, - PipelineExpression? expression12, - PipelineExpression? expression13, - PipelineExpression? expression14, - PipelineExpression? expression15, - PipelineExpression? expression16, - PipelineExpression? expression17, - PipelineExpression? expression18, - PipelineExpression? expression19, - PipelineExpression? expression20, - PipelineExpression? expression21, - PipelineExpression? expression22, - PipelineExpression? expression23, - PipelineExpression? expression24, - PipelineExpression? expression25, - PipelineExpression? expression26, - PipelineExpression? expression27, - PipelineExpression? expression28, - PipelineExpression? expression29, - PipelineExpression? expression30, + Expression expression1, [ + Expression? expression2, + Expression? expression3, + Expression? expression4, + Expression? expression5, + Expression? expression6, + Expression? expression7, + Expression? expression8, + Expression? expression9, + Expression? expression10, + Expression? expression11, + Expression? expression12, + Expression? expression13, + Expression? expression14, + Expression? expression15, + Expression? expression16, + Expression? expression17, + Expression? expression18, + Expression? expression19, + Expression? expression20, + Expression? expression21, + Expression? expression22, + Expression? expression23, + Expression? expression24, + Expression? expression25, + Expression? expression26, + Expression? expression27, + Expression? expression28, + Expression? expression29, + Expression? expression30, ]) { - final expressions = [expression1]; + final expressions = [expression1]; if (expression2 != null) expressions.add(expression2); if (expression3 != null) expressions.add(expression3); if (expression4 != null) expressions.add(expression4); @@ -251,10 +259,11 @@ class Pipeline { if (expression29 != null) expressions.add(expression29); if (expression30 != null) expressions.add(expression30); - return Pipeline._(_firestore, [ - ..._stages, - _DistinctStage(expressions), - ]); + final stage = _DistinctStage(expressions); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Finds nearest vectors using vector similarity search @@ -264,27 +273,30 @@ class Pipeline { DistanceMeasure distanceMeasure, { int? limit, }) { - return Pipeline._(_firestore, [ - ..._stages, - _FindNearestStage(vectorField, vectorValue, distanceMeasure, - limit: limit), - ]); + final stage = _FindNearestStage(vectorField, vectorValue, distanceMeasure, + limit: limit); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Limits the number of results Pipeline limit(int limit) { - return Pipeline._(_firestore, [ - ..._stages, - _LimitStage(limit), - ]); + final stage = _LimitStage(limit); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Offsets the results Pipeline offset(int offset) { - return Pipeline._(_firestore, [ - ..._stages, - _OffsetStage(offset), - ]); + final stage = _OffsetStage(offset); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Removes specified fields from documents @@ -351,62 +363,65 @@ class Pipeline { if (fieldPath29 != null) fieldPaths.add(fieldPath29); if (fieldPath30 != null) fieldPaths.add(fieldPath30); - return Pipeline._(_firestore, [ - ..._stages, - _RemoveFieldsStage(fieldPaths), - ]); + final stage = _RemoveFieldsStage(fieldPaths); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Replaces documents with the result of an expression - Pipeline replaceWith(PipelineExpression expression) { - return Pipeline._(_firestore, [ - ..._stages, - _ReplaceWithStage(expression), - ]); + Pipeline replaceWith(Expression expression) { + final stage = _ReplaceWithStage(expression); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Samples documents using a sampling strategy Pipeline sample(PipelineSample sample) { - return Pipeline._(_firestore, [ - ..._stages, - _SampleStage(sample), - ]); + final stage = _SampleStage(sample); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } - /// Selects specific fields using expressions + /// Selects specific fields using selectable expressions Pipeline select( - PipelineExpression expression1, [ - PipelineExpression? expression2, - PipelineExpression? expression3, - PipelineExpression? expression4, - PipelineExpression? expression5, - PipelineExpression? expression6, - PipelineExpression? expression7, - PipelineExpression? expression8, - PipelineExpression? expression9, - PipelineExpression? expression10, - PipelineExpression? expression11, - PipelineExpression? expression12, - PipelineExpression? expression13, - PipelineExpression? expression14, - PipelineExpression? expression15, - PipelineExpression? expression16, - PipelineExpression? expression17, - PipelineExpression? expression18, - PipelineExpression? expression19, - PipelineExpression? expression20, - PipelineExpression? expression21, - PipelineExpression? expression22, - PipelineExpression? expression23, - PipelineExpression? expression24, - PipelineExpression? expression25, - PipelineExpression? expression26, - PipelineExpression? expression27, - PipelineExpression? expression28, - PipelineExpression? expression29, - PipelineExpression? expression30, + Selectable expression1, [ + Selectable? expression2, + Selectable? expression3, + Selectable? expression4, + Selectable? expression5, + Selectable? expression6, + Selectable? expression7, + Selectable? expression8, + Selectable? expression9, + Selectable? expression10, + Selectable? expression11, + Selectable? expression12, + Selectable? expression13, + Selectable? expression14, + Selectable? expression15, + Selectable? expression16, + Selectable? expression17, + Selectable? expression18, + Selectable? expression19, + Selectable? expression20, + Selectable? expression21, + Selectable? expression22, + Selectable? expression23, + Selectable? expression24, + Selectable? expression25, + Selectable? expression26, + Selectable? expression27, + Selectable? expression28, + Selectable? expression29, + Selectable? expression30, ]) { - final expressions = [expression1]; + final expressions = [expression1]; if (expression2 != null) expressions.add(expression2); if (expression3 != null) expressions.add(expression3); if (expression4 != null) expressions.add(expression4); @@ -437,41 +452,46 @@ class Pipeline { if (expression29 != null) expressions.add(expression29); if (expression30 != null) expressions.add(expression30); - return Pipeline._(_firestore, [ - ..._stages, - _SelectStage(expressions), - ]); + final stage = _SelectStage(expressions); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Sorts results using an ordering specification Pipeline sort(Ordering ordering) { - return Pipeline._(_firestore, [ - ..._stages, - _SortStage(ordering), - ]); + final stage = _SortStage(ordering); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Unnests arrays into separate documents - Pipeline unnest(PipelineExpression expression, [String? indexField]) { - return Pipeline._(_firestore, [ - ..._stages, - _UnnestStage(expression, indexField), - ]); + Pipeline unnest(Expression expression, [String? indexField]) { + final stage = _UnnestStage(expression, indexField); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Unions results with another pipeline Pipeline union(Pipeline pipeline) { - return Pipeline._(_firestore, [ - ..._stages, - _UnionStage(pipeline), - ]); + final stage = _UnionStage(pipeline); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Filters documents using a boolean expression Pipeline where(BooleanExpression expression) { - return Pipeline._(_firestore, [ - ..._stages, - _WhereStage(expression), - ]); + final stage = _WhereStage(expression); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } } diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart index 1dd2fff39d52..f0946ac18995 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart @@ -40,7 +40,7 @@ class CountAll extends PipelineAggregateFunction { /// Counts non-null values of the specified expression class Count extends PipelineAggregateFunction { - final PipelineExpression expression; + final Expression expression; Count(this.expression); diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_execute_options.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_execute_options.dart new file mode 100644 index 000000000000..ee8b8e2eb42d --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_execute_options.dart @@ -0,0 +1,20 @@ +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of '../cloud_firestore.dart'; + +/// Index mode for pipeline execution +enum IndexMode { + /// Use recommended index mode + recommended, +} + +/// Options for executing a pipeline +class ExecuteOptions { + final IndexMode indexMode; + + const ExecuteOptions({ + this.indexMode = IndexMode.recommended, + }); +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart index 415764350786..6323f23941fe 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart @@ -1,4 +1,4 @@ -// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. @@ -10,13 +10,13 @@ abstract class PipelineSerializable { } /// Base class for all pipeline expressions -abstract class PipelineExpression implements PipelineSerializable { - String? _alias; - - /// Assigns an alias to this expression - PipelineExpression as(String alias) { - _alias = alias; - return this; +abstract class Expression implements PipelineSerializable { + /// Creates an aliased expression + AliasedExpression as(String alias) { + return AliasedExpression( + alias: alias, + expression: this, + ); } /// Creates a descending ordering for this expression @@ -29,27 +29,54 @@ abstract class PipelineExpression implements PipelineSerializable { return Ordering(this, OrderDirection.asc); } - String? get alias => _alias; - String get name; @override Map toMap() { - final map = { + return { 'name': name, }; - if (_alias != null) { - map['alias'] = _alias; - } - return map; } } /// Base class for function expressions -abstract class FunctionExpression extends PipelineExpression {} +abstract class FunctionExpression extends Expression {} + +/// Base class for selectable expressions (can be used in select stage) +abstract class Selectable extends Expression { + String get alias; + Expression get expression; +} + +/// Represents an aliased expression wrapper +class AliasedExpression extends Selectable { + @override + final String alias; + + @override + final Expression expression; + + AliasedExpression({ + required this.alias, + required this.expression, + }); + + @override + String get name => 'alias'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} /// Represents a field reference in a pipeline expression -class Field extends PipelineExpression { +class Field extends Selectable { final String fieldName; Field(this.fieldName); @@ -57,18 +84,25 @@ class Field extends PipelineExpression { @override String get name => 'field'; + @override + String get alias => fieldName; + + @override + Expression get expression => this; + @override Map toMap() { - final map = super.toMap(); - map['args'] = { - 'field': fieldName, + return { + 'name': name, + 'args': { + 'field': fieldName, + }, }; - return map; } } /// Represents a constant literal value in a pipeline expression -class Constant extends PipelineExpression { +class Constant extends Expression { final dynamic literal; Constant(this.literal); @@ -78,17 +112,18 @@ class Constant extends PipelineExpression { @override Map toMap() { - final map = super.toMap(); - map['args'] = { - 'literal': literal, + return { + 'name': name, + 'args': { + 'literal': literal, + }, }; - return map; } } /// Represents a concatenation function expression class Concat extends FunctionExpression { - final List expressions; + final List expressions; Concat(this.expressions); @@ -97,35 +132,17 @@ class Concat extends FunctionExpression { @override Map toMap() { - final map = super.toMap(); - map['args'] = { - 'expressions': expressions.map((expr) => expr.toMap()).toList(), - }; - return map; - } -} - -/// Represents an aliased expression wrapper -class AliasedExpression extends PipelineExpression { - final PipelineExpression expression; - - AliasedExpression(this.expression); - - @override - String get name => 'alias'; - - @override - Map toMap() { - final map = super.toMap(); - map['args'] = { - 'expression': expression.toMap(), + return { + 'name': name, + 'args': { + 'expressions': expressions.map((expr) => expr.toMap()).toList(), + }, }; - return map; } } /// Base class for boolean expressions used in filtering -abstract class BooleanExpression extends PipelineExpression {} +abstract class BooleanExpression extends Expression {} /// Represents a filter expression for pipeline where clauses class PipelineFilter extends BooleanExpression { @@ -246,8 +263,8 @@ class PipelineFilter extends BooleanExpression { if (expression30 != null) expressions.add(expression30); return PipelineFilter._internal( - orExpression: _combineExpressions(expressions, 'or'), andExpression: null, + orExpression: _combineExpressions(expressions, 'or'), ); } diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_ordering.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_ordering.dart index b9832c32a105..b55687fa5d8b 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_ordering.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_ordering.dart @@ -1,4 +1,4 @@ -// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. @@ -15,7 +15,7 @@ enum OrderDirection { /// Represents an ordering specification for pipeline sorting class Ordering implements PipelineSerializable { - final PipelineExpression expression; + final Expression expression; final OrderDirection direction; Ordering(this.expression, this.direction); diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_source.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_source.dart index 12e4f1ba6cb7..c981d53cae0d 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_source.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_source.dart @@ -18,17 +18,17 @@ class PipelineSource { throw ArgumentError('A collection path must not contain "//".'); } - return Pipeline._(_firestore, [ - _CollectionPipelineStage(collectionPath), - ]); + final stage = _CollectionPipelineStage(collectionPath); + final delegate = _firestore._delegate.pipeline([stage.toMap()]); + return Pipeline._(_firestore, delegate); } /// Creates a pipeline from a collection reference Pipeline collectionReference( CollectionReference> collectionReference) { - return Pipeline._(_firestore, [ - _CollectionPipelineStage(collectionReference.path), - ]); + final stage = _CollectionPipelineStage(collectionReference.path); + final delegate = _firestore._delegate.pipeline([stage.toMap()]); + return Pipeline._(_firestore, delegate); } /// Creates a pipeline from a collection group @@ -41,9 +41,9 @@ class PipelineSource { ); } - return Pipeline._(_firestore, [ - _CollectionGroupPipelineStage(collectionId), - ]); + final stage = _CollectionGroupPipelineStage(collectionId); + final delegate = _firestore._delegate.pipeline([stage.toMap()]); + return Pipeline._(_firestore, delegate); } /// Creates a pipeline from a list of document references @@ -52,15 +52,15 @@ class PipelineSource { throw ArgumentError('Documents list must not be empty.'); } - return Pipeline._(_firestore, [ - _DocumentsPipelineStage(documents), - ]); + final stage = _DocumentsPipelineStage(documents); + final delegate = _firestore._delegate.pipeline([stage.toMap()]); + return Pipeline._(_firestore, delegate); } /// Creates a pipeline from the entire database Pipeline database() { - return Pipeline._(_firestore, [ - _DatabasePipelineStage(), - ]); + final stage = _DatabasePipelineStage(); + final delegate = _firestore._delegate.pipeline([stage.toMap()]); + return Pipeline._(_firestore, delegate); } } diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart index 9207533abbe8..7532ddb8e544 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart @@ -88,7 +88,7 @@ final class _CollectionGroupPipelineStage extends PipelineStage { /// Stage for adding fields to documents final class _AddFieldsStage extends PipelineStage { - final List expressions; + final List expressions; _AddFieldsStage(this.expressions); @@ -129,7 +129,7 @@ final class _AggregateStage extends PipelineStage { /// Stage for getting distinct values final class _DistinctStage extends PipelineStage { - final List expressions; + final List expressions; _DistinctStage(this.expressions); @@ -243,7 +243,7 @@ final class _RemoveFieldsStage extends PipelineStage { /// Stage for replacing documents final class _ReplaceWithStage extends PipelineStage { - final PipelineExpression expression; + final Expression expression; _ReplaceWithStage(this.expression); @@ -281,7 +281,7 @@ final class _SampleStage extends PipelineStage { /// Stage for selecting specific fields final class _SelectStage extends PipelineStage { - final List expressions; + final List expressions; _SelectStage(this.expressions); @@ -323,7 +323,7 @@ final class _SortStage extends PipelineStage { /// Stage for unnesting arrays final class _UnnestStage extends PipelineStage { - final PipelineExpression expression; + final Expression expression; final String? indexField; _UnnestStage(this.expression, this.indexField); @@ -360,7 +360,7 @@ final class _UnionStage extends PipelineStage { return { 'stage': name, 'args': { - 'pipeline': pipeline.getSerializableStages(), + 'pipeline': pipeline.stages, }, }; } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart index 58a894053a76..2e7e97bec179 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart @@ -29,6 +29,7 @@ export 'src/platform_interface/platform_interface_index_definitions.dart'; export 'src/platform_interface/platform_interface_load_bundle_task.dart'; export 'src/platform_interface/platform_interface_load_bundle_task_snapshot.dart'; export 'src/platform_interface/platform_interface_persistent_cache_index_manager.dart'; +export 'src/platform_interface/platform_interface_pipeline.dart'; export 'src/platform_interface/platform_interface_pipeline_snapshot.dart'; export 'src/platform_interface/platform_interface_query.dart'; export 'src/platform_interface/platform_interface_query_snapshot.dart'; diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_firestore.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_firestore.dart index 573e5a3c91b9..73aacc45f2bb 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_firestore.dart @@ -15,6 +15,8 @@ import 'package:flutter/services.dart'; import 'method_channel_collection_reference.dart'; import 'method_channel_document_reference.dart'; +import 'method_channel_pipeline.dart'; +import 'method_channel_pipeline_snapshot.dart'; import 'method_channel_query.dart'; import 'method_channel_transaction.dart'; import 'method_channel_write_batch.dart'; @@ -350,4 +352,33 @@ class MethodChannelFirebaseFirestore extends FirebaseFirestorePlatform { convertPlatformException(e, stack); } } + + @override + PipelinePlatform pipeline(List> initialStages) { + return MethodChannelPipeline(this, pigeonApp, stages: initialStages); + } + + @override + Future executePipeline( + List> stages, { + Map? options, + }) async { + try { + final MethodChannel channel = const MethodChannel( + 'plugins.flutter.io/firebase_firestore', + ); + final result = await channel.invokeMethod>( + 'Pipeline#execute', + { + 'app': pigeonApp, + 'stages': stages, + if (options != null) 'options': options, + }, + ); + + return MethodChannelPipelineSnapshot(this, pigeonApp, result!); + } on PlatformException catch (e, stack) { + throw convertPlatformException(e, stack); + } + } } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline.dart new file mode 100644 index 000000000000..7e0b65e43c7d --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline.dart @@ -0,0 +1,51 @@ +// ignore_for_file: require_trailing_commas, unnecessary_lambdas +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:cloud_firestore_platform_interface/src/platform_interface/platform_interface_pipeline.dart' + as pipeline; + +/// An implementation of [PipelinePlatform] that uses [MethodChannel] to +/// communicate with Firebase plugins. +class MethodChannelPipeline extends pipeline.PipelinePlatform { + /// Create a [MethodChannelPipeline] from [stages] + MethodChannelPipeline( + FirebaseFirestorePlatform _firestore, + this.pigeonApp, { + List>? stages, + }) : super(_firestore, stages); + + final FirestorePigeonFirebaseApp pigeonApp; + + /// Creates a new instance of [MethodChannelPipeline], however overrides + /// any existing [stages]. + /// + /// This is in place to ensure that changes to a pipeline don't mutate + /// other pipelines. + MethodChannelPipeline _copyWithStages(List> newStages) { + return MethodChannelPipeline( + firestore, + pigeonApp, + stages: List.unmodifiable([ + ...stages, + ...newStages, + ]), + ); + } + + @override + pipeline.PipelinePlatform addStage(Map serializedStage) { + return _copyWithStages([serializedStage]); + } + + @override + Future execute({ + Map? options, + }) async { + return firestore.executePipeline(stages, options: options); + } +} diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart new file mode 100644 index 000000000000..16cde78be465 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart @@ -0,0 +1,69 @@ +// ignore_for_file: require_trailing_commas +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:cloud_firestore_platform_interface/src/method_channel/method_channel_document_reference.dart'; + +/// An implementation of [PipelineSnapshotPlatform] that uses [MethodChannel] to +/// communicate with Firebase plugins. +class MethodChannelPipelineSnapshot extends PipelineSnapshotPlatform { + final List _results; + final DateTime _executionTime; + + /// Creates a [MethodChannelPipelineSnapshot] from the given [data] + MethodChannelPipelineSnapshot( + FirebaseFirestorePlatform firestore, + FirestorePigeonFirebaseApp pigeonApp, + Map data, + ) : _results = (data['results'] as List) + .map((result) => MethodChannelPipelineResult( + firestore, + pigeonApp, + result['document'] as String, + DateTime.fromMillisecondsSinceEpoch(result['createTime']), + DateTime.fromMillisecondsSinceEpoch(result['updateTime']), + )) + .toList(), + _executionTime = DateTime.fromMillisecondsSinceEpoch( + data['executionTime'] as int, + ), + super(); + + @override + List get results => _results; + + @override + DateTime get executionTime => _executionTime; +} + +/// An implementation of [PipelineResultPlatform] that uses [MethodChannel] to +/// communicate with Firebase plugins. +class MethodChannelPipelineResult extends PipelineResultPlatform { + final DocumentReferencePlatform _document; + final DateTime _createTime; + final DateTime _updateTime; + + MethodChannelPipelineResult( + FirebaseFirestorePlatform firestore, + FirestorePigeonFirebaseApp pigeonApp, + String documentPath, + this._createTime, + this._updateTime, + ) : _document = MethodChannelDocumentReference( + firestore, + documentPath, + pigeonApp, + ), + super(); + + @override + DocumentReferencePlatform get document => _document; + + @override + DateTime get createTime => _createTime; + + @override + DateTime get updateTime => _updateTime; +} diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_firestore.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_firestore.dart index 1e76c5825670..ee9cd3a32478 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_firestore.dart @@ -243,11 +243,18 @@ abstract class FirebaseFirestorePlatform extends PlatformInterface { throw UnimplementedError('setLoggingEnabled() is not implemented'); } + /// Creates a pipeline platform instance with initial stages. + PipelinePlatform pipeline(List> initialStages) { + throw UnimplementedError('pipeline() is not implemented'); + } + /// Executes a pipeline and returns the results. /// /// The [stages] parameter contains the serialized pipeline stages. Future executePipeline( - List> stages) { + List> stages, { + Map? options, + }) { throw UnimplementedError('executePipeline() is not implemented'); } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline.dart new file mode 100644 index 000000000000..2d0449ee4aef --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline.dart @@ -0,0 +1,54 @@ +// ignore_for_file: require_trailing_commas +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:meta/meta.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +/// Represents a pipeline for querying and transforming Firestore data. +@immutable +abstract class PipelinePlatform extends PlatformInterface { + /// Create a [PipelinePlatform] instance + PipelinePlatform(this.firestore, List>? stages) + : _stages = stages ?? [], + super(token: _token); + + static final Object _token = Object(); + + /// Throws an [AssertionError] if [instance] does not extend + /// [PipelinePlatform]. + /// + /// This is used by the app-facing [Pipeline] to ensure that + /// the object in which it's going to delegate calls has been + /// constructed properly. + static void verify(PipelinePlatform instance) { + PlatformInterface.verify(instance, _token); + } + + /// The [FirebaseFirestorePlatform] interface for this current pipeline. + final FirebaseFirestorePlatform firestore; + + /// Stores the pipeline stages. + final List> _stages; + + /// Exposes the [stages] on the pipeline delegate. + /// + /// This should only be used for testing to ensure that all + /// pipeline stages are correctly set on the underlying delegate + /// when being tested from a different package. + List> get stages { + return List.unmodifiable(_stages); + } + + /// Adds a serialized stage to the pipeline + PipelinePlatform addStage(Map serializedStage); + + /// Executes the pipeline and returns a snapshot of the results + Future execute({ + Map? options, + }); +} From 1094fd0e90c54be73bca226a76ca90296ee41e09 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Thu, 12 Feb 2026 10:09:53 +0000 Subject: [PATCH 03/72] refactor: change PipelineSerializable to mixin and update Constant class properties --- .../cloud_firestore/lib/src/pipeline_expression.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart index 6323f23941fe..07d575168af5 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart @@ -5,7 +5,7 @@ part of '../cloud_firestore.dart'; /// Base interface for pipeline serialization -abstract class PipelineSerializable { +mixin PipelineSerializable { Map toMap(); } @@ -101,11 +101,11 @@ class Field extends Selectable { } } -/// Represents a constant literal value in a pipeline expression +/// Represents a constant value in a pipeline expression class Constant extends Expression { - final dynamic literal; + final Object value; - Constant(this.literal); + Constant(this.value); @override String get name => 'constant'; @@ -115,7 +115,7 @@ class Constant extends Expression { return { 'name': name, 'args': { - 'literal': literal, + 'value': value, }, }; } From 89eb1984ac1a6dad13e8e6adfcf6d863806a14aa Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Thu, 12 Feb 2026 13:08:16 +0000 Subject: [PATCH 04/72] chore: add `Expression` functions --- .../lib/src/pipeline_expression.dart | 3118 +++++++++++++++-- 1 file changed, 2820 insertions(+), 298 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart index 07d575168af5..2c295e0f2e6f 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart @@ -9,6 +9,12 @@ mixin PipelineSerializable { Map toMap(); } +/// Helper function to convert values to Expression (wraps in Constant if needed) +Expression _toExpression(Object? value) { + if (value is Expression) return value; + return Constant(value!); +} + /// Base class for all pipeline expressions abstract class Expression implements PipelineSerializable { /// Creates an aliased expression @@ -29,280 +35,1485 @@ abstract class Expression implements PipelineSerializable { return Ordering(this, OrderDirection.asc); } - String get name; + // ============================================================================ + // CONDITIONAL / LOGIC OPERATIONS + // ============================================================================ - @override - Map toMap() { - return { - 'name': name, - }; + /// Returns an alternative expression if this expression is absent + Expression ifAbsent(Expression elseExpr) { + return _IfAbsentExpression(this, elseExpr); } -} -/// Base class for function expressions -abstract class FunctionExpression extends Expression {} + /// Returns an alternative value if this expression is absent + Expression ifAbsentValue(Object? elseValue) { + return _IfAbsentExpression(this, _toExpression(elseValue)); + } -/// Base class for selectable expressions (can be used in select stage) -abstract class Selectable extends Expression { - String get alias; - Expression get expression; -} + /// Returns an alternative expression if this expression errors + Expression ifError(Expression catchExpr) { + return _IfErrorExpression(this, catchExpr); + } -/// Represents an aliased expression wrapper -class AliasedExpression extends Selectable { - @override - final String alias; + /// Returns an alternative value if this expression errors + Expression ifErrorValue(Object? catchValue) { + return _IfErrorExpression(this, _toExpression(catchValue)); + } - @override - final Expression expression; + /// Checks if this expression is absent (null/undefined) + // ignore: use_to_and_as_if_applicable + BooleanExpression isAbsent() { + return _IsAbsentExpression(this); + } - AliasedExpression({ - required this.alias, - required this.expression, - }); + /// Checks if this expression produces an error + // ignore: use_to_and_as_if_applicable + BooleanExpression isError() { + return _IsErrorExpression(this); + } - @override - String get name => 'alias'; + /// Checks if this field expression exists in the document + // ignore: use_to_and_as_if_applicable + BooleanExpression exists() { + return _ExistsExpression(this); + } - @override - Map toMap() { - return { - 'name': name, - 'args': { - 'expression': expression.toMap(), - }, - }; + // ============================================================================ + // TYPE CONVERSION + // ============================================================================ + + /// Casts this expression to a boolean expression + BooleanExpression asBoolean() { + return _AsBooleanExpression(this); } -} -/// Represents a field reference in a pipeline expression -class Field extends Selectable { - final String fieldName; + /// Converts this expression to a string with a format + Expression toStringWithFormat(Expression format) { + return _ToStringWithFormatExpression(this, format); + } - Field(this.fieldName); + /// Converts this expression to a string with a literal format + Expression toStringWithFormatLiteral(String format) { + return _ToStringWithFormatExpression(this, Constant(format)); + } - @override - String get name => 'field'; + // ============================================================================ + // BITWISE OPERATIONS + // ============================================================================ - @override - String get alias => fieldName; + /// Performs bitwise AND with another expression + Expression bitAnd(Expression bitsOther) { + return _BitAndExpression(this, bitsOther); + } - @override - Expression get expression => this; + /// Performs bitwise AND with byte array + Expression bitAndBytes(List bitsOther) { + return _BitAndExpression(this, Constant(bitsOther)); + } - @override - Map toMap() { - return { - 'name': name, - 'args': { - 'field': fieldName, - }, - }; + /// Performs bitwise OR with another expression + Expression bitOr(Expression bitsOther) { + return _BitOrExpression(this, bitsOther); } -} -/// Represents a constant value in a pipeline expression -class Constant extends Expression { - final Object value; + /// Performs bitwise OR with byte array + Expression bitOrBytes(List bitsOther) { + return _BitOrExpression(this, Constant(bitsOther)); + } - Constant(this.value); + /// Performs bitwise XOR with another expression + Expression bitXor(Expression bitsOther) { + return _BitXorExpression(this, bitsOther); + } - @override - String get name => 'constant'; + /// Performs bitwise XOR with byte array + Expression bitXorBytes(List bitsOther) { + return _BitXorExpression(this, Constant(bitsOther)); + } - @override - Map toMap() { - return { - 'name': name, - 'args': { - 'value': value, - }, - }; + /// Performs bitwise NOT on this expression + // ignore: use_to_and_as_if_applicable + Expression bitNot() { + return _BitNotExpression(this); } -} -/// Represents a concatenation function expression -class Concat extends FunctionExpression { - final List expressions; + /// Shifts bits left by an expression amount + Expression bitLeftShift(Expression numberExpr) { + return _BitLeftShiftExpression(this, numberExpr); + } - Concat(this.expressions); + /// Shifts bits left by a literal amount + Expression bitLeftShiftLiteral(int number) { + return _BitLeftShiftExpression(this, Constant(number)); + } - @override - String get name => 'concat'; + /// Shifts bits right by an expression amount + Expression bitRightShift(Expression numberExpr) { + return _BitRightShiftExpression(this, numberExpr); + } - @override - Map toMap() { - return { - 'name': name, - 'args': { - 'expressions': expressions.map((expr) => expr.toMap()).toList(), - }, - }; + /// Shifts bits right by a literal amount + Expression bitRightShiftLiteral(int number) { + return _BitRightShiftExpression(this, Constant(number)); } -} -/// Base class for boolean expressions used in filtering -abstract class BooleanExpression extends Expression {} + // ============================================================================ + // DOCUMENT / PATH OPERATIONS + // ============================================================================ -/// Represents a filter expression for pipeline where clauses -class PipelineFilter extends BooleanExpression { - final Object field; - final Object? isEqualTo; - final Object? isNotEqualTo; - final Object? isLessThan; - final Object? isLessThanOrEqualTo; - final Object? isGreaterThan; - final Object? isGreaterThanOrEqualTo; - final Object? arrayContains; - final List? arrayContainsAny; - final List? whereIn; - final List? whereNotIn; - final bool? isNull; - final bool? isNotNull; - final BooleanExpression? _andExpression; - final BooleanExpression? _orExpression; + /// Returns the document ID from this path expression + // ignore: use_to_and_as_if_applicable + Expression documentId() { + return _DocumentIdExpression(this); + } - PipelineFilter( - this.field, { - this.isEqualTo, - this.isNotEqualTo, - this.isLessThan, - this.isLessThanOrEqualTo, - this.isGreaterThan, - this.isGreaterThanOrEqualTo, - this.arrayContains, - this.arrayContainsAny, - this.whereIn, - this.whereNotIn, - this.isNull, - this.isNotNull, - }) : _andExpression = null, - _orExpression = null; + /// Returns the collection ID from this path expression + // ignore: use_to_and_as_if_applicable + Expression collectionId() { + return _CollectionIdExpression(this); + } - PipelineFilter._internal({ - required BooleanExpression? andExpression, - required BooleanExpression? orExpression, - }) : field = '', - isEqualTo = null, - isNotEqualTo = null, - isLessThan = null, - isLessThanOrEqualTo = null, - isGreaterThan = null, - isGreaterThanOrEqualTo = null, - arrayContains = null, - arrayContainsAny = null, - whereIn = null, - whereNotIn = null, - isNull = null, - isNotNull = null, - _andExpression = andExpression, - _orExpression = orExpression; + // ============================================================================ + // MAP OPERATIONS + // ============================================================================ - /// Creates an OR filter combining multiple boolean expressions - static PipelineFilter or( - BooleanExpression expression1, [ - BooleanExpression? expression2, - BooleanExpression? expression3, - BooleanExpression? expression4, - BooleanExpression? expression5, - BooleanExpression? expression6, - BooleanExpression? expression7, - BooleanExpression? expression8, - BooleanExpression? expression9, - BooleanExpression? expression10, - BooleanExpression? expression11, - BooleanExpression? expression12, - BooleanExpression? expression13, - BooleanExpression? expression14, - BooleanExpression? expression15, - BooleanExpression? expression16, - BooleanExpression? expression17, - BooleanExpression? expression18, - BooleanExpression? expression19, - BooleanExpression? expression20, - BooleanExpression? expression21, - BooleanExpression? expression22, - BooleanExpression? expression23, - BooleanExpression? expression24, - BooleanExpression? expression25, - BooleanExpression? expression26, - BooleanExpression? expression27, - BooleanExpression? expression28, - BooleanExpression? expression29, - BooleanExpression? expression30, - ]) { - final expressions = [expression1]; - if (expression2 != null) expressions.add(expression2); - if (expression3 != null) expressions.add(expression3); - if (expression4 != null) expressions.add(expression4); - if (expression5 != null) expressions.add(expression5); - if (expression6 != null) expressions.add(expression6); - if (expression7 != null) expressions.add(expression7); - if (expression8 != null) expressions.add(expression8); - if (expression9 != null) expressions.add(expression9); - if (expression10 != null) expressions.add(expression10); - if (expression11 != null) expressions.add(expression11); - if (expression12 != null) expressions.add(expression12); - if (expression13 != null) expressions.add(expression13); - if (expression14 != null) expressions.add(expression14); - if (expression15 != null) expressions.add(expression15); - if (expression16 != null) expressions.add(expression16); - if (expression17 != null) expressions.add(expression17); - if (expression18 != null) expressions.add(expression18); - if (expression19 != null) expressions.add(expression19); - if (expression20 != null) expressions.add(expression20); - if (expression21 != null) expressions.add(expression21); - if (expression22 != null) expressions.add(expression22); - if (expression23 != null) expressions.add(expression23); - if (expression24 != null) expressions.add(expression24); - if (expression25 != null) expressions.add(expression25); - if (expression26 != null) expressions.add(expression26); - if (expression27 != null) expressions.add(expression27); - if (expression28 != null) expressions.add(expression28); - if (expression29 != null) expressions.add(expression29); - if (expression30 != null) expressions.add(expression30); + /// Gets a value from this map expression by key expression + Expression mapGet(Expression key) { + return _MapGetExpression(this, key); + } - return PipelineFilter._internal( - andExpression: null, - orExpression: _combineExpressions(expressions, 'or'), - ); + /// Gets a value from this map expression by literal key + Expression mapGetLiteral(String key) { + return _MapGetExpression(this, Constant(key)); } - /// Creates an AND filter combining multiple boolean expressions - static PipelineFilter and( - BooleanExpression expression1, [ - BooleanExpression? expression2, - BooleanExpression? expression3, - BooleanExpression? expression4, - BooleanExpression? expression5, - BooleanExpression? expression6, - BooleanExpression? expression7, - BooleanExpression? expression8, - BooleanExpression? expression9, - BooleanExpression? expression10, - BooleanExpression? expression11, - BooleanExpression? expression12, - BooleanExpression? expression13, - BooleanExpression? expression14, - BooleanExpression? expression15, - BooleanExpression? expression16, - BooleanExpression? expression17, - BooleanExpression? expression18, - BooleanExpression? expression19, - BooleanExpression? expression20, - BooleanExpression? expression21, - BooleanExpression? expression22, - BooleanExpression? expression23, - BooleanExpression? expression24, - BooleanExpression? expression25, - BooleanExpression? expression26, - BooleanExpression? expression27, - BooleanExpression? expression28, - BooleanExpression? expression29, - BooleanExpression? expression30, - ]) { - final expressions = [expression1]; - if (expression2 != null) expressions.add(expression2); + /// Returns the keys from this map expression + // ignore: use_to_and_as_if_applicable + Expression mapKeys() { + return _MapKeysExpression(this); + } + + /// Returns the values from this map expression + // ignore: use_to_and_as_if_applicable + Expression mapValues() { + return _MapValuesExpression(this); + } + + // ============================================================================ + // ALIASING + // ============================================================================ + + /// Assigns an alias to this expression for use in output + Selectable alias(String alias) { + return AliasedExpression(alias: alias, expression: this); + } + + // ============================================================================ + // ARITHMETIC OPERATIONS + // ============================================================================ + + /// Adds this expression to another expression + Expression add(Expression other) { + return _AddExpression(this, other); + } + + /// Adds a number to this expression + Expression addNumber(num other) { + return _AddExpression(this, Constant(other)); + } + + /// Subtracts another expression from this expression + Expression subtract(Expression other) { + return _SubtractExpression(this, other); + } + + /// Subtracts a number from this expression + Expression subtractNumber(num other) { + return _SubtractExpression(this, Constant(other)); + } + + /// Multiplies this expression by another expression + Expression multiply(Expression other) { + return _MultiplyExpression(this, other); + } + + /// Multiplies this expression by a number + Expression multiplyNumber(num other) { + return _MultiplyExpression(this, Constant(other)); + } + + /// Divides this expression by another expression + Expression divide(Expression other) { + return _DivideExpression(this, other); + } + + /// Divides this expression by a number + Expression divideNumber(num other) { + return _DivideExpression(this, Constant(other)); + } + + /// Returns the remainder of dividing this expression by another + Expression modulo(Expression other) { + return _ModuloExpression(this, other); + } + + /// Returns the remainder of dividing this expression by a number + Expression moduloNumber(num other) { + return _ModuloExpression(this, Constant(other)); + } + + /// Returns the absolute value of this expression + // ignore: use_to_and_as_if_applicable + Expression abs() { + return _AbsExpression(this); + } + + /// Returns the negation of this expression + // ignore: use_to_and_as_if_applicable + Expression negate() { + return _NegateExpression(this); + } + + // ============================================================================ + // COMPARISON OPERATIONS (return BooleanExpression) + // ============================================================================ + + /// Checks if this expression equals another expression + BooleanExpression equal(Expression other) { + return _EqualExpression(this, other); + } + + /// Checks if this expression equals a value + BooleanExpression equalValue(Object? value) { + return _EqualExpression(this, _toExpression(value)); + } + + /// Checks if this expression does not equal another expression + BooleanExpression notEqual(Expression other) { + return _NotEqualExpression(this, other); + } + + /// Checks if this expression does not equal a value + BooleanExpression notEqualValue(Object? value) { + return _NotEqualExpression(this, _toExpression(value)); + } + + /// Checks if this expression is greater than another expression + BooleanExpression greaterThan(Expression other) { + return _GreaterThanExpression(this, other); + } + + /// Checks if this expression is greater than a value + BooleanExpression greaterThanValue(Object? value) { + return _GreaterThanExpression(this, _toExpression(value)); + } + + /// Checks if this expression is greater than or equal to another expression + BooleanExpression greaterThanOrEqual(Expression other) { + return _GreaterThanOrEqualExpression(this, other); + } + + /// Checks if this expression is greater than or equal to a value + BooleanExpression greaterThanOrEqualValue(Object? value) { + return _GreaterThanOrEqualExpression(this, _toExpression(value)); + } + + /// Checks if this expression is less than another expression + BooleanExpression lessThan(Expression other) { + return _LessThanExpression(this, other); + } + + /// Checks if this expression is less than a value + BooleanExpression lessThanValue(Object? value) { + return _LessThanExpression(this, _toExpression(value)); + } + + /// Checks if this expression is less than or equal to another expression + BooleanExpression lessThanOrEqual(Expression other) { + return _LessThanOrEqualExpression(this, other); + } + + /// Checks if this expression is less than or equal to a value + BooleanExpression lessThanOrEqualValue(Object? value) { + return _LessThanOrEqualExpression(this, _toExpression(value)); + } + + // ============================================================================ + // STRING OPERATIONS + // ============================================================================ + + /// Returns the length of this string expression + // ignore: use_to_and_as_if_applicable + Expression length() { + return _LengthExpression(this); + } + + /// Concatenates this expression with other expressions/values + Expression concat(List others) { + final expressions = [this]; + for (final other in others) { + expressions.add(_toExpression(other)); + } + return _ConcatExpression(expressions); + } + + /// Converts this string expression to lowercase + Expression toLowerCase() { + return _ToLowerCaseExpression(this); + } + + /// Converts this string expression to uppercase + Expression toUpperCase() { + return _ToUpperCaseExpression(this); + } + + /// Extracts a substring from this string expression + Expression substring(Expression start, Expression end) { + return _SubstringExpression(this, start, end); + } + + /// Extracts a substring using literal indices + Expression substringLiteral(int start, int end) { + return _SubstringExpression(this, Constant(start), Constant(end)); + } + + /// Replaces occurrences of a pattern in this string + Expression replace(Expression find, Expression replacement) { + return _ReplaceExpression(this, find, replacement); + } + + /// Replaces occurrences of a string literal + Expression replaceLiteral(String find, String replacement) { + return _ReplaceExpression(this, Constant(find), Constant(replacement)); + } + + /// Splits this string expression by a delimiter + Expression split(Expression delimiter) { + return _SplitExpression(this, delimiter); + } + + /// Splits this string by a literal delimiter + Expression splitLiteral(String delimiter) { + return _SplitExpression(this, Constant(delimiter)); + } + + /// Joins array elements with a delimiter + Expression join(Expression delimiter) { + return _JoinExpression(this, delimiter); + } + + /// Joins array elements with a literal delimiter + Expression joinLiteral(String delimiter) { + return _JoinExpression(this, Constant(delimiter)); + } + + /// Trims whitespace from this string expression + // ignore: use_to_and_as_if_applicable + Expression trim() { + return _TrimExpression(this); + } + + // ============================================================================ + // ARRAY OPERATIONS + // ============================================================================ + + /// Concatenates this array with another array expression + Expression arrayConcat(Expression secondArray) { + return _ArrayConcatExpression(this, secondArray); + } + + /// Concatenates this array with multiple arrays/values + Expression arrayConcatMultiple(List otherArrays) { + final expressions = [this]; + for (final other in otherArrays) { + expressions.add(_toExpression(other)); + } + return _ArrayConcatMultipleExpression(expressions); + } + + /// Checks if this array contains an element expression + BooleanExpression arrayContainsElement(Expression element) { + return _ArrayContainsExpression(this, element); + } + + /// Checks if this array contains a value + BooleanExpression arrayContainsValue(Object? element) { + return _ArrayContainsExpression(this, _toExpression(element)); + } + + /// Returns the length of this array expression + // ignore: use_to_and_as_if_applicable + Expression arrayLength() { + return _ArrayLengthExpression(this); + } + + /// Reverses this array expression + // ignore: use_to_and_as_if_applicable + Expression arrayReverse() { + return _ArrayReverseExpression(this); + } + + /// Returns the sum of numeric elements in this array + // ignore: use_to_and_as_if_applicable + Expression arraySum() { + return _ArraySumExpression(this); + } + + /// Extracts a slice from this array + Expression arraySlice(Expression start, Expression end) { + return _ArraySliceExpression(this, start, end); + } + + /// Extracts a slice using literal indices + Expression arraySliceLiteral(int start, int end) { + return _ArraySliceExpression(this, Constant(start), Constant(end)); + } + + // ============================================================================ + // AGGREGATE FUNCTIONS + // ============================================================================ + + /// Creates a sum aggregation function from this expression + // ignore: use_to_and_as_if_applicable + PipelineAggregateFunction sum() { + return Sum(this); + } + + /// Creates an average aggregation function from this expression + // ignore: use_to_and_as_if_applicable + PipelineAggregateFunction average() { + return Average(this); + } + + /// Creates a count aggregation function from this expression + // ignore: use_to_and_as_if_applicable + PipelineAggregateFunction count() { + return Count(this); + } + + /// Creates a count distinct aggregation function from this expression + // ignore: use_to_and_as_if_applicable + PipelineAggregateFunction countDistinct() { + return CountDistinct(this); + } + + /// Creates a minimum aggregation function from this expression + // ignore: use_to_and_as_if_applicable + PipelineAggregateFunction minimum() { + return Minimum(this); + } + + /// Creates a maximum aggregation function from this expression + // ignore: use_to_and_as_if_applicable + PipelineAggregateFunction maximum() { + return Maximum(this); + } + + String get name; + + @override + Map toMap() { + return { + 'name': name, + }; + } + + // ============================================================================ + // STATIC FACTORY METHODS + // ============================================================================ + + /// Creates a constant expression from a string value + static Expression constantString(String value) => Constant(value); + + /// Creates a constant expression from a number value + static Expression constantNumber(num value) => Constant(value); + + /// Creates a constant expression from a boolean value + static Expression constantBoolean(bool value) => Constant(value); + + /// Creates a constant expression from a DateTime value + static Expression constantDateTime(DateTime value) => Constant(value); + + /// Creates a constant expression from a Timestamp value + static Expression constantTimestamp(Timestamp value) => Constant(value); + + /// Creates a constant expression from a GeoPoint value + static Expression constantGeoPoint(GeoPoint value) => Constant(value); + + /// Creates a constant expression from a Blob value + static Expression constantBlob(Blob value) => Constant(value); + + /// Creates a constant expression from a DocumentReference value + static Expression constantDocumentReference(DocumentReference value) => + Constant(value); + + /// Creates a constant expression from a byte array value + static Expression constantBytes(List value) => Constant(value); + + /// Creates a constant expression from a VectorValue value + static Expression constantVector(VectorValue value) => Constant(value); + + /// Creates a constant expression from any value (convenience) + static Expression constant(Object? value) => Constant(value!); + + /// Creates a field reference expression from a field path string + static Field field(String fieldPath) => Field(fieldPath); + + /// Creates a field reference expression from a FieldPath object + static Field fieldPath(FieldPath fieldPath) => Field(fieldPath.toString()); + + /// Creates a null value expression + static Expression nullValue() => _NullExpression(); + + /// Creates a conditional (ternary) expression + static Expression conditional( + BooleanExpression condition, + Expression thenExpr, + Expression elseExpr, + ) { + return _ConditionalExpression(condition, thenExpr, elseExpr); + } + + /// Creates a conditional expression with literal values + static Expression conditionalValues( + BooleanExpression condition, + Object? thenValue, + Object? elseValue, + ) { + return _ConditionalExpression( + condition, + _toExpression(thenValue), + _toExpression(elseValue), + ); + } + + /// Creates an array expression from elements + static Expression array(List elements) { + return _ArrayExpression( + elements.map(_toExpression).toList(), + ); + } + + /// Creates a map expression from key-value pairs + static Expression map(Map data) { + return _MapExpression(data.map((k, v) => MapEntry(k, _toExpression(v)))); + } + + /// Creates a map expression from alternating key-value expressions + static Expression mapFromPairs(List keyValuePairs) { + return _MapFromPairsExpression(keyValuePairs); + } + + /// Returns the current timestamp + static Expression currentTimestamp() { + return _CurrentTimestampExpression(); + } + + /// Adds time to a timestamp expression + static Expression timestampAdd( + Expression timestamp, + String unit, + Expression amount, + ) { + return _TimestampAddExpression(timestamp, unit, amount); + } + + /// Adds time to a timestamp with a literal amount + static Expression timestampAddLiteral( + Expression timestamp, + String unit, + int amount, + ) { + return _TimestampAddExpression(timestamp, unit, Constant(amount)); + } + + /// Subtracts time from a timestamp expression + static Expression timestampSubtract( + Expression timestamp, + String unit, + Expression amount, + ) { + return _TimestampSubtractExpression(timestamp, unit, amount); + } + + /// Subtracts time from a timestamp with a literal amount + static Expression timestampSubtractLiteral( + Expression timestamp, + String unit, + int amount, + ) { + return _TimestampSubtractExpression(timestamp, unit, Constant(amount)); + } + + /// Calculates the difference between two timestamps + static Expression timestampDiff( + Expression timestamp1, + Expression timestamp2, + String unit, + ) { + return _TimestampDiffExpression(timestamp1, timestamp2, unit); + } + + /// Truncates a timestamp to a specific unit + static Expression timestampTruncate( + Expression timestamp, + String unit, + ) { + return _TimestampTruncateExpression(timestamp, unit); + } + + /// Calculates the distance between two GeoPoint expressions + static Expression distance( + Expression geoPoint1, + Expression geoPoint2, + ) { + return _DistanceExpression(geoPoint1, geoPoint2); + } + + /// Creates a document ID expression from a DocumentReference + static Expression documentIdFromRef(DocumentReference docRef) { + return _DocumentIdFromRefExpression(docRef); + } + + /// Checks if a value is in a list (IN operator) + static BooleanExpression equalAny( + Expression value, + List values, + ) { + return _EqualAnyExpression(value, values.map(_toExpression).toList()); + } + + /// Checks if a value is not in a list (NOT IN operator) + static BooleanExpression notEqualAny( + Expression value, + List values, + ) { + return _NotEqualAnyExpression(value, values.map(_toExpression).toList()); + } + + /// Checks if a field exists in the document + static BooleanExpression existsField(String fieldName) { + return _ExistsExpression(Field(fieldName)); + } + + /// Returns an expression if another is absent + static Expression ifAbsentStatic( + Expression ifExpr, + Expression elseExpr, + ) { + return _IfAbsentExpression(ifExpr, elseExpr); + } + + /// Returns a value if an expression is absent + static Expression ifAbsentValueStatic( + Expression ifExpr, + Object? elseValue, + ) { + return _IfAbsentExpression(ifExpr, _toExpression(elseValue)); + } + + /// Checks if an expression is absent + static BooleanExpression isAbsentStatic(Expression value) { + return _IsAbsentExpression(value); + } + + /// Checks if a field is absent + static BooleanExpression isAbsentField(String fieldName) { + return _IsAbsentExpression(Field(fieldName)); + } + + /// Returns an expression if another errors + static BooleanExpression ifErrorStatic( + BooleanExpression tryExpr, + BooleanExpression catchExpr, + ) { + return _IfErrorExpression(tryExpr, catchExpr) as BooleanExpression; + } + + /// Checks if an expression produces an error + static BooleanExpression isErrorStatic(Expression expr) { + return _IsErrorExpression(expr); + } + + /// Joins array elements with a delimiter + static Expression joinStatic( + Expression arrayExpression, + Expression delimiterExpression, + ) { + return _JoinExpression(arrayExpression, delimiterExpression); + } + + /// Joins array elements with a literal delimiter + static Expression joinStaticLiteral( + Expression arrayExpression, + String delimiter, + ) { + return _JoinExpression(arrayExpression, Constant(delimiter)); + } + + /// Joins a field's array with a delimiter + static Expression joinField( + String arrayFieldName, + String delimiter, + ) { + return _JoinExpression(Field(arrayFieldName), Constant(delimiter)); + } + + /// Concatenates arrays + static Expression arrayConcatStatic( + Expression firstArray, + Expression secondArray, + List? otherArrays, + ) { + final expressions = [firstArray, secondArray]; + if (otherArrays != null) { + for (final other in otherArrays) { + expressions.add(_toExpression(other)); + } + } + return _ArrayConcatMultipleExpression(expressions); + } + + /// Returns the length of an expression + static Expression lengthStatic(Expression expr) { + return _LengthExpression(expr); + } + + /// Returns the length of a field + static Expression lengthField(String fieldName) { + return _LengthExpression(Field(fieldName)); + } + + /// Returns the absolute value of an expression + static Expression absStatic(Expression numericExpr) { + return _AbsExpression(numericExpr); + } + + /// Returns the absolute value of a field + static Expression absField(String numericField) { + return _AbsExpression(Field(numericField)); + } + + /// Negates an expression + static Expression negateStatic(Expression numericExpr) { + return _NegateExpression(numericExpr); + } + + /// Negates a field + static Expression negateField(String numericField) { + return _NegateExpression(Field(numericField)); + } + + /// Adds two expressions + static Expression addStatic( + Expression first, + Expression second, + ) { + return _AddExpression(first, second); + } + + /// Adds an expression and a number + static Expression addStaticNumber( + Expression first, + num second, + ) { + return _AddExpression(first, Constant(second)); + } + + /// Adds a field and an expression + static Expression addField( + String numericFieldName, + Expression second, + ) { + return _AddExpression(Field(numericFieldName), second); + } + + /// Adds a field and a number + static Expression addFieldNumber( + String numericFieldName, + num second, + ) { + return _AddExpression(Field(numericFieldName), Constant(second)); + } + + /// Subtracts two expressions + static Expression subtractStatic( + Expression minuend, + Expression subtrahend, + ) { + return _SubtractExpression(minuend, subtrahend); + } + + /// Multiplies two expressions + static Expression multiplyStatic( + Expression multiplicand, + Expression multiplier, + ) { + return _MultiplyExpression(multiplicand, multiplier); + } + + /// Divides two expressions + static Expression divideStatic( + Expression dividend, + Expression divisor, + ) { + return _DivideExpression(dividend, divisor); + } + + /// Returns modulo of two expressions + static Expression moduloStatic( + Expression dividend, + Expression divisor, + ) { + return _ModuloExpression(dividend, divisor); + } + + /// Compares two expressions for equality + static BooleanExpression equalStatic( + Expression left, + Expression right, + ) { + return _EqualExpression(left, right); + } + + /// Compares expression with value for equality + static BooleanExpression equalStaticValue( + Expression left, + Object? right, + ) { + return _EqualExpression(left, _toExpression(right)); + } + + /// Compares field with value for equality + static BooleanExpression equalField( + String fieldName, + Object? value, + ) { + return _EqualExpression(Field(fieldName), _toExpression(value)); + } + + /// Compares two expressions for inequality + static BooleanExpression notEqualStatic( + Expression left, + Expression right, + ) { + return _NotEqualExpression(left, right); + } + + /// Compares expression with value for inequality + static BooleanExpression notEqualStaticValue( + Expression left, + Object? right, + ) { + return _NotEqualExpression(left, _toExpression(right)); + } + + /// Greater than comparison + static BooleanExpression greaterThanStatic( + Expression left, + Expression right, + ) { + return _GreaterThanExpression(left, right); + } + + /// Greater than comparison with value + static BooleanExpression greaterThanStaticValue( + Expression left, + Object? right, + ) { + return _GreaterThanExpression(left, _toExpression(right)); + } + + /// Greater than comparison for field + static BooleanExpression greaterThanField( + String fieldName, + Object? value, + ) { + return _GreaterThanExpression(Field(fieldName), _toExpression(value)); + } + + /// Greater than or equal comparison + static BooleanExpression greaterThanOrEqualStatic( + Expression left, + Expression right, + ) { + return _GreaterThanOrEqualExpression(left, right); + } + + /// Less than comparison + static BooleanExpression lessThanStatic( + Expression left, + Expression right, + ) { + return _LessThanExpression(left, right); + } + + /// Less than comparison with value + static BooleanExpression lessThanStaticValue( + Expression left, + Object? right, + ) { + return _LessThanExpression(left, _toExpression(right)); + } + + /// Less than comparison for field + static BooleanExpression lessThanField( + String fieldName, + Object? value, + ) { + return _LessThanExpression(Field(fieldName), _toExpression(value)); + } + + /// Less than or equal comparison + static BooleanExpression lessThanOrEqualStatic( + Expression left, + Expression right, + ) { + return _LessThanOrEqualExpression(left, right); + } + + /// Concatenates expressions + static Expression concatStatic( + Expression first, + Expression second, + List? others, + ) { + final expressions = [first, second]; + if (others != null) { + for (final other in others) { + expressions.add(_toExpression(other)); + } + } + return _ConcatExpression(expressions); + } + + /// Converts to lowercase + static Expression toLowerCaseStatic(Expression stringExpr) { + return _ToLowerCaseExpression(stringExpr); + } + + /// Converts field to lowercase + static Expression toLowerCaseField(String stringField) { + return _ToLowerCaseExpression(Field(stringField)); + } + + /// Converts to uppercase + static Expression toUpperCaseStatic(Expression stringExpr) { + return _ToUpperCaseExpression(stringExpr); + } + + /// Converts field to uppercase + static Expression toUpperCaseField(String stringField) { + return _ToUpperCaseExpression(Field(stringField)); + } + + /// Trims whitespace + static Expression trimStatic(Expression stringExpr) { + return _TrimExpression(stringExpr); + } + + /// Trims field whitespace + static Expression trimField(String stringField) { + return _TrimExpression(Field(stringField)); + } + + /// Extracts substring + static Expression substringStatic( + Expression stringExpr, + Expression start, + Expression end, + ) { + return _SubstringExpression(stringExpr, start, end); + } + + /// Replaces in string + static Expression replaceStatic( + Expression stringExpr, + Expression find, + Expression replacement, + ) { + return _ReplaceExpression(stringExpr, find, replacement); + } + + /// Splits string + static Expression splitStatic( + Expression stringExpr, + Expression delimiter, + ) { + return _SplitExpression(stringExpr, delimiter); + } + + /// Reverses array + static Expression arrayReverseStatic(Expression array) { + return _ArrayReverseExpression(array); + } + + /// Reverses field array + static Expression arrayReverseField(String arrayFieldName) { + return _ArrayReverseExpression(Field(arrayFieldName)); + } + + /// Sums array + static Expression arraySumStatic(Expression array) { + return _ArraySumExpression(array); + } + + /// Sums field array + static Expression arraySumField(String arrayFieldName) { + return _ArraySumExpression(Field(arrayFieldName)); + } + + /// Gets array length + static Expression arrayLengthStatic(Expression array) { + return _ArrayLengthExpression(array); + } + + /// Gets field array length + static Expression arrayLengthField(String arrayFieldName) { + return _ArrayLengthExpression(Field(arrayFieldName)); + } + + /// Slices array + static Expression arraySliceStatic( + Expression array, + Expression start, + Expression end, + ) { + return _ArraySliceExpression(array, start, end); + } + + /// Checks array contains + static BooleanExpression arrayContainsElementStatic( + Expression array, + Expression element, + ) { + return _ArrayContainsExpression(array, element); + } + + /// Checks field array contains + static BooleanExpression arrayContainsField( + String arrayFieldName, + Object? element, + ) { + return _ArrayContainsExpression( + Field(arrayFieldName), _toExpression(element)); + } + + /// Creates a raw/custom function expression + static Expression rawFunction( + String name, + List args, + ) { + return _RawFunctionExpression(name, args); + } +} + +/// Base class for function expressions +abstract class FunctionExpression extends Expression {} + +/// Base class for selectable expressions (can be used in select stage) +abstract class Selectable extends Expression { + String get aliasName; + Expression get expression; +} + +/// Represents an aliased expression wrapper +class AliasedExpression extends Selectable { + final String _alias; + + @override + String get aliasName => _alias; + + @override + final Expression expression; + + AliasedExpression({ + required String alias, + required this.expression, + }) : _alias = alias; + + @override + String get name => 'alias'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'alias': _alias, + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a field reference in a pipeline expression +class Field extends Selectable { + final String fieldName; + + Field(this.fieldName); + + @override + String get name => 'field'; + + @override + String get aliasName => fieldName; + + @override + Expression get expression => this; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'field': fieldName, + }, + }; + } +} + +/// Represents a null value expression +class _NullExpression extends Expression { + _NullExpression(); + + @override + String get name => 'null'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'value': null, + }, + }; + } +} + +/// Represents a constant value in a pipeline expression +class Constant extends Expression { + final Object value; + + Constant(this.value); + + @override + String get name => 'constant'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'value': value, + }, + }; + } +} + +/// Represents a concatenation function expression +class Concat extends FunctionExpression { + final List expressions; + + Concat(this.expressions); + + @override + String get name => 'concat'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expressions': expressions.map((expr) => expr.toMap()).toList(), + }, + }; + } +} + +/// Represents a concat function expression (internal) +class _ConcatExpression extends FunctionExpression { + final List expressions; + + _ConcatExpression(this.expressions); + + @override + String get name => 'concat'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expressions': expressions.map((expr) => expr.toMap()).toList(), + }, + }; + } +} + +/// Represents a length function expression +class _LengthExpression extends FunctionExpression { + final Expression expression; + + _LengthExpression(this.expression); + + @override + String get name => 'length'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a toLowerCase function expression +class _ToLowerCaseExpression extends FunctionExpression { + final Expression expression; + + _ToLowerCaseExpression(this.expression); + + @override + String get name => 'to_lower_case'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a toUpperCase function expression +class _ToUpperCaseExpression extends FunctionExpression { + final Expression expression; + + _ToUpperCaseExpression(this.expression); + + @override + String get name => 'to_upper_case'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a substring function expression +class _SubstringExpression extends FunctionExpression { + final Expression expression; + final Expression start; + final Expression end; + + _SubstringExpression(this.expression, this.start, this.end); + + @override + String get name => 'substring'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + 'start': start.toMap(), + 'end': end.toMap(), + }, + }; + } +} + +/// Represents a replace function expression +class _ReplaceExpression extends FunctionExpression { + final Expression expression; + final Expression find; + final Expression replacement; + + _ReplaceExpression(this.expression, this.find, this.replacement); + + @override + String get name => 'replace'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + 'find': find.toMap(), + 'replacement': replacement.toMap(), + }, + }; + } +} + +/// Represents a split function expression +class _SplitExpression extends FunctionExpression { + final Expression expression; + final Expression delimiter; + + _SplitExpression(this.expression, this.delimiter); + + @override + String get name => 'split'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + 'delimiter': delimiter.toMap(), + }, + }; + } +} + +/// Represents a join function expression +class _JoinExpression extends FunctionExpression { + final Expression expression; + final Expression delimiter; + + _JoinExpression(this.expression, this.delimiter); + + @override + String get name => 'join'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + 'delimiter': delimiter.toMap(), + }, + }; + } +} + +/// Represents a trim function expression +class _TrimExpression extends FunctionExpression { + final Expression expression; + + _TrimExpression(this.expression); + + @override + String get name => 'trim'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Base class for boolean expressions used in filtering +abstract class BooleanExpression extends Expression {} + +/// Represents a filter expression for pipeline where clauses +class PipelineFilter extends BooleanExpression { + final Object field; + final Object? isEqualTo; + final Object? isNotEqualTo; + final Object? isLessThan; + final Object? isLessThanOrEqualTo; + final Object? isGreaterThan; + final Object? isGreaterThanOrEqualTo; + final Object? arrayContains; + final List? arrayContainsAny; + final List? whereIn; + final List? whereNotIn; + final bool? isNull; + final bool? isNotNull; + final BooleanExpression? _andExpression; + final BooleanExpression? _orExpression; + + PipelineFilter( + this.field, { + this.isEqualTo, + this.isNotEqualTo, + this.isLessThan, + this.isLessThanOrEqualTo, + this.isGreaterThan, + this.isGreaterThanOrEqualTo, + this.arrayContains, + this.arrayContainsAny, + this.whereIn, + this.whereNotIn, + this.isNull, + this.isNotNull, + }) : _andExpression = null, + _orExpression = null; + + PipelineFilter._internal({ + required BooleanExpression? andExpression, + required BooleanExpression? orExpression, + }) : field = '', + isEqualTo = null, + isNotEqualTo = null, + isLessThan = null, + isLessThanOrEqualTo = null, + isGreaterThan = null, + isGreaterThanOrEqualTo = null, + arrayContains = null, + arrayContainsAny = null, + whereIn = null, + whereNotIn = null, + isNull = null, + isNotNull = null, + _andExpression = andExpression, + _orExpression = orExpression; + + /// Creates an OR filter combining multiple boolean expressions + static PipelineFilter or( + BooleanExpression expression1, [ + BooleanExpression? expression2, + BooleanExpression? expression3, + BooleanExpression? expression4, + BooleanExpression? expression5, + BooleanExpression? expression6, + BooleanExpression? expression7, + BooleanExpression? expression8, + BooleanExpression? expression9, + BooleanExpression? expression10, + BooleanExpression? expression11, + BooleanExpression? expression12, + BooleanExpression? expression13, + BooleanExpression? expression14, + BooleanExpression? expression15, + BooleanExpression? expression16, + BooleanExpression? expression17, + BooleanExpression? expression18, + BooleanExpression? expression19, + BooleanExpression? expression20, + BooleanExpression? expression21, + BooleanExpression? expression22, + BooleanExpression? expression23, + BooleanExpression? expression24, + BooleanExpression? expression25, + BooleanExpression? expression26, + BooleanExpression? expression27, + BooleanExpression? expression28, + BooleanExpression? expression29, + BooleanExpression? expression30, + ]) { + final expressions = [expression1]; + if (expression2 != null) expressions.add(expression2); if (expression3 != null) expressions.add(expression3); if (expression4 != null) expressions.add(expression4); if (expression5 != null) expressions.add(expression5); @@ -332,80 +1543,1391 @@ class PipelineFilter extends BooleanExpression { if (expression29 != null) expressions.add(expression29); if (expression30 != null) expressions.add(expression30); - return PipelineFilter._internal( - andExpression: _combineExpressions(expressions, 'and'), - orExpression: null, - ); + return PipelineFilter._internal( + andExpression: null, + orExpression: _combineExpressions(expressions, 'or'), + ); + } + + /// Creates an AND filter combining multiple boolean expressions + static PipelineFilter and( + BooleanExpression expression1, [ + BooleanExpression? expression2, + BooleanExpression? expression3, + BooleanExpression? expression4, + BooleanExpression? expression5, + BooleanExpression? expression6, + BooleanExpression? expression7, + BooleanExpression? expression8, + BooleanExpression? expression9, + BooleanExpression? expression10, + BooleanExpression? expression11, + BooleanExpression? expression12, + BooleanExpression? expression13, + BooleanExpression? expression14, + BooleanExpression? expression15, + BooleanExpression? expression16, + BooleanExpression? expression17, + BooleanExpression? expression18, + BooleanExpression? expression19, + BooleanExpression? expression20, + BooleanExpression? expression21, + BooleanExpression? expression22, + BooleanExpression? expression23, + BooleanExpression? expression24, + BooleanExpression? expression25, + BooleanExpression? expression26, + BooleanExpression? expression27, + BooleanExpression? expression28, + BooleanExpression? expression29, + BooleanExpression? expression30, + ]) { + final expressions = [expression1]; + if (expression2 != null) expressions.add(expression2); + if (expression3 != null) expressions.add(expression3); + if (expression4 != null) expressions.add(expression4); + if (expression5 != null) expressions.add(expression5); + if (expression6 != null) expressions.add(expression6); + if (expression7 != null) expressions.add(expression7); + if (expression8 != null) expressions.add(expression8); + if (expression9 != null) expressions.add(expression9); + if (expression10 != null) expressions.add(expression10); + if (expression11 != null) expressions.add(expression11); + if (expression12 != null) expressions.add(expression12); + if (expression13 != null) expressions.add(expression13); + if (expression14 != null) expressions.add(expression14); + if (expression15 != null) expressions.add(expression15); + if (expression16 != null) expressions.add(expression16); + if (expression17 != null) expressions.add(expression17); + if (expression18 != null) expressions.add(expression18); + if (expression19 != null) expressions.add(expression19); + if (expression20 != null) expressions.add(expression20); + if (expression21 != null) expressions.add(expression21); + if (expression22 != null) expressions.add(expression22); + if (expression23 != null) expressions.add(expression23); + if (expression24 != null) expressions.add(expression24); + if (expression25 != null) expressions.add(expression25); + if (expression26 != null) expressions.add(expression26); + if (expression27 != null) expressions.add(expression27); + if (expression28 != null) expressions.add(expression28); + if (expression29 != null) expressions.add(expression29); + if (expression30 != null) expressions.add(expression30); + + return PipelineFilter._internal( + andExpression: _combineExpressions(expressions, 'and'), + orExpression: null, + ); + } + + static BooleanExpression _combineExpressions( + List expressions, + String operator, + ) { + if (expressions.length == 1) return expressions.first; + + // Create a nested structure for multiple expressions + BooleanExpression result = expressions.first; + for (int i = 1; i < expressions.length; i++) { + if (operator == 'and') { + result = PipelineFilter.and(result, expressions[i]); + } else { + result = PipelineFilter.or(result, expressions[i]); + } + } + return result; + } + + @override + String get name => 'filter'; + + @override + Map toMap() { + final map = super.toMap(); + + if (_andExpression != null) { + map['args'] = { + 'operator': 'and', + 'expressions': [_andExpression.toMap()], + }; + return map; + } + + if (_orExpression != null) { + map['args'] = { + 'operator': 'or', + 'expressions': [_orExpression.toMap()], + }; + return map; + } + + final args = {}; + if (field is String) { + args['field'] = field; + } else if (field is Field) { + args['field'] = (field as Field).fieldName; + } + + if (isEqualTo != null) args['isEqualTo'] = isEqualTo; + if (isNotEqualTo != null) args['isNotEqualTo'] = isNotEqualTo; + if (isLessThan != null) args['isLessThan'] = isLessThan; + if (isLessThanOrEqualTo != null) { + args['isLessThanOrEqualTo'] = isLessThanOrEqualTo; + } + if (isGreaterThan != null) args['isGreaterThan'] = isGreaterThan; + if (isGreaterThanOrEqualTo != null) { + args['isGreaterThanOrEqualTo'] = isGreaterThanOrEqualTo; + } + if (arrayContains != null) args['arrayContains'] = arrayContains; + if (arrayContainsAny != null) { + args['arrayContainsAny'] = arrayContainsAny; + } + if (whereIn != null) args['whereIn'] = whereIn; + if (whereNotIn != null) args['whereNotIn'] = whereNotIn; + if (isNull != null) args['isNull'] = isNull; + if (isNotNull != null) args['isNotNull'] = isNotNull; + + map['args'] = args; + return map; + } +} + +// ============================================================================ +// PATTERN DEMONSTRATION - Concrete Function Expression Classes +// ============================================================================ + +/// Represents an addition function expression +class _AddExpression extends FunctionExpression { + final Expression left; + final Expression right; + + _AddExpression(this.left, this.right); + + @override + String get name => 'add'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a subtraction function expression +class _SubtractExpression extends FunctionExpression { + final Expression left; + final Expression right; + + _SubtractExpression(this.left, this.right); + + @override + String get name => 'subtract'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents an equality comparison function expression +class _EqualExpression extends BooleanExpression { + final Expression left; + final Expression right; + + _EqualExpression(this.left, this.right); + + @override + String get name => 'equal'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a greater-than comparison function expression +class _GreaterThanExpression extends BooleanExpression { + final Expression left; + final Expression right; + + _GreaterThanExpression(this.left, this.right); + + @override + String get name => 'greater_than'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a multiply function expression +class _MultiplyExpression extends FunctionExpression { + final Expression left; + final Expression right; + + _MultiplyExpression(this.left, this.right); + + @override + String get name => 'multiply'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a divide function expression +class _DivideExpression extends FunctionExpression { + final Expression left; + final Expression right; + + _DivideExpression(this.left, this.right); + + @override + String get name => 'divide'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a modulo function expression +class _ModuloExpression extends FunctionExpression { + final Expression left; + final Expression right; + + _ModuloExpression(this.left, this.right); + + @override + String get name => 'modulo'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents an absolute value function expression +class _AbsExpression extends FunctionExpression { + final Expression expression; + + _AbsExpression(this.expression); + + @override + String get name => 'abs'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a negation function expression +class _NegateExpression extends FunctionExpression { + final Expression expression; + + _NegateExpression(this.expression); + + @override + String get name => 'negate'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a not-equal comparison function expression +class _NotEqualExpression extends BooleanExpression { + final Expression left; + final Expression right; + + _NotEqualExpression(this.left, this.right); + + @override + String get name => 'not_equal'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a greater-than-or-equal comparison function expression +class _GreaterThanOrEqualExpression extends BooleanExpression { + final Expression left; + final Expression right; + + _GreaterThanOrEqualExpression(this.left, this.right); + + @override + String get name => 'greater_than_or_equal'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a less-than comparison function expression +class _LessThanExpression extends BooleanExpression { + final Expression left; + final Expression right; + + _LessThanExpression(this.left, this.right); + + @override + String get name => 'less_than'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a less-than-or-equal comparison function expression +class _LessThanOrEqualExpression extends BooleanExpression { + final Expression left; + final Expression right; + + _LessThanOrEqualExpression(this.left, this.right); + + @override + String get name => 'less_than_or_equal'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +// ============================================================================ +// ARRAY OPERATION EXPRESSION CLASSES +// ============================================================================ + +/// Represents an array concat function expression +class _ArrayConcatExpression extends FunctionExpression { + final Expression firstArray; + final Expression secondArray; + + _ArrayConcatExpression(this.firstArray, this.secondArray); + + @override + String get name => 'array_concat'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'first': firstArray.toMap(), + 'second': secondArray.toMap(), + }, + }; + } +} + +/// Represents an array concat multiple function expression +class _ArrayConcatMultipleExpression extends FunctionExpression { + final List arrays; + + _ArrayConcatMultipleExpression(this.arrays); + + @override + String get name => 'array_concat_multiple'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'arrays': arrays.map((expr) => expr.toMap()).toList(), + }, + }; + } +} + +/// Represents an array contains function expression +class _ArrayContainsExpression extends BooleanExpression { + final Expression array; + final Expression element; + + _ArrayContainsExpression(this.array, this.element); + + @override + String get name => 'array_contains'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'array': array.toMap(), + 'element': element.toMap(), + }, + }; + } +} + +/// Represents an array length function expression +class _ArrayLengthExpression extends FunctionExpression { + final Expression expression; + + _ArrayLengthExpression(this.expression); + + @override + String get name => 'array_length'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents an array reverse function expression +class _ArrayReverseExpression extends FunctionExpression { + final Expression expression; + + _ArrayReverseExpression(this.expression); + + @override + String get name => 'array_reverse'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents an array sum function expression +class _ArraySumExpression extends FunctionExpression { + final Expression expression; + + _ArraySumExpression(this.expression); + + @override + String get name => 'array_sum'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents an array slice function expression +class _ArraySliceExpression extends FunctionExpression { + final Expression array; + final Expression start; + final Expression end; + + _ArraySliceExpression(this.array, this.start, this.end); + + @override + String get name => 'array_slice'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'array': array.toMap(), + 'start': start.toMap(), + 'end': end.toMap(), + }, + }; + } +} + +// ============================================================================ +// CONDITIONAL / LOGIC OPERATION EXPRESSION CLASSES +// ============================================================================ + +/// Represents an ifAbsent function expression +class _IfAbsentExpression extends FunctionExpression { + final Expression expression; + final Expression elseExpr; + + _IfAbsentExpression(this.expression, this.elseExpr); + + @override + String get name => 'if_absent'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + 'else': elseExpr.toMap(), + }, + }; + } +} + +/// Represents an ifError function expression +class _IfErrorExpression extends FunctionExpression { + final Expression expression; + final Expression catchExpr; + + _IfErrorExpression(this.expression, this.catchExpr); + + @override + String get name => 'if_error'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + 'catch': catchExpr.toMap(), + }, + }; + } +} + +/// Represents an isAbsent function expression +class _IsAbsentExpression extends BooleanExpression { + final Expression expression; + + _IsAbsentExpression(this.expression); + + @override + String get name => 'is_absent'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents an isError function expression +class _IsErrorExpression extends BooleanExpression { + final Expression expression; + + _IsErrorExpression(this.expression); + + @override + String get name => 'is_error'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents an exists function expression +class _ExistsExpression extends BooleanExpression { + final Expression expression; + + _ExistsExpression(this.expression); + + @override + String get name => 'exists'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a conditional (ternary) function expression +class _ConditionalExpression extends FunctionExpression { + final BooleanExpression condition; + final Expression thenExpr; + final Expression elseExpr; + + _ConditionalExpression(this.condition, this.thenExpr, this.elseExpr); + + @override + String get name => 'conditional'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'condition': condition.toMap(), + 'then': thenExpr.toMap(), + 'else': elseExpr.toMap(), + }, + }; + } +} + +// ============================================================================ +// TYPE CONVERSION EXPRESSION CLASSES +// ============================================================================ + +/// Represents an asBoolean function expression +class _AsBooleanExpression extends BooleanExpression { + final Expression expression; + + _AsBooleanExpression(this.expression); + + @override + String get name => 'as_boolean'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a toStringWithFormat function expression +class _ToStringWithFormatExpression extends FunctionExpression { + final Expression expression; + final Expression format; + + _ToStringWithFormatExpression(this.expression, this.format); + + @override + String get name => 'to_string_with_format'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + 'format': format.toMap(), + }, + }; + } +} + +// ============================================================================ +// BITWISE OPERATION EXPRESSION CLASSES +// ============================================================================ + +/// Represents a bitAnd function expression +class _BitAndExpression extends FunctionExpression { + final Expression left; + final Expression right; + + _BitAndExpression(this.left, this.right); + + @override + String get name => 'bit_and'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a bitOr function expression +class _BitOrExpression extends FunctionExpression { + final Expression left; + final Expression right; + + _BitOrExpression(this.left, this.right); + + @override + String get name => 'bit_or'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a bitXor function expression +class _BitXorExpression extends FunctionExpression { + final Expression left; + final Expression right; + + _BitXorExpression(this.left, this.right); + + @override + String get name => 'bit_xor'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a bitNot function expression +class _BitNotExpression extends FunctionExpression { + final Expression expression; + + _BitNotExpression(this.expression); + + @override + String get name => 'bit_not'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a bitLeftShift function expression +class _BitLeftShiftExpression extends FunctionExpression { + final Expression expression; + final Expression amount; + + _BitLeftShiftExpression(this.expression, this.amount); + + @override + String get name => 'bit_left_shift'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + 'amount': amount.toMap(), + }, + }; + } +} + +/// Represents a bitRightShift function expression +class _BitRightShiftExpression extends FunctionExpression { + final Expression expression; + final Expression amount; + + _BitRightShiftExpression(this.expression, this.amount); + + @override + String get name => 'bit_right_shift'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + 'amount': amount.toMap(), + }, + }; + } +} + +// ============================================================================ +// DOCUMENT / PATH OPERATION EXPRESSION CLASSES +// ============================================================================ + +/// Represents a documentId function expression +class _DocumentIdExpression extends FunctionExpression { + final Expression expression; + + _DocumentIdExpression(this.expression); + + @override + String get name => 'document_id'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a collectionId function expression +class _CollectionIdExpression extends FunctionExpression { + final Expression expression; + + _CollectionIdExpression(this.expression); + + @override + String get name => 'collection_id'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a documentIdFromRef function expression +class _DocumentIdFromRefExpression extends FunctionExpression { + final DocumentReference docRef; + + _DocumentIdFromRefExpression(this.docRef); + + @override + String get name => 'document_id_from_ref'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'doc_ref': docRef.path, + }, + }; + } +} + +// ============================================================================ +// MAP OPERATION EXPRESSION CLASSES +// ============================================================================ + +/// Represents a mapGet function expression +class _MapGetExpression extends FunctionExpression { + final Expression map; + final Expression key; + + _MapGetExpression(this.map, this.key); + + @override + String get name => 'map_get'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'map': map.toMap(), + 'key': key.toMap(), + }, + }; + } +} + +/// Represents a mapKeys function expression +class _MapKeysExpression extends FunctionExpression { + final Expression expression; + + _MapKeysExpression(this.expression); + + @override + String get name => 'map_keys'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a mapValues function expression +class _MapValuesExpression extends FunctionExpression { + final Expression expression; + + _MapValuesExpression(this.expression); + + @override + String get name => 'map_values'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +// ============================================================================ +// TIMESTAMP OPERATION EXPRESSION CLASSES +// ============================================================================ + +/// Represents a currentTimestamp function expression +class _CurrentTimestampExpression extends FunctionExpression { + _CurrentTimestampExpression(); + + @override + String get name => 'current_timestamp'; + + @override + Map toMap() { + return { + 'name': name, + }; + } +} + +/// Represents a timestampAdd function expression +class _TimestampAddExpression extends FunctionExpression { + final Expression timestamp; + final String unit; + final Expression amount; + + _TimestampAddExpression(this.timestamp, this.unit, this.amount); + + @override + String get name => 'timestamp_add'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'timestamp': timestamp.toMap(), + 'unit': unit, + 'amount': amount.toMap(), + }, + }; } +} - static BooleanExpression _combineExpressions( - List expressions, - String operator, - ) { - if (expressions.length == 1) return expressions.first; +/// Represents a timestampSubtract function expression +class _TimestampSubtractExpression extends FunctionExpression { + final Expression timestamp; + final String unit; + final Expression amount; - // Create a nested structure for multiple expressions - BooleanExpression result = expressions.first; - for (int i = 1; i < expressions.length; i++) { - if (operator == 'and') { - result = PipelineFilter.and(result, expressions[i]); - } else { - result = PipelineFilter.or(result, expressions[i]); - } - } - return result; + _TimestampSubtractExpression(this.timestamp, this.unit, this.amount); + + @override + String get name => 'timestamp_subtract'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'timestamp': timestamp.toMap(), + 'unit': unit, + 'amount': amount.toMap(), + }, + }; } +} + +/// Represents a timestampDiff function expression +class _TimestampDiffExpression extends FunctionExpression { + final Expression timestamp1; + final Expression timestamp2; + final String unit; + + _TimestampDiffExpression(this.timestamp1, this.timestamp2, this.unit); @override - String get name => 'filter'; + String get name => 'timestamp_diff'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'timestamp1': timestamp1.toMap(), + 'timestamp2': timestamp2.toMap(), + 'unit': unit, + }, + }; + } +} + +/// Represents a timestampTruncate function expression +class _TimestampTruncateExpression extends FunctionExpression { + final Expression timestamp; + final String unit; + + _TimestampTruncateExpression(this.timestamp, this.unit); + + @override + String get name => 'timestamp_truncate'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'timestamp': timestamp.toMap(), + 'unit': unit, + }, + }; + } +} + +/// Represents a distance function expression +class _DistanceExpression extends FunctionExpression { + final Expression geoPoint1; + final Expression geoPoint2; + + _DistanceExpression(this.geoPoint1, this.geoPoint2); + + @override + String get name => 'distance'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'geo_point1': geoPoint1.toMap(), + 'geo_point2': geoPoint2.toMap(), + }, + }; + } +} + +// ============================================================================ +// SPECIAL OPERATION EXPRESSION CLASSES +// ============================================================================ + +/// Represents an equalAny (IN) function expression +class _EqualAnyExpression extends BooleanExpression { + final Expression value; + final List values; + + _EqualAnyExpression(this.value, this.values); + + @override + String get name => 'equal_any'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'value': value.toMap(), + 'values': values.map((expr) => expr.toMap()).toList(), + }, + }; + } +} + +/// Represents a notEqualAny (NOT IN) function expression +class _NotEqualAnyExpression extends BooleanExpression { + final Expression value; + final List values; + + _NotEqualAnyExpression(this.value, this.values); + + @override + String get name => 'not_equal_any'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'value': value.toMap(), + 'values': values.map((expr) => expr.toMap()).toList(), + }, + }; + } +} + +/// Represents an array expression +class _ArrayExpression extends FunctionExpression { + final List elements; + + _ArrayExpression(this.elements); + + @override + String get name => 'array'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'elements': elements.map((expr) => expr.toMap()).toList(), + }, + }; + } +} + +/// Represents a map expression +class _MapExpression extends FunctionExpression { + final Map data; + + _MapExpression(this.data); + + @override + String get name => 'map'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'data': data.map((k, v) => MapEntry(k, v.toMap())), + }, + }; + } +} + +/// Represents a mapFromPairs expression +class _MapFromPairsExpression extends FunctionExpression { + final List keyValuePairs; + + _MapFromPairsExpression(this.keyValuePairs); + + @override + String get name => 'map_from_pairs'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'pairs': keyValuePairs.map((expr) => expr.toMap()).toList(), + }, + }; + } +} + +/// Represents a raw function expression +class _RawFunctionExpression extends FunctionExpression { + final String functionName; + final List args; + + _RawFunctionExpression(this.functionName, this.args); + + @override + String get name => functionName; + + @override + Map toMap() { + return { + 'name': name, + 'args': args.map((expr) => expr.toMap()).toList(), + }; + } +} + +// ============================================================================ +// AGGREGATE FUNCTION CLASSES +// ============================================================================ + +/// Sums numeric values of the specified expression +class Sum extends PipelineAggregateFunction { + final Expression expression; + + Sum(this.expression); + + @override + String get name => 'sum'; @override Map toMap() { final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} - if (_andExpression != null) { - map['args'] = { - 'operator': 'and', - 'expressions': [_andExpression.toMap()], - }; - return map; - } +/// Calculates average of numeric values of the specified expression +class Average extends PipelineAggregateFunction { + final Expression expression; - if (_orExpression != null) { - map['args'] = { - 'operator': 'or', - 'expressions': [_orExpression.toMap()], - }; - return map; - } + Average(this.expression); - final args = {}; - if (field is String) { - args['field'] = field; - } else if (field is Field) { - args['field'] = (field as Field).fieldName; - } + @override + String get name => 'average'; - if (isEqualTo != null) args['isEqualTo'] = isEqualTo; - if (isNotEqualTo != null) args['isNotEqualTo'] = isNotEqualTo; - if (isLessThan != null) args['isLessThan'] = isLessThan; - if (isLessThanOrEqualTo != null) { - args['isLessThanOrEqualTo'] = isLessThanOrEqualTo; - } - if (isGreaterThan != null) args['isGreaterThan'] = isGreaterThan; - if (isGreaterThanOrEqualTo != null) { - args['isGreaterThanOrEqualTo'] = isGreaterThanOrEqualTo; - } - if (arrayContains != null) args['arrayContains'] = arrayContains; - if (arrayContainsAny != null) { - args['arrayContainsAny'] = arrayContainsAny; - } - if (whereIn != null) args['whereIn'] = whereIn; - if (whereNotIn != null) args['whereNotIn'] = whereNotIn; - if (isNull != null) args['isNull'] = isNull; - if (isNotNull != null) args['isNotNull'] = isNotNull; + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} - map['args'] = args; +/// Counts distinct values of the specified expression +class CountDistinct extends PipelineAggregateFunction { + final Expression expression; + + CountDistinct(this.expression); + + @override + String get name => 'count_distinct'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} + +/// Finds minimum value of the specified expression +class Minimum extends PipelineAggregateFunction { + final Expression expression; + + Minimum(this.expression); + + @override + String get name => 'minimum'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} + +/// Finds maximum value of the specified expression +class Maximum extends PipelineAggregateFunction { + final Expression expression; + + Maximum(this.expression); + + @override + String get name => 'maximum'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; return map; } } From d92a2b0d3480ff5ac0bc8d1768571d06b6f12ce0 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Fri, 13 Feb 2026 11:17:58 +0000 Subject: [PATCH 05/72] chore: add support for Pigeon --- .../GeneratedAndroidFirebaseFirestore.java | 273 +++++- .../cloud_firestore/FirestoreMessages.g.m | 137 ++- .../Public/FirestoreMessages.g.h | 29 +- .../cloud_firestore/lib/src/firestore.dart | 1 + .../cloud_firestore/windows/messages.g.cpp | 177 +++- .../cloud_firestore/windows/messages.g.h | 72 +- .../method_channel_firestore.dart | 28 +- .../method_channel_pipeline_snapshot.dart | 15 +- .../lib/src/pigeon/messages.pigeon.dart | 409 ++++---- .../pigeons/messages.dart | 29 + .../test/pigeon/test_api.dart | 899 +++++++----------- 11 files changed, 1275 insertions(+), 794 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java index f7d24bc7c7ec..3638853507b6 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java @@ -12,6 +12,7 @@ import io.flutter.plugin.common.BasicMessageChannel; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MessageCodec; +import io.flutter.plugin.common.StandardMessageCodec; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -325,7 +326,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(5); toListResult.add(persistenceEnabled); toListResult.add(host); @@ -433,7 +434,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(3); toListResult.add(appName); toListResult.add((settings == null) ? null : settings.toList()); @@ -512,7 +513,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(2); toListResult.add(hasPendingWrites); toListResult.add(isFromCache); @@ -603,7 +604,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(3); toListResult.add(path); toListResult.add(data); @@ -724,7 +725,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(4); toListResult.add(type == null ? null : type.index); toListResult.add((document == null) ? null : document.toList()); @@ -833,7 +834,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(3); toListResult.add(documents); toListResult.add(documentChanges); @@ -856,6 +857,187 @@ public ArrayList toList() { } } + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class PigeonPipelineResult { + private @NonNull String documentPath; + + public @NonNull String getDocumentPath() { + return documentPath; + } + + public void setDocumentPath(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"documentPath\" is null."); + } + this.documentPath = setterArg; + } + + private @NonNull Long createTime; + + public @NonNull Long getCreateTime() { + return createTime; + } + + public void setCreateTime(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"createTime\" is null."); + } + this.createTime = setterArg; + } + + private @NonNull Long updateTime; + + public @NonNull Long getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"updateTime\" is null."); + } + this.updateTime = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PigeonPipelineResult() {} + + public static final class Builder { + + private @Nullable String documentPath; + + public @NonNull Builder setDocumentPath(@NonNull String setterArg) { + this.documentPath = setterArg; + return this; + } + + private @Nullable Long createTime; + + public @NonNull Builder setCreateTime(@NonNull Long setterArg) { + this.createTime = setterArg; + return this; + } + + private @Nullable Long updateTime; + + public @NonNull Builder setUpdateTime(@NonNull Long setterArg) { + this.updateTime = setterArg; + return this; + } + + public @NonNull PigeonPipelineResult build() { + PigeonPipelineResult pigeonReturn = new PigeonPipelineResult(); + pigeonReturn.setDocumentPath(documentPath); + pigeonReturn.setCreateTime(createTime); + pigeonReturn.setUpdateTime(updateTime); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(3); + toListResult.add(documentPath); + toListResult.add(createTime); + toListResult.add(updateTime); + return toListResult; + } + + static @NonNull PigeonPipelineResult fromList(@NonNull ArrayList list) { + PigeonPipelineResult pigeonResult = new PigeonPipelineResult(); + Object documentPath = list.get(0); + pigeonResult.setDocumentPath((String) documentPath); + Object createTime = list.get(1); + pigeonResult.setCreateTime( + (createTime == null) + ? null + : ((createTime instanceof Integer) ? (Integer) createTime : (Long) createTime)); + Object updateTime = list.get(2); + pigeonResult.setUpdateTime( + (updateTime == null) + ? null + : ((updateTime instanceof Integer) ? (Integer) updateTime : (Long) updateTime)); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class PigeonPipelineSnapshot { + private @NonNull List results; + + public @NonNull List getResults() { + return results; + } + + public void setResults(@NonNull List setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"results\" is null."); + } + this.results = setterArg; + } + + private @NonNull Long executionTime; + + public @NonNull Long getExecutionTime() { + return executionTime; + } + + public void setExecutionTime(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"executionTime\" is null."); + } + this.executionTime = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PigeonPipelineSnapshot() {} + + public static final class Builder { + + private @Nullable List results; + + public @NonNull Builder setResults(@NonNull List setterArg) { + this.results = setterArg; + return this; + } + + private @Nullable Long executionTime; + + public @NonNull Builder setExecutionTime(@NonNull Long setterArg) { + this.executionTime = setterArg; + return this; + } + + public @NonNull PigeonPipelineSnapshot build() { + PigeonPipelineSnapshot pigeonReturn = new PigeonPipelineSnapshot(); + pigeonReturn.setResults(results); + pigeonReturn.setExecutionTime(executionTime); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(2); + toListResult.add(results); + toListResult.add(executionTime); + return toListResult; + } + + static @NonNull PigeonPipelineSnapshot fromList(@NonNull ArrayList list) { + PigeonPipelineSnapshot pigeonResult = new PigeonPipelineSnapshot(); + Object results = list.get(0); + pigeonResult.setResults((List) results); + Object executionTime = list.get(1); + pigeonResult.setExecutionTime( + (executionTime == null) + ? null + : ((executionTime instanceof Integer) + ? (Integer) executionTime + : (Long) executionTime)); + return pigeonResult; + } + } + /** Generated class from Pigeon that represents data sent in messages. */ public static final class PigeonGetOptions { private @NonNull Source source; @@ -913,7 +1095,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(2); toListResult.add(source == null ? null : source.index); toListResult.add(serverTimestampBehavior == null ? null : serverTimestampBehavior.index); @@ -978,7 +1160,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(2); toListResult.add(merge); toListResult.add(mergeFields); @@ -1087,7 +1269,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(4); toListResult.add(type == null ? null : type.index); toListResult.add(path); @@ -1219,7 +1401,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(5); toListResult.add(path); toListResult.add(data); @@ -1422,7 +1604,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(9); toListResult.add(where); toListResult.add(orderBy); @@ -1517,7 +1699,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(2); toListResult.add(type == null ? null : type.index); toListResult.add(field); @@ -1605,7 +1787,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(3); toListResult.add(type == null ? null : type.index); toListResult.add(field); @@ -1632,7 +1814,7 @@ public interface Result { void error(@NonNull Throwable error); } - private static class FirebaseFirestoreHostApiCodec extends FlutterFirebaseFirestoreMessageCodec { + private static class FirebaseFirestoreHostApiCodec extends StandardMessageCodec { public static final FirebaseFirestoreHostApiCodec INSTANCE = new FirebaseFirestoreHostApiCodec(); @@ -1660,12 +1842,16 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { case (byte) 136: return PigeonGetOptions.fromList((ArrayList) readValue(buffer)); case (byte) 137: - return PigeonQueryParameters.fromList((ArrayList) readValue(buffer)); + return PigeonPipelineResult.fromList((ArrayList) readValue(buffer)); case (byte) 138: - return PigeonQuerySnapshot.fromList((ArrayList) readValue(buffer)); + return PigeonPipelineSnapshot.fromList((ArrayList) readValue(buffer)); case (byte) 139: - return PigeonSnapshotMetadata.fromList((ArrayList) readValue(buffer)); + return PigeonQueryParameters.fromList((ArrayList) readValue(buffer)); case (byte) 140: + return PigeonQuerySnapshot.fromList((ArrayList) readValue(buffer)); + case (byte) 141: + return PigeonSnapshotMetadata.fromList((ArrayList) readValue(buffer)); + case (byte) 142: return PigeonTransactionCommand.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); @@ -1701,17 +1887,23 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { } else if (value instanceof PigeonGetOptions) { stream.write(136); writeValue(stream, ((PigeonGetOptions) value).toList()); - } else if (value instanceof PigeonQueryParameters) { + } else if (value instanceof PigeonPipelineResult) { stream.write(137); + writeValue(stream, ((PigeonPipelineResult) value).toList()); + } else if (value instanceof PigeonPipelineSnapshot) { + stream.write(138); + writeValue(stream, ((PigeonPipelineSnapshot) value).toList()); + } else if (value instanceof PigeonQueryParameters) { + stream.write(139); writeValue(stream, ((PigeonQueryParameters) value).toList()); } else if (value instanceof PigeonQuerySnapshot) { - stream.write(138); + stream.write(140); writeValue(stream, ((PigeonQuerySnapshot) value).toList()); } else if (value instanceof PigeonSnapshotMetadata) { - stream.write(139); + stream.write(141); writeValue(stream, ((PigeonSnapshotMetadata) value).toList()); } else if (value instanceof PigeonTransactionCommand) { - stream.write(140); + stream.write(142); writeValue(stream, ((PigeonTransactionCommand) value).toList()); } else { super.writeValue(stream, value); @@ -1836,6 +2028,12 @@ void persistenceCacheIndexManagerRequest( @NonNull PersistenceCacheIndexManagerRequest request, @NonNull Result result); + void executePipeline( + @NonNull FirestorePigeonFirebaseApp app, + @NonNull List> stages, + @Nullable Map options, + @NonNull Result result); + /** The codec used by FirebaseFirestoreHostApi. */ static @NonNull MessageCodec getCodec() { return FirebaseFirestoreHostApiCodec.INSTANCE; @@ -2624,6 +2822,39 @@ public void error(Throwable error) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + FirestorePigeonFirebaseApp appArg = (FirestorePigeonFirebaseApp) args.get(0); + List> stagesArg = (List>) args.get(1); + Map optionsArg = (Map) args.get(2); + Result resultCallback = + new Result() { + public void success(PigeonPipelineSnapshot result) { + wrapped.add(0, result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.executePipeline(appArg, stagesArg, optionsArg, resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } } } } diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m index 34e717b694a7..2818f5742f4b 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m @@ -5,8 +5,6 @@ // See also: https://pub.dev/packages/pigeon #import "FirestoreMessages.g.h" -#import "FLTFirebaseFirestoreReader.h" -#import "FLTFirebaseFirestoreWriter.h" #if TARGET_OS_OSX #import @@ -168,6 +166,18 @@ + (nullable PigeonQuerySnapshot *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end +@interface PigeonPipelineResult () ++ (PigeonPipelineResult *)fromList:(NSArray *)list; ++ (nullable PigeonPipelineResult *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + +@interface PigeonPipelineSnapshot () ++ (PigeonPipelineSnapshot *)fromList:(NSArray *)list; ++ (nullable PigeonPipelineSnapshot *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + @interface PigeonGetOptions () + (PigeonGetOptions *)fromList:(NSArray *)list; + (nullable PigeonGetOptions *)nullableFromList:(NSArray *)list; @@ -349,7 +359,7 @@ + (instancetype)makeWithType:(DocumentChangeType)type pigeonResult.type = type; pigeonResult.document = document; pigeonResult.oldIndex = oldIndex; - pigeonResult.index = newIndex; + pigeonResult.newIndex = newIndex; return pigeonResult; } + (PigeonDocumentChange *)fromList:(NSArray *)list { @@ -360,8 +370,8 @@ + (PigeonDocumentChange *)fromList:(NSArray *)list { NSAssert(pigeonResult.document != nil, @""); pigeonResult.oldIndex = GetNullableObjectAtIndex(list, 2); NSAssert(pigeonResult.oldIndex != nil, @""); - pigeonResult.index = GetNullableObjectAtIndex(list, 3); - NSAssert(pigeonResult.index != nil, @""); + pigeonResult.newIndex = GetNullableObjectAtIndex(list, 3); + NSAssert(pigeonResult.newIndex != nil, @""); return pigeonResult; } + (nullable PigeonDocumentChange *)nullableFromList:(NSArray *)list { @@ -372,7 +382,7 @@ - (NSArray *)toList { @(self.type), (self.document ? [self.document toList] : [NSNull null]), (self.oldIndex ?: [NSNull null]), - (self.index ?: [NSNull null]), + (self.newIndex ?: [NSNull null]), ]; } @end @@ -410,6 +420,65 @@ - (NSArray *)toList { } @end +@implementation PigeonPipelineResult ++ (instancetype)makeWithDocumentPath:(NSString *)documentPath + createTime:(NSNumber *)createTime + updateTime:(NSNumber *)updateTime { + PigeonPipelineResult *pigeonResult = [[PigeonPipelineResult alloc] init]; + pigeonResult.documentPath = documentPath; + pigeonResult.createTime = createTime; + pigeonResult.updateTime = updateTime; + return pigeonResult; +} ++ (PigeonPipelineResult *)fromList:(NSArray *)list { + PigeonPipelineResult *pigeonResult = [[PigeonPipelineResult alloc] init]; + pigeonResult.documentPath = GetNullableObjectAtIndex(list, 0); + NSAssert(pigeonResult.documentPath != nil, @""); + pigeonResult.createTime = GetNullableObjectAtIndex(list, 1); + NSAssert(pigeonResult.createTime != nil, @""); + pigeonResult.updateTime = GetNullableObjectAtIndex(list, 2); + NSAssert(pigeonResult.updateTime != nil, @""); + return pigeonResult; +} ++ (nullable PigeonPipelineResult *)nullableFromList:(NSArray *)list { + return (list) ? [PigeonPipelineResult fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + (self.documentPath ?: [NSNull null]), + (self.createTime ?: [NSNull null]), + (self.updateTime ?: [NSNull null]), + ]; +} +@end + +@implementation PigeonPipelineSnapshot ++ (instancetype)makeWithResults:(NSArray *)results + executionTime:(NSNumber *)executionTime { + PigeonPipelineSnapshot *pigeonResult = [[PigeonPipelineSnapshot alloc] init]; + pigeonResult.results = results; + pigeonResult.executionTime = executionTime; + return pigeonResult; +} ++ (PigeonPipelineSnapshot *)fromList:(NSArray *)list { + PigeonPipelineSnapshot *pigeonResult = [[PigeonPipelineSnapshot alloc] init]; + pigeonResult.results = GetNullableObjectAtIndex(list, 0); + NSAssert(pigeonResult.results != nil, @""); + pigeonResult.executionTime = GetNullableObjectAtIndex(list, 1); + NSAssert(pigeonResult.executionTime != nil, @""); + return pigeonResult; +} ++ (nullable PigeonPipelineSnapshot *)nullableFromList:(NSArray *)list { + return (list) ? [PigeonPipelineSnapshot fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + (self.results ?: [NSNull null]), + (self.executionTime ?: [NSNull null]), + ]; +} +@end + @implementation PigeonGetOptions + (instancetype)makeWithSource:(Source)source serverTimestampBehavior:(ServerTimestampBehavior)serverTimestampBehavior { @@ -649,7 +718,7 @@ - (NSArray *)toList { } @end -@interface FirebaseFirestoreHostApiCodecReader : FLTFirebaseFirestoreReader +@interface FirebaseFirestoreHostApiCodecReader : FlutterStandardReader @end @implementation FirebaseFirestoreHostApiCodecReader - (nullable id)readValueOfType:(UInt8)type { @@ -673,12 +742,16 @@ - (nullable id)readValueOfType:(UInt8)type { case 136: return [PigeonGetOptions fromList:[self readValue]]; case 137: - return [PigeonQueryParameters fromList:[self readValue]]; + return [PigeonPipelineResult fromList:[self readValue]]; case 138: - return [PigeonQuerySnapshot fromList:[self readValue]]; + return [PigeonPipelineSnapshot fromList:[self readValue]]; case 139: - return [PigeonSnapshotMetadata fromList:[self readValue]]; + return [PigeonQueryParameters fromList:[self readValue]]; case 140: + return [PigeonQuerySnapshot fromList:[self readValue]]; + case 141: + return [PigeonSnapshotMetadata fromList:[self readValue]]; + case 142: return [PigeonTransactionCommand fromList:[self readValue]]; default: return [super readValueOfType:type]; @@ -686,7 +759,7 @@ - (nullable id)readValueOfType:(UInt8)type { } @end -@interface FirebaseFirestoreHostApiCodecWriter : FLTFirebaseFirestoreWriter +@interface FirebaseFirestoreHostApiCodecWriter : FlutterStandardWriter @end @implementation FirebaseFirestoreHostApiCodecWriter - (void)writeValue:(id)value { @@ -717,18 +790,24 @@ - (void)writeValue:(id)value { } else if ([value isKindOfClass:[PigeonGetOptions class]]) { [self writeByte:136]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[PigeonQueryParameters class]]) { + } else if ([value isKindOfClass:[PigeonPipelineResult class]]) { [self writeByte:137]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[PigeonQuerySnapshot class]]) { + } else if ([value isKindOfClass:[PigeonPipelineSnapshot class]]) { [self writeByte:138]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[PigeonSnapshotMetadata class]]) { + } else if ([value isKindOfClass:[PigeonQueryParameters class]]) { [self writeByte:139]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[PigeonTransactionCommand class]]) { + } else if ([value isKindOfClass:[PigeonQuerySnapshot class]]) { [self writeByte:140]; [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[PigeonSnapshotMetadata class]]) { + [self writeByte:141]; + [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[PigeonTransactionCommand class]]) { + [self writeByte:142]; + [self writeValue:[value toList]]; } else { [super writeValue:value]; } @@ -1379,4 +1458,32 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, [channel setMessageHandler:nil]; } } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.cloud_firestore_platform_interface." + @"FirebaseFirestoreHostApi.executePipeline" + binaryMessenger:binaryMessenger + codec:FirebaseFirestoreHostApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(executePipelineApp:stages:options:completion:)], + @"FirebaseFirestoreHostApi api (%@) doesn't respond to " + @"@selector(executePipelineApp:stages:options:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + FirestorePigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); + NSArray *> *arg_stages = GetNullableObjectAtIndex(args, 1); + NSDictionary *arg_options = GetNullableObjectAtIndex(args, 2); + [api executePipelineApp:arg_app + stages:arg_stages + options:arg_options + completion:^(PigeonPipelineSnapshot *_Nullable output, + FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } } diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h index 2eabaeaef25f..1f43fa87bf89 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h @@ -165,6 +165,8 @@ typedef NS_ENUM(NSUInteger, AggregateType) { @class PigeonDocumentSnapshot; @class PigeonDocumentChange; @class PigeonQuerySnapshot; +@class PigeonPipelineResult; +@class PigeonPipelineSnapshot; @class PigeonGetOptions; @class PigeonDocumentOption; @class PigeonTransactionCommand; @@ -229,7 +231,7 @@ typedef NS_ENUM(NSUInteger, AggregateType) { @property(nonatomic, assign) DocumentChangeType type; @property(nonatomic, strong) PigeonDocumentSnapshot *document; @property(nonatomic, strong) NSNumber *oldIndex; -@property(nonatomic, strong) NSNumber *index; +@property(nonatomic, strong) NSNumber *newIndex; @end @interface PigeonQuerySnapshot : NSObject @@ -243,6 +245,26 @@ typedef NS_ENUM(NSUInteger, AggregateType) { @property(nonatomic, strong) PigeonSnapshotMetadata *metadata; @end +@interface PigeonPipelineResult : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithDocumentPath:(NSString *)documentPath + createTime:(NSNumber *)createTime + updateTime:(NSNumber *)updateTime; +@property(nonatomic, copy) NSString *documentPath; +@property(nonatomic, strong) NSNumber *createTime; +@property(nonatomic, strong) NSNumber *updateTime; +@end + +@interface PigeonPipelineSnapshot : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithResults:(NSArray *)results + executionTime:(NSNumber *)executionTime; +@property(nonatomic, strong) NSArray *results; +@property(nonatomic, strong) NSNumber *executionTime; +@end + @interface PigeonGetOptions : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; @@ -416,6 +438,11 @@ NSObject *FirebaseFirestoreHostApiGetCodec(void); - (void)persistenceCacheIndexManagerRequestApp:(FirestorePigeonFirebaseApp *)app request:(PersistenceCacheIndexManagerRequest)request completion:(void (^)(FlutterError *_Nullable))completion; +- (void)executePipelineApp:(FirestorePigeonFirebaseApp *)app + stages:(NSArray *> *)stages + options:(nullable NSDictionary *)options + completion:(void (^)(PigeonPipelineSnapshot *_Nullable, + FlutterError *_Nullable))completion; @end extern void FirebaseFirestoreHostApiSetup(id binaryMessenger, diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart b/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart index 3e583c3f3331..32fe4f3e16db 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart @@ -354,6 +354,7 @@ class FirebaseFirestore extends FirebasePluginPlatform { /// .limit(10) /// .execute(); /// ``` + // ignore: use_to_and_as_if_applicable PipelineSource pipeline() { return PipelineSource._(this); } diff --git a/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp b/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp index bb9f58d5775d..a70d4a7b95d5 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp +++ b/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp @@ -397,6 +397,88 @@ PigeonQuerySnapshot PigeonQuerySnapshot::FromEncodableList( return decoded; } +// PigeonPipelineResult + +PigeonPipelineResult::PigeonPipelineResult(const std::string& document_path, + int64_t create_time, + int64_t update_time) + : document_path_(document_path), + create_time_(create_time), + update_time_(update_time) {} + +const std::string& PigeonPipelineResult::document_path() const { + return document_path_; +} + +void PigeonPipelineResult::set_document_path(std::string_view value_arg) { + document_path_ = value_arg; +} + +int64_t PigeonPipelineResult::create_time() const { return create_time_; } + +void PigeonPipelineResult::set_create_time(int64_t value_arg) { + create_time_ = value_arg; +} + +int64_t PigeonPipelineResult::update_time() const { return update_time_; } + +void PigeonPipelineResult::set_update_time(int64_t value_arg) { + update_time_ = value_arg; +} + +EncodableList PigeonPipelineResult::ToEncodableList() const { + EncodableList list; + list.reserve(3); + list.push_back(EncodableValue(document_path_)); + list.push_back(EncodableValue(create_time_)); + list.push_back(EncodableValue(update_time_)); + return list; +} + +PigeonPipelineResult PigeonPipelineResult::FromEncodableList( + const EncodableList& list) { + PigeonPipelineResult decoded(std::get(list[0]), + list[1].LongValue(), list[2].LongValue()); + return decoded; +} + +// PigeonPipelineSnapshot + +PigeonPipelineSnapshot::PigeonPipelineSnapshot(const EncodableList& results, + int64_t execution_time) + : results_(results), execution_time_(execution_time) {} + +const EncodableList& PigeonPipelineSnapshot::results() const { + return results_; +} + +void PigeonPipelineSnapshot::set_results(const EncodableList& value_arg) { + results_ = value_arg; +} + +int64_t PigeonPipelineSnapshot::execution_time() const { + return execution_time_; +} + +void PigeonPipelineSnapshot::set_execution_time(int64_t value_arg) { + execution_time_ = value_arg; +} + +EncodableList PigeonPipelineSnapshot::ToEncodableList() const { + EncodableList list; + list.reserve(2); + list.push_back(EncodableValue(results_)); + list.push_back(EncodableValue(execution_time_)); + return list; +} + +PigeonPipelineSnapshot PigeonPipelineSnapshot::FromEncodableList( + const EncodableList& list) { + PigeonPipelineSnapshot decoded(std::get(list[0]), + list[1].LongValue()); + return decoded; +} + // PigeonGetOptions PigeonGetOptions::PigeonGetOptions( @@ -1034,20 +1116,25 @@ EncodableValue FirebaseFirestoreHostApiCodecSerializer::ReadValueOfType( return CustomEncodableValue(PigeonGetOptions::FromEncodableList( std::get(ReadValue(stream)))); case 137: - return CustomEncodableValue(PigeonQueryParameters::FromEncodableList( + return CustomEncodableValue(PigeonPipelineResult::FromEncodableList( std::get(ReadValue(stream)))); case 138: - return CustomEncodableValue(PigeonQuerySnapshot::FromEncodableList( + return CustomEncodableValue(PigeonPipelineSnapshot::FromEncodableList( std::get(ReadValue(stream)))); case 139: - return CustomEncodableValue(PigeonSnapshotMetadata::FromEncodableList( + return CustomEncodableValue(PigeonQueryParameters::FromEncodableList( std::get(ReadValue(stream)))); case 140: + return CustomEncodableValue(PigeonQuerySnapshot::FromEncodableList( + std::get(ReadValue(stream)))); + case 141: + return CustomEncodableValue(PigeonSnapshotMetadata::FromEncodableList( + std::get(ReadValue(stream)))); + case 142: return CustomEncodableValue(PigeonTransactionCommand::FromEncodableList( std::get(ReadValue(stream)))); default: - return cloud_firestore_windows::FirestoreCodec::ReadValueOfType(type, - stream); + return flutter::StandardCodecSerializer::ReadValueOfType(type, stream); } } @@ -1127,8 +1214,24 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( stream); return; } - if (custom_value->type() == typeid(PigeonQueryParameters)) { + if (custom_value->type() == typeid(PigeonPipelineResult)) { stream->WriteByte(137); + WriteValue( + EncodableValue(std::any_cast(*custom_value) + .ToEncodableList()), + stream); + return; + } + if (custom_value->type() == typeid(PigeonPipelineSnapshot)) { + stream->WriteByte(138); + WriteValue( + EncodableValue(std::any_cast(*custom_value) + .ToEncodableList()), + stream); + return; + } + if (custom_value->type() == typeid(PigeonQueryParameters)) { + stream->WriteByte(139); WriteValue( EncodableValue(std::any_cast(*custom_value) .ToEncodableList()), @@ -1136,7 +1239,7 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(PigeonQuerySnapshot)) { - stream->WriteByte(138); + stream->WriteByte(140); WriteValue( EncodableValue(std::any_cast(*custom_value) .ToEncodableList()), @@ -1144,7 +1247,7 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(PigeonSnapshotMetadata)) { - stream->WriteByte(139); + stream->WriteByte(141); WriteValue( EncodableValue(std::any_cast(*custom_value) .ToEncodableList()), @@ -1152,7 +1255,7 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(PigeonTransactionCommand)) { - stream->WriteByte(140); + stream->WriteByte(142); WriteValue( EncodableValue(std::any_cast(*custom_value) .ToEncodableList()), @@ -1160,7 +1263,7 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( return; } } - cloud_firestore_windows::FirestoreCodec::WriteValue(value, stream); + flutter::StandardCodecSerializer::WriteValue(value, stream); } /// The codec used by FirebaseFirestoreHostApi. @@ -2290,8 +2393,8 @@ void FirebaseFirestoreHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, reply(WrapError("request_arg unexpectedly null.")); return; } - const PersistenceCacheIndexManagerRequestEnum& request_arg = - (PersistenceCacheIndexManagerRequestEnum) + const PersistenceCacheIndexManagerRequest& request_arg = + (PersistenceCacheIndexManagerRequest) encodable_request_arg.LongValue(); api->PersistenceCacheIndexManagerRequest( app_arg, request_arg, @@ -2312,6 +2415,56 @@ void FirebaseFirestoreHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, channel->SetMessageHandler(nullptr); } } + { + auto channel = std::make_unique>( + binary_messenger, + "dev.flutter.pigeon.cloud_firestore_platform_interface." + "FirebaseFirestoreHostApi.executePipeline", + &GetCodec()); + if (api != nullptr) { + channel->SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_stages_arg = args.at(1); + if (encodable_stages_arg.IsNull()) { + reply(WrapError("stages_arg unexpectedly null.")); + return; + } + const auto& stages_arg = + std::get(encodable_stages_arg); + const auto& encodable_options_arg = args.at(2); + const auto* options_arg = + std::get_if(&encodable_options_arg); + api->ExecutePipeline( + app_arg, stages_arg, options_arg, + [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back( + CustomEncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel->SetMessageHandler(nullptr); + } + } } EncodableValue FirebaseFirestoreHostApi::WrapError( diff --git a/packages/cloud_firestore/cloud_firestore/windows/messages.g.h b/packages/cloud_firestore/cloud_firestore/windows/messages.g.h index 8aecb887facc..40e4369d79d8 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/messages.g.h +++ b/packages/cloud_firestore/cloud_firestore/windows/messages.g.h @@ -15,8 +15,6 @@ #include #include -#include "firestore_codec.h" - namespace cloud_firestore_windows { // Generated class from Pigeon. @@ -136,7 +134,7 @@ enum class AggregateSource { // [PersistenceCacheIndexManagerRequest] represents the request types for the // persistence cache index manager. -enum class PersistenceCacheIndexManagerRequestEnum { +enum class PersistenceCacheIndexManagerRequest { enableIndexAutoCreation = 0, disableIndexAutoCreation = 1, deleteAllIndexes = 2 @@ -239,11 +237,10 @@ class PigeonSnapshotMetadata { bool is_from_cache() const; void set_is_from_cache(bool value_arg); + private: static PigeonSnapshotMetadata FromEncodableList( const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; - - private: friend class PigeonDocumentSnapshot; friend class PigeonQuerySnapshot; friend class FirebaseFirestoreHostApi; @@ -274,11 +271,10 @@ class PigeonDocumentSnapshot { const PigeonSnapshotMetadata& metadata() const; void set_metadata(const PigeonSnapshotMetadata& value_arg); + private: static PigeonDocumentSnapshot FromEncodableList( const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; - - private: friend class PigeonDocumentChange; friend class FirebaseFirestoreHostApi; friend class FirebaseFirestoreHostApiCodecSerializer; @@ -307,11 +303,10 @@ class PigeonDocumentChange { int64_t new_index() const; void set_new_index(int64_t value_arg); + private: static PigeonDocumentChange FromEncodableList( const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; - - private: friend class FirebaseFirestoreHostApi; friend class FirebaseFirestoreHostApiCodecSerializer; DocumentChangeType type_; @@ -348,6 +343,56 @@ class PigeonQuerySnapshot { PigeonSnapshotMetadata metadata_; }; +// Generated class from Pigeon that represents data sent in messages. +class PigeonPipelineResult { + public: + // Constructs an object setting all fields. + explicit PigeonPipelineResult(const std::string& document_path, + int64_t create_time, int64_t update_time); + + const std::string& document_path() const; + void set_document_path(std::string_view value_arg); + + int64_t create_time() const; + void set_create_time(int64_t value_arg); + + int64_t update_time() const; + void set_update_time(int64_t value_arg); + + private: + static PigeonPipelineResult FromEncodableList( + const flutter::EncodableList& list); + flutter::EncodableList ToEncodableList() const; + friend class FirebaseFirestoreHostApi; + friend class FirebaseFirestoreHostApiCodecSerializer; + std::string document_path_; + int64_t create_time_; + int64_t update_time_; +}; + +// Generated class from Pigeon that represents data sent in messages. +class PigeonPipelineSnapshot { + public: + // Constructs an object setting all fields. + explicit PigeonPipelineSnapshot(const flutter::EncodableList& results, + int64_t execution_time); + + const flutter::EncodableList& results() const; + void set_results(const flutter::EncodableList& value_arg); + + int64_t execution_time() const; + void set_execution_time(int64_t value_arg); + + private: + static PigeonPipelineSnapshot FromEncodableList( + const flutter::EncodableList& list); + flutter::EncodableList ToEncodableList() const; + friend class FirebaseFirestoreHostApi; + friend class FirebaseFirestoreHostApiCodecSerializer; + flutter::EncodableList results_; + int64_t execution_time_; +}; + // Generated class from Pigeon that represents data sent in messages. class PigeonGetOptions { public: @@ -613,7 +658,7 @@ class AggregateQueryResponse { }; class FirebaseFirestoreHostApiCodecSerializer - : public cloud_firestore_windows::FirestoreCodec { + : public flutter::StandardCodecSerializer { public: FirebaseFirestoreHostApiCodecSerializer(); inline static FirebaseFirestoreHostApiCodecSerializer& GetInstance() { @@ -724,8 +769,13 @@ class FirebaseFirestoreHostApi { std::function reply)> result) = 0; virtual void PersistenceCacheIndexManagerRequest( const FirestorePigeonFirebaseApp& app, - const PersistenceCacheIndexManagerRequestEnum& request, + const PersistenceCacheIndexManagerRequest& request, std::function reply)> result) = 0; + virtual void ExecutePipeline( + const FirestorePigeonFirebaseApp& app, + const flutter::EncodableList& stages, + const flutter::EncodableMap* options, + std::function reply)> result) = 0; // The codec used by FirebaseFirestoreHostApi. static const flutter::StandardMessageCodec& GetCodec(); diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_firestore.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_firestore.dart index 73aacc45f2bb..889ea4c5351b 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_firestore.dart @@ -364,21 +364,25 @@ class MethodChannelFirebaseFirestore extends FirebaseFirestorePlatform { Map? options, }) async { try { - final MethodChannel channel = const MethodChannel( - 'plugins.flutter.io/firebase_firestore', + // Convert stages to Pigeon format (List?>) + final List?> pigeonStages = stages.map((stage) { + return stage.map(MapEntry.new); + }).toList(); + + // Convert options to Pigeon format (Map?) + final Map? pigeonOptions = options?.map( + MapEntry.new, ); - final result = await channel.invokeMethod>( - 'Pipeline#execute', - { - 'app': pigeonApp, - 'stages': stages, - if (options != null) 'options': options, - }, + + final PigeonPipelineSnapshot result = await pigeonChannel.executePipeline( + pigeonApp, + pigeonStages, + pigeonOptions, ); - return MethodChannelPipelineSnapshot(this, pigeonApp, result!); - } on PlatformException catch (e, stack) { - throw convertPlatformException(e, stack); + return MethodChannelPipelineSnapshot(this, pigeonApp, result); + } catch (e, stack) { + convertPlatformException(e, stack); } } } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart index 16cde78be465..51ff99ffcff9 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart @@ -12,22 +12,23 @@ class MethodChannelPipelineSnapshot extends PipelineSnapshotPlatform { final List _results; final DateTime _executionTime; - /// Creates a [MethodChannelPipelineSnapshot] from the given [data] + /// Creates a [MethodChannelPipelineSnapshot] from the given [pigeonSnapshot] MethodChannelPipelineSnapshot( FirebaseFirestorePlatform firestore, FirestorePigeonFirebaseApp pigeonApp, - Map data, - ) : _results = (data['results'] as List) + PigeonPipelineSnapshot pigeonSnapshot, + ) : _results = (pigeonSnapshot.results ?? []) + .whereType() .map((result) => MethodChannelPipelineResult( firestore, pigeonApp, - result['document'] as String, - DateTime.fromMillisecondsSinceEpoch(result['createTime']), - DateTime.fromMillisecondsSinceEpoch(result['updateTime']), + result.documentPath, + DateTime.fromMillisecondsSinceEpoch(result.createTime), + DateTime.fromMillisecondsSinceEpoch(result.updateTime), )) .toList(), _executionTime = DateTime.fromMillisecondsSinceEpoch( - data['executionTime'] as int, + pigeonSnapshot.executionTime, ), super(); diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart index 85af6edc5260..0ea2ee91ccd5 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart @@ -8,7 +8,6 @@ import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; -import 'package:cloud_firestore_platform_interface/src/method_channel/utils/firestore_message_codec.dart'; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; @@ -301,6 +300,63 @@ class PigeonQuerySnapshot { } } +class PigeonPipelineResult { + PigeonPipelineResult({ + required this.documentPath, + required this.createTime, + required this.updateTime, + }); + + String documentPath; + + int createTime; + + int updateTime; + + Object encode() { + return [ + documentPath, + createTime, + updateTime, + ]; + } + + static PigeonPipelineResult decode(Object result) { + result as List; + return PigeonPipelineResult( + documentPath: result[0]! as String, + createTime: result[1]! as int, + updateTime: result[2]! as int, + ); + } +} + +class PigeonPipelineSnapshot { + PigeonPipelineSnapshot({ + required this.results, + required this.executionTime, + }); + + List results; + + int executionTime; + + Object encode() { + return [ + results, + executionTime, + ]; + } + + static PigeonPipelineSnapshot decode(Object result) { + result as List; + return PigeonPipelineSnapshot( + results: (result[0] as List?)!.cast(), + executionTime: result[1]! as int, + ); + } +} + class PigeonGetOptions { PigeonGetOptions({ required this.source, @@ -555,7 +611,7 @@ class AggregateQueryResponse { } } -class _FirebaseFirestoreHostApiCodec extends FirestoreMessageCodec { +class _FirebaseFirestoreHostApiCodec extends StandardMessageCodec { const _FirebaseFirestoreHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { @@ -586,18 +642,24 @@ class _FirebaseFirestoreHostApiCodec extends FirestoreMessageCodec { } else if (value is PigeonGetOptions) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is PigeonQueryParameters) { + } else if (value is PigeonPipelineResult) { buffer.putUint8(137); writeValue(buffer, value.encode()); - } else if (value is PigeonQuerySnapshot) { + } else if (value is PigeonPipelineSnapshot) { buffer.putUint8(138); writeValue(buffer, value.encode()); - } else if (value is PigeonSnapshotMetadata) { + } else if (value is PigeonQueryParameters) { buffer.putUint8(139); writeValue(buffer, value.encode()); - } else if (value is PigeonTransactionCommand) { + } else if (value is PigeonQuerySnapshot) { buffer.putUint8(140); writeValue(buffer, value.encode()); + } else if (value is PigeonSnapshotMetadata) { + buffer.putUint8(141); + writeValue(buffer, value.encode()); + } else if (value is PigeonTransactionCommand) { + buffer.putUint8(142); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -625,12 +687,16 @@ class _FirebaseFirestoreHostApiCodec extends FirestoreMessageCodec { case 136: return PigeonGetOptions.decode(readValue(buffer)!); case 137: - return PigeonQueryParameters.decode(readValue(buffer)!); + return PigeonPipelineResult.decode(readValue(buffer)!); case 138: - return PigeonQuerySnapshot.decode(readValue(buffer)!); + return PigeonPipelineSnapshot.decode(readValue(buffer)!); case 139: - return PigeonSnapshotMetadata.decode(readValue(buffer)!); + return PigeonQueryParameters.decode(readValue(buffer)!); case 140: + return PigeonQuerySnapshot.decode(readValue(buffer)!); + case 141: + return PigeonSnapshotMetadata.decode(readValue(buffer)!); + case 142: return PigeonTransactionCommand.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -649,14 +715,11 @@ class FirebaseFirestoreHostApi { static const MessageCodec codec = _FirebaseFirestoreHostApiCodec(); Future loadBundle( - FirestorePigeonFirebaseApp arg_app, - Uint8List arg_bundle, - ) async { + FirestorePigeonFirebaseApp arg_app, Uint8List arg_bundle) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app, arg_bundle]) as List?; if (replyList == null) { @@ -680,16 +743,12 @@ class FirebaseFirestoreHostApi { } } - Future namedQueryGet( - FirestorePigeonFirebaseApp arg_app, - String arg_name, - PigeonGetOptions arg_options, - ) async { + Future namedQueryGet(FirestorePigeonFirebaseApp arg_app, + String arg_name, PigeonGetOptions arg_options) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_app, arg_name, arg_options]) as List?; if (replyList == null) { @@ -715,10 +774,9 @@ class FirebaseFirestoreHostApi { Future clearPersistence(FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -739,10 +797,9 @@ class FirebaseFirestoreHostApi { Future disableNetwork(FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -763,10 +820,9 @@ class FirebaseFirestoreHostApi { Future enableNetwork(FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -787,10 +843,9 @@ class FirebaseFirestoreHostApi { Future terminate(FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -811,10 +866,9 @@ class FirebaseFirestoreHostApi { Future waitForPendingWrites(FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -834,14 +888,11 @@ class FirebaseFirestoreHostApi { } Future setIndexConfiguration( - FirestorePigeonFirebaseApp arg_app, - String arg_indexConfiguration, - ) async { + FirestorePigeonFirebaseApp arg_app, String arg_indexConfiguration) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_app, arg_indexConfiguration]) as List?; if (replyList == null) { @@ -862,10 +913,9 @@ class FirebaseFirestoreHostApi { Future setLoggingEnabled(bool arg_loggingEnabled) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_loggingEnabled]) as List?; if (replyList == null) { @@ -885,13 +935,11 @@ class FirebaseFirestoreHostApi { } Future snapshotsInSyncSetup( - FirestorePigeonFirebaseApp arg_app, - ) async { + FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -915,16 +963,12 @@ class FirebaseFirestoreHostApi { } } - Future transactionCreate( - FirestorePigeonFirebaseApp arg_app, - int arg_timeout, - int arg_maxAttempts, - ) async { + Future transactionCreate(FirestorePigeonFirebaseApp arg_app, + int arg_timeout, int arg_maxAttempts) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app, arg_timeout, arg_maxAttempts]) as List?; @@ -950,18 +994,16 @@ class FirebaseFirestoreHostApi { } Future transactionStoreResult( - String arg_transactionId, - PigeonTransactionResult arg_resultType, - List? arg_commands, - ) async { + String arg_transactionId, + PigeonTransactionResult arg_resultType, + List? arg_commands) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send( - [arg_transactionId, arg_resultType.index, arg_commands], - ) as List?; + [arg_transactionId, arg_resultType.index, arg_commands]) + as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -979,15 +1021,13 @@ class FirebaseFirestoreHostApi { } Future transactionGet( - FirestorePigeonFirebaseApp arg_app, - String arg_transactionId, - String arg_path, - ) async { + FirestorePigeonFirebaseApp arg_app, + String arg_transactionId, + String arg_path) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app, arg_transactionId, arg_path]) as List?; @@ -1012,15 +1052,12 @@ class FirebaseFirestoreHostApi { } } - Future documentReferenceSet( - FirestorePigeonFirebaseApp arg_app, - DocumentReferenceRequest arg_request, - ) async { + Future documentReferenceSet(FirestorePigeonFirebaseApp arg_app, + DocumentReferenceRequest arg_request) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app, arg_request]) as List?; if (replyList == null) { @@ -1039,15 +1076,12 @@ class FirebaseFirestoreHostApi { } } - Future documentReferenceUpdate( - FirestorePigeonFirebaseApp arg_app, - DocumentReferenceRequest arg_request, - ) async { + Future documentReferenceUpdate(FirestorePigeonFirebaseApp arg_app, + DocumentReferenceRequest arg_request) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app, arg_request]) as List?; if (replyList == null) { @@ -1067,14 +1101,12 @@ class FirebaseFirestoreHostApi { } Future documentReferenceGet( - FirestorePigeonFirebaseApp arg_app, - DocumentReferenceRequest arg_request, - ) async { + FirestorePigeonFirebaseApp arg_app, + DocumentReferenceRequest arg_request) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app, arg_request]) as List?; if (replyList == null) { @@ -1098,15 +1130,12 @@ class FirebaseFirestoreHostApi { } } - Future documentReferenceDelete( - FirestorePigeonFirebaseApp arg_app, - DocumentReferenceRequest arg_request, - ) async { + Future documentReferenceDelete(FirestorePigeonFirebaseApp arg_app, + DocumentReferenceRequest arg_request) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app, arg_request]) as List?; if (replyList == null) { @@ -1126,23 +1155,21 @@ class FirebaseFirestoreHostApi { } Future queryGet( - FirestorePigeonFirebaseApp arg_app, - String arg_path, - bool arg_isCollectionGroup, - PigeonQueryParameters arg_parameters, - PigeonGetOptions arg_options, - ) async { + FirestorePigeonFirebaseApp arg_app, + String arg_path, + bool arg_isCollectionGroup, + PigeonQueryParameters arg_parameters, + PigeonGetOptions arg_options) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([ arg_app, arg_path, arg_isCollectionGroup, arg_parameters, - arg_options, + arg_options ]) as List?; if (replyList == null) { throw PlatformException( @@ -1166,25 +1193,23 @@ class FirebaseFirestoreHostApi { } Future> aggregateQuery( - FirestorePigeonFirebaseApp arg_app, - String arg_path, - PigeonQueryParameters arg_parameters, - AggregateSource arg_source, - List arg_queries, - bool arg_isCollectionGroup, - ) async { + FirestorePigeonFirebaseApp arg_app, + String arg_path, + PigeonQueryParameters arg_parameters, + AggregateSource arg_source, + List arg_queries, + bool arg_isCollectionGroup) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([ arg_app, arg_path, arg_parameters, arg_source.index, arg_queries, - arg_isCollectionGroup, + arg_isCollectionGroup ]) as List?; if (replyList == null) { throw PlatformException( @@ -1207,15 +1232,12 @@ class FirebaseFirestoreHostApi { } } - Future writeBatchCommit( - FirestorePigeonFirebaseApp arg_app, - List arg_writes, - ) async { + Future writeBatchCommit(FirestorePigeonFirebaseApp arg_app, + List arg_writes) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app, arg_writes]) as List?; if (replyList == null) { @@ -1235,19 +1257,17 @@ class FirebaseFirestoreHostApi { } Future querySnapshot( - FirestorePigeonFirebaseApp arg_app, - String arg_path, - bool arg_isCollectionGroup, - PigeonQueryParameters arg_parameters, - PigeonGetOptions arg_options, - bool arg_includeMetadataChanges, - ListenSource arg_source, - ) async { + FirestorePigeonFirebaseApp arg_app, + String arg_path, + bool arg_isCollectionGroup, + PigeonQueryParameters arg_parameters, + PigeonGetOptions arg_options, + bool arg_includeMetadataChanges, + ListenSource arg_source) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([ arg_app, arg_path, @@ -1255,7 +1275,7 @@ class FirebaseFirestoreHostApi { arg_parameters, arg_options, arg_includeMetadataChanges, - arg_source.index, + arg_source.index ]) as List?; if (replyList == null) { throw PlatformException( @@ -1279,21 +1299,19 @@ class FirebaseFirestoreHostApi { } Future documentReferenceSnapshot( - FirestorePigeonFirebaseApp arg_app, - DocumentReferenceRequest arg_parameters, - bool arg_includeMetadataChanges, - ListenSource arg_source, - ) async { + FirestorePigeonFirebaseApp arg_app, + DocumentReferenceRequest arg_parameters, + bool arg_includeMetadataChanges, + ListenSource arg_source) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([ arg_app, arg_parameters, arg_includeMetadataChanges, - arg_source.index, + arg_source.index ]) as List?; if (replyList == null) { throw PlatformException( @@ -1317,14 +1335,12 @@ class FirebaseFirestoreHostApi { } Future persistenceCacheIndexManagerRequest( - FirestorePigeonFirebaseApp arg_app, - PersistenceCacheIndexManagerRequest arg_request, - ) async { + FirestorePigeonFirebaseApp arg_app, + PersistenceCacheIndexManagerRequest arg_request) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_app, arg_request.index]) as List?; if (replyList == null) { @@ -1342,4 +1358,35 @@ class FirebaseFirestoreHostApi { return; } } + + Future executePipeline( + FirestorePigeonFirebaseApp arg_app, + List?> arg_stages, + Map? arg_options) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel + .send([arg_app, arg_stages, arg_options]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as PigeonPipelineSnapshot?)!; + } + } } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart index 19a5ddfdc4ee..33b146653a45 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart @@ -118,6 +118,28 @@ class PigeonQuerySnapshot { final PigeonSnapshotMetadata metadata; } +class PigeonPipelineResult { + const PigeonPipelineResult({ + required this.documentPath, + required this.createTime, + required this.updateTime, + }); + + final String documentPath; + final int createTime; // Timestamp in milliseconds since epoch + final int updateTime; // Timestamp in milliseconds since epoch +} + +class PigeonPipelineSnapshot { + const PigeonPipelineSnapshot({ + required this.results, + required this.executionTime, + }); + + final List results; + final int executionTime; // Timestamp in milliseconds since epoch +} + /// An enumeration of firestore source types. enum Source { /// Causes Firestore to try to retrieve an up-to-date (server-retrieved) snapshot, but fall back to @@ -442,4 +464,11 @@ abstract class FirebaseFirestoreHostApi { FirestorePigeonFirebaseApp app, PersistenceCacheIndexManagerRequest request, ); + + @async + PigeonPipelineSnapshot executePipeline( + FirestorePigeonFirebaseApp app, + List?> stages, + Map? options, + ); } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/test/pigeon/test_api.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/test/pigeon/test_api.dart index 32d57ebdcb48..67978a0a8f2c 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/test/pigeon/test_api.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/test/pigeon/test_api.dart @@ -6,13 +6,13 @@ // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import // ignore_for_file: avoid_relative_lib_imports import 'dart:async'; -import 'dart:typed_data' show Uint8List; - -import 'package:cloud_firestore_platform_interface/src/pigeon/messages.pigeon.dart'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:cloud_firestore_platform_interface/src/pigeon/messages.pigeon.dart'; + class _TestFirebaseFirestoreHostApiCodec extends StandardMessageCodec { const _TestFirebaseFirestoreHostApiCodec(); @override @@ -44,18 +44,24 @@ class _TestFirebaseFirestoreHostApiCodec extends StandardMessageCodec { } else if (value is PigeonGetOptions) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is PigeonQueryParameters) { + } else if (value is PigeonPipelineResult) { buffer.putUint8(137); writeValue(buffer, value.encode()); - } else if (value is PigeonQuerySnapshot) { + } else if (value is PigeonPipelineSnapshot) { buffer.putUint8(138); writeValue(buffer, value.encode()); - } else if (value is PigeonSnapshotMetadata) { + } else if (value is PigeonQueryParameters) { buffer.putUint8(139); writeValue(buffer, value.encode()); - } else if (value is PigeonTransactionCommand) { + } else if (value is PigeonQuerySnapshot) { buffer.putUint8(140); writeValue(buffer, value.encode()); + } else if (value is PigeonSnapshotMetadata) { + buffer.putUint8(141); + writeValue(buffer, value.encode()); + } else if (value is PigeonTransactionCommand) { + buffer.putUint8(142); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -63,22 +69,40 @@ class _TestFirebaseFirestoreHostApiCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { - return switch (type) { - 128 => AggregateQuery.decode(readValue(buffer)!), - 129 => AggregateQueryResponse.decode(readValue(buffer)!), - 130 => DocumentReferenceRequest.decode(readValue(buffer)!), - 131 => FirestorePigeonFirebaseApp.decode(readValue(buffer)!), - 132 => PigeonDocumentChange.decode(readValue(buffer)!), - 133 => PigeonDocumentOption.decode(readValue(buffer)!), - 134 => PigeonDocumentSnapshot.decode(readValue(buffer)!), - 135 => PigeonFirebaseSettings.decode(readValue(buffer)!), - 136 => PigeonGetOptions.decode(readValue(buffer)!), - 137 => PigeonQueryParameters.decode(readValue(buffer)!), - 138 => PigeonQuerySnapshot.decode(readValue(buffer)!), - 139 => PigeonSnapshotMetadata.decode(readValue(buffer)!), - 140 => PigeonTransactionCommand.decode(readValue(buffer)!), - _ => super.readValueOfType(type, buffer) - }; + switch (type) { + case 128: + return AggregateQuery.decode(readValue(buffer)!); + case 129: + return AggregateQueryResponse.decode(readValue(buffer)!); + case 130: + return DocumentReferenceRequest.decode(readValue(buffer)!); + case 131: + return FirestorePigeonFirebaseApp.decode(readValue(buffer)!); + case 132: + return PigeonDocumentChange.decode(readValue(buffer)!); + case 133: + return PigeonDocumentOption.decode(readValue(buffer)!); + case 134: + return PigeonDocumentSnapshot.decode(readValue(buffer)!); + case 135: + return PigeonFirebaseSettings.decode(readValue(buffer)!); + case 136: + return PigeonGetOptions.decode(readValue(buffer)!); + case 137: + return PigeonPipelineResult.decode(readValue(buffer)!); + case 138: + return PigeonPipelineSnapshot.decode(readValue(buffer)!); + case 139: + return PigeonQueryParameters.decode(readValue(buffer)!); + case 140: + return PigeonQuerySnapshot.decode(readValue(buffer)!); + case 141: + return PigeonSnapshotMetadata.decode(readValue(buffer)!); + case 142: + return PigeonTransactionCommand.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } } } @@ -91,10 +115,7 @@ abstract class TestFirebaseFirestoreHostApi { Future loadBundle(FirestorePigeonFirebaseApp app, Uint8List bundle); Future namedQueryGet( - FirestorePigeonFirebaseApp app, - String name, - PigeonGetOptions options, - ); + FirestorePigeonFirebaseApp app, String name, PigeonGetOptions options); Future clearPersistence(FirestorePigeonFirebaseApp app); @@ -107,106 +128,82 @@ abstract class TestFirebaseFirestoreHostApi { Future waitForPendingWrites(FirestorePigeonFirebaseApp app); Future setIndexConfiguration( - FirestorePigeonFirebaseApp app, - String indexConfiguration, - ); + FirestorePigeonFirebaseApp app, String indexConfiguration); Future setLoggingEnabled(bool loggingEnabled); Future snapshotsInSyncSetup(FirestorePigeonFirebaseApp app); Future transactionCreate( - FirestorePigeonFirebaseApp app, - int timeout, - int maxAttempts, - ); + FirestorePigeonFirebaseApp app, int timeout, int maxAttempts); Future transactionStoreResult( - String transactionId, - PigeonTransactionResult resultType, - List? commands, - ); + String transactionId, + PigeonTransactionResult resultType, + List? commands); Future transactionGet( - FirestorePigeonFirebaseApp app, - String transactionId, - String path, - ); + FirestorePigeonFirebaseApp app, String transactionId, String path); Future documentReferenceSet( - FirestorePigeonFirebaseApp app, - DocumentReferenceRequest request, - ); + FirestorePigeonFirebaseApp app, DocumentReferenceRequest request); Future documentReferenceUpdate( - FirestorePigeonFirebaseApp app, - DocumentReferenceRequest request, - ); + FirestorePigeonFirebaseApp app, DocumentReferenceRequest request); Future documentReferenceGet( - FirestorePigeonFirebaseApp app, - DocumentReferenceRequest request, - ); + FirestorePigeonFirebaseApp app, DocumentReferenceRequest request); Future documentReferenceDelete( - FirestorePigeonFirebaseApp app, - DocumentReferenceRequest request, - ); + FirestorePigeonFirebaseApp app, DocumentReferenceRequest request); Future queryGet( - FirestorePigeonFirebaseApp app, - String path, - bool isCollectionGroup, - PigeonQueryParameters parameters, - PigeonGetOptions options, - ); + FirestorePigeonFirebaseApp app, + String path, + bool isCollectionGroup, + PigeonQueryParameters parameters, + PigeonGetOptions options); Future> aggregateQuery( - FirestorePigeonFirebaseApp app, - String path, - PigeonQueryParameters parameters, - AggregateSource source, - List queries, - bool isCollectionGroup, - ); + FirestorePigeonFirebaseApp app, + String path, + PigeonQueryParameters parameters, + AggregateSource source, + List queries, + bool isCollectionGroup); Future writeBatchCommit( - FirestorePigeonFirebaseApp app, - List writes, - ); + FirestorePigeonFirebaseApp app, List writes); Future querySnapshot( - FirestorePigeonFirebaseApp app, - String path, - bool isCollectionGroup, - PigeonQueryParameters parameters, - PigeonGetOptions options, - bool includeMetadataChanges, - ListenSource source, - ); + FirestorePigeonFirebaseApp app, + String path, + bool isCollectionGroup, + PigeonQueryParameters parameters, + PigeonGetOptions options, + bool includeMetadataChanges, + ListenSource source); Future documentReferenceSnapshot( - FirestorePigeonFirebaseApp app, - DocumentReferenceRequest parameters, - bool includeMetadataChanges, - ListenSource source, - ); + FirestorePigeonFirebaseApp app, + DocumentReferenceRequest parameters, + bool includeMetadataChanges, + ListenSource source); Future persistenceCacheIndexManagerRequest( - FirestorePigeonFirebaseApp app, - PersistenceCacheIndexManagerRequest request, - ); + FirestorePigeonFirebaseApp app, + PersistenceCacheIndexManagerRequest request); + + Future executePipeline(FirestorePigeonFirebaseApp app, + List?> stages, Map? options); - static void setup( - TestFirebaseFirestoreHostApi? api, { - BinaryMessenger? binaryMessenger, - }) { + static void setup(TestFirebaseFirestoreHostApi? api, + {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -214,22 +211,16 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null, expected non-null FirestorePigeonFirebaseApp.'); final Uint8List? arg_bundle = (args[1] as Uint8List?); - assert( - arg_bundle != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null, expected non-null Uint8List.', - ); + assert(arg_bundle != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null, expected non-null Uint8List.'); final String output = await api.loadBundle(arg_app!, arg_bundle!); return [output]; }); @@ -237,10 +228,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -248,27 +238,19 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null FirestorePigeonFirebaseApp.'); final String? arg_name = (args[1] as String?); - assert( - arg_name != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null String.', - ); + assert(arg_name != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null String.'); final PigeonGetOptions? arg_options = (args[2] as PigeonGetOptions?); - assert( - arg_options != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null PigeonGetOptions.', - ); + assert(arg_options != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null PigeonGetOptions.'); final PigeonQuerySnapshot output = await api.namedQueryGet(arg_app!, arg_name!, arg_options!); return [output]; @@ -277,10 +259,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -288,17 +269,13 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence was null, expected non-null FirestorePigeonFirebaseApp.'); await api.clearPersistence(arg_app!); return []; }); @@ -306,10 +283,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -317,17 +293,13 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork was null, expected non-null FirestorePigeonFirebaseApp.'); await api.disableNetwork(arg_app!); return []; }); @@ -335,10 +307,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -346,17 +317,13 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork was null, expected non-null FirestorePigeonFirebaseApp.'); await api.enableNetwork(arg_app!); return []; }); @@ -364,10 +331,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -375,17 +341,13 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate was null, expected non-null FirestorePigeonFirebaseApp.'); await api.terminate(arg_app!); return []; }); @@ -393,10 +355,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -404,17 +365,13 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites was null, expected non-null FirestorePigeonFirebaseApp.'); await api.waitForPendingWrites(arg_app!); return []; }); @@ -422,10 +379,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -433,22 +389,16 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null, expected non-null FirestorePigeonFirebaseApp.'); final String? arg_indexConfiguration = (args[1] as String?); - assert( - arg_indexConfiguration != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null, expected non-null String.', - ); + assert(arg_indexConfiguration != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null, expected non-null String.'); await api.setIndexConfiguration(arg_app!, arg_indexConfiguration!); return []; }); @@ -456,10 +406,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -467,16 +416,12 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled was null.'); final List args = (message as List?)!; final bool? arg_loggingEnabled = (args[0] as bool?); - assert( - arg_loggingEnabled != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled was null, expected non-null bool.', - ); + assert(arg_loggingEnabled != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled was null, expected non-null bool.'); await api.setLoggingEnabled(arg_loggingEnabled!); return []; }); @@ -484,10 +429,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -495,17 +439,13 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup was null, expected non-null FirestorePigeonFirebaseApp.'); final String output = await api.snapshotsInSyncSetup(arg_app!); return [output]; }); @@ -513,10 +453,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -524,42 +463,30 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null FirestorePigeonFirebaseApp.'); final int? arg_timeout = (args[1] as int?); - assert( - arg_timeout != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null int.', - ); + assert(arg_timeout != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null int.'); final int? arg_maxAttempts = (args[2] as int?); - assert( - arg_maxAttempts != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null int.', - ); + assert(arg_maxAttempts != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null int.'); final String output = await api.transactionCreate( - arg_app!, - arg_timeout!, - arg_maxAttempts!, - ); + arg_app!, arg_timeout!, arg_maxAttempts!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -567,40 +494,30 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null.'); final List args = (message as List?)!; final String? arg_transactionId = (args[0] as String?); - assert( - arg_transactionId != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null, expected non-null String.', - ); + assert(arg_transactionId != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null, expected non-null String.'); final PigeonTransactionResult? arg_resultType = args[1] == null ? null : PigeonTransactionResult.values[args[1]! as int]; - assert( - arg_resultType != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null, expected non-null PigeonTransactionResult.', - ); + assert(arg_resultType != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null, expected non-null PigeonTransactionResult.'); final List? arg_commands = (args[2] as List?)?.cast(); await api.transactionStoreResult( - arg_transactionId!, - arg_resultType!, - arg_commands, - ); + arg_transactionId!, arg_resultType!, arg_commands); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -608,27 +525,19 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null FirestorePigeonFirebaseApp.'); final String? arg_transactionId = (args[1] as String?); - assert( - arg_transactionId != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null String.', - ); + assert(arg_transactionId != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null String.'); final String? arg_path = (args[2] as String?); - assert( - arg_path != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null String.', - ); + assert(arg_path != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null String.'); final PigeonDocumentSnapshot output = await api.transactionGet(arg_app!, arg_transactionId!, arg_path!); return [output]; @@ -637,10 +546,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -648,23 +556,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null, expected non-null FirestorePigeonFirebaseApp.'); final DocumentReferenceRequest? arg_request = (args[1] as DocumentReferenceRequest?); - assert( - arg_request != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null, expected non-null DocumentReferenceRequest.', - ); + assert(arg_request != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null, expected non-null DocumentReferenceRequest.'); await api.documentReferenceSet(arg_app!, arg_request!); return []; }); @@ -672,10 +574,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -683,23 +584,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null, expected non-null FirestorePigeonFirebaseApp.'); final DocumentReferenceRequest? arg_request = (args[1] as DocumentReferenceRequest?); - assert( - arg_request != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null, expected non-null DocumentReferenceRequest.', - ); + assert(arg_request != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null, expected non-null DocumentReferenceRequest.'); await api.documentReferenceUpdate(arg_app!, arg_request!); return []; }); @@ -707,10 +602,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -718,23 +612,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null, expected non-null FirestorePigeonFirebaseApp.'); final DocumentReferenceRequest? arg_request = (args[1] as DocumentReferenceRequest?); - assert( - arg_request != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null, expected non-null DocumentReferenceRequest.', - ); + assert(arg_request != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null, expected non-null DocumentReferenceRequest.'); final PigeonDocumentSnapshot output = await api.documentReferenceGet(arg_app!, arg_request!); return [output]; @@ -743,10 +631,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -754,23 +641,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null, expected non-null FirestorePigeonFirebaseApp.'); final DocumentReferenceRequest? arg_request = (args[1] as DocumentReferenceRequest?); - assert( - arg_request != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null, expected non-null DocumentReferenceRequest.', - ); + assert(arg_request != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null, expected non-null DocumentReferenceRequest.'); await api.documentReferenceDelete(arg_app!, arg_request!); return []; }); @@ -778,10 +659,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -789,55 +669,37 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null FirestorePigeonFirebaseApp.'); final String? arg_path = (args[1] as String?); - assert( - arg_path != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null String.', - ); + assert(arg_path != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null String.'); final bool? arg_isCollectionGroup = (args[2] as bool?); - assert( - arg_isCollectionGroup != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null bool.', - ); + assert(arg_isCollectionGroup != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null bool.'); final PigeonQueryParameters? arg_parameters = (args[3] as PigeonQueryParameters?); - assert( - arg_parameters != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null PigeonQueryParameters.', - ); + assert(arg_parameters != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null PigeonQueryParameters.'); final PigeonGetOptions? arg_options = (args[4] as PigeonGetOptions?); - assert( - arg_options != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null PigeonGetOptions.', - ); - final PigeonQuerySnapshot output = await api.queryGet( - arg_app!, - arg_path!, - arg_isCollectionGroup!, - arg_parameters!, - arg_options!, - ); + assert(arg_options != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null PigeonGetOptions.'); + final PigeonQuerySnapshot output = await api.queryGet(arg_app!, + arg_path!, arg_isCollectionGroup!, arg_parameters!, arg_options!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -845,63 +707,47 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null FirestorePigeonFirebaseApp.'); final String? arg_path = (args[1] as String?); - assert( - arg_path != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null String.', - ); + assert(arg_path != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null String.'); final PigeonQueryParameters? arg_parameters = (args[2] as PigeonQueryParameters?); - assert( - arg_parameters != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null PigeonQueryParameters.', - ); + assert(arg_parameters != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null PigeonQueryParameters.'); final AggregateSource? arg_source = args[3] == null ? null : AggregateSource.values[args[3]! as int]; - assert( - arg_source != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null AggregateSource.', - ); + assert(arg_source != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null AggregateSource.'); final List? arg_queries = (args[4] as List?)?.cast(); - assert( - arg_queries != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null List.', - ); + assert(arg_queries != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null List.'); final bool? arg_isCollectionGroup = (args[5] as bool?); - assert( - arg_isCollectionGroup != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null bool.', - ); + assert(arg_isCollectionGroup != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null bool.'); final List output = await api.aggregateQuery( - arg_app!, - arg_path!, - arg_parameters!, - arg_source!, - arg_queries!, - arg_isCollectionGroup!, - ); + arg_app!, + arg_path!, + arg_parameters!, + arg_source!, + arg_queries!, + arg_isCollectionGroup!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -909,23 +755,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null, expected non-null FirestorePigeonFirebaseApp.'); final List? arg_writes = (args[1] as List?)?.cast(); - assert( - arg_writes != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null, expected non-null List.', - ); + assert(arg_writes != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null, expected non-null List.'); await api.writeBatchCommit(arg_app!, arg_writes!); return []; }); @@ -933,10 +773,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -944,68 +783,50 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null FirestorePigeonFirebaseApp.'); final String? arg_path = (args[1] as String?); - assert( - arg_path != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null String.', - ); + assert(arg_path != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null String.'); final bool? arg_isCollectionGroup = (args[2] as bool?); - assert( - arg_isCollectionGroup != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null bool.', - ); + assert(arg_isCollectionGroup != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null bool.'); final PigeonQueryParameters? arg_parameters = (args[3] as PigeonQueryParameters?); - assert( - arg_parameters != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null PigeonQueryParameters.', - ); + assert(arg_parameters != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null PigeonQueryParameters.'); final PigeonGetOptions? arg_options = (args[4] as PigeonGetOptions?); - assert( - arg_options != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null PigeonGetOptions.', - ); + assert(arg_options != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null PigeonGetOptions.'); final bool? arg_includeMetadataChanges = (args[5] as bool?); - assert( - arg_includeMetadataChanges != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null bool.', - ); + assert(arg_includeMetadataChanges != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null bool.'); final ListenSource? arg_source = args[6] == null ? null : ListenSource.values[args[6]! as int]; - assert( - arg_source != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null ListenSource.', - ); + assert(arg_source != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null ListenSource.'); final String output = await api.querySnapshot( - arg_app!, - arg_path!, - arg_isCollectionGroup!, - arg_parameters!, - arg_options!, - arg_includeMetadataChanges!, - arg_source!, - ); + arg_app!, + arg_path!, + arg_isCollectionGroup!, + arg_parameters!, + arg_options!, + arg_includeMetadataChanges!, + arg_source!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -1013,50 +834,35 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null FirestorePigeonFirebaseApp.'); final DocumentReferenceRequest? arg_parameters = (args[1] as DocumentReferenceRequest?); - assert( - arg_parameters != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null DocumentReferenceRequest.', - ); + assert(arg_parameters != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null DocumentReferenceRequest.'); final bool? arg_includeMetadataChanges = (args[2] as bool?); - assert( - arg_includeMetadataChanges != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null bool.', - ); + assert(arg_includeMetadataChanges != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null bool.'); final ListenSource? arg_source = args[3] == null ? null : ListenSource.values[args[3]! as int]; - assert( - arg_source != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null ListenSource.', - ); - final String output = await api.documentReferenceSnapshot( - arg_app!, - arg_parameters!, - arg_includeMetadataChanges!, - arg_source!, - ); + assert(arg_source != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null ListenSource.'); + final String output = await api.documentReferenceSnapshot(arg_app!, + arg_parameters!, arg_includeMetadataChanges!, arg_source!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -1064,29 +870,54 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null, expected non-null FirestorePigeonFirebaseApp.'); final PersistenceCacheIndexManagerRequest? arg_request = args[1] == null ? null : PersistenceCacheIndexManagerRequest.values[args[1]! as int]; - assert( - arg_request != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null, expected non-null PersistenceCacheIndexManagerRequest.', - ); + assert(arg_request != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null, expected non-null PersistenceCacheIndexManagerRequest.'); await api.persistenceCacheIndexManagerRequest(arg_app!, arg_request!); return []; }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline was null.'); + final List args = (message as List?)!; + final FirestorePigeonFirebaseApp? arg_app = + (args[0] as FirestorePigeonFirebaseApp?); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline was null, expected non-null FirestorePigeonFirebaseApp.'); + final List?>? arg_stages = + (args[1] as List?)?.cast?>(); + assert(arg_stages != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline was null, expected non-null List?>.'); + final Map? arg_options = + (args[2] as Map?)?.cast(); + final PigeonPipelineSnapshot output = + await api.executePipeline(arg_app!, arg_stages!, arg_options); + return [output]; + }); + } + } } } From b4089d8cacbdd879838e932c6b0e3922eb6ae00a Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Tue, 17 Feb 2026 15:58:53 +0000 Subject: [PATCH 06/72] chore: implement pipeline execution and expression parsing utilities for Android --- .../FlutterFirebaseFirestorePlugin.java | 65 +++ .../GeneratedAndroidFirebaseFirestore.java | 6 +- .../firestore/utils/ExpressionHelpers.java | 152 +++++ .../firestore/utils/ExpressionParsers.java | 531 ++++++++++++++++++ .../firestore/utils/PipelineParser.java | 114 ++++ .../utils/PipelineStageHandlers.java | 311 ++++++++++ .../cloud_firestore/lib/src/pipeline.dart | 291 +++++----- .../lib/src/pipeline_aggregate.dart | 77 ++- .../lib/src/pipeline_expression.dart | 81 ++- .../lib/src/pipeline_stage.dart | 30 +- 10 files changed, 1511 insertions(+), 147 deletions(-) create mode 100644 packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java create mode 100644 packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java create mode 100644 packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineParser.java create mode 100644 packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java index 4b65130f62a4..297f70fb24d1 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java @@ -28,6 +28,8 @@ import com.google.firebase.firestore.MemoryCacheSettings; import com.google.firebase.firestore.PersistentCacheIndexManager; import com.google.firebase.firestore.PersistentCacheSettings; +import com.google.firebase.firestore.Pipeline; +import com.google.firebase.firestore.PipelineResult; import com.google.firebase.firestore.Query; import com.google.firebase.firestore.QuerySnapshot; import com.google.firebase.firestore.SetOptions; @@ -52,6 +54,7 @@ import io.flutter.plugins.firebase.firestore.streamhandler.TransactionStreamHandler; import io.flutter.plugins.firebase.firestore.utils.ExceptionConverter; import io.flutter.plugins.firebase.firestore.utils.PigeonParser; +import io.flutter.plugins.firebase.firestore.utils.PipelineParser; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -983,4 +986,66 @@ public void documentReferenceSnapshot( parameters.getServerTimestampBehavior()), PigeonParser.parseListenSource(source)))); } + + @Override + public void executePipeline( + @NonNull GeneratedAndroidFirebaseFirestore.FirestorePigeonFirebaseApp app, + @NonNull List> stages, + @Nullable Map options, + @NonNull + GeneratedAndroidFirebaseFirestore.Result< + GeneratedAndroidFirebaseFirestore.PigeonPipelineSnapshot> + result) { + cachedThreadPool.execute( + () -> { + try { + FirebaseFirestore firestore = getFirestoreFromPigeon(app); + + // Execute pipeline using Android Firestore SDK + Pipeline.Snapshot snapshot = PipelineParser.executePipeline(firestore, stages, options); + + // Convert Pipeline.Snapshot to PigeonPipelineSnapshot + List pipelineResults = + new ArrayList<>(); + + // Iterate through snapshot results + for (PipelineResult pipelineResult : snapshot.getResults()) { + GeneratedAndroidFirebaseFirestore.PigeonPipelineResult.Builder resultBuilder = + new GeneratedAndroidFirebaseFirestore.PigeonPipelineResult.Builder(); + resultBuilder.setDocumentPath(pipelineResult.getRef().getPath()); + + // Convert timestamps (assuming they're in milliseconds) + if (pipelineResult.getCreateTime() != null) { + resultBuilder.setCreateTime(pipelineResult.getCreateTime().toDate().getTime()); + } else { + resultBuilder.setCreateTime(0L); + } + + if (pipelineResult.getUpdateTime() != null) { + resultBuilder.setUpdateTime(pipelineResult.getUpdateTime().toDate().getTime()); + } else { + resultBuilder.setUpdateTime(0L); + } + + pipelineResults.add(resultBuilder.build()); + } + + // Build the snapshot + GeneratedAndroidFirebaseFirestore.PigeonPipelineSnapshot.Builder snapshotBuilder = + new GeneratedAndroidFirebaseFirestore.PigeonPipelineSnapshot.Builder(); + snapshotBuilder.setResults(pipelineResults); + + // Set execution time (use current time if not available from snapshot) + if (snapshot.getExecutionTime() != null) { + snapshotBuilder.setExecutionTime(snapshot.getExecutionTime().toDate().getTime()); + } else { + snapshotBuilder.setExecutionTime(System.currentTimeMillis()); + } + + result.success(snapshotBuilder.build()); + } catch (Exception e) { + ExceptionConverter.sendErrorToFlutter(result, e); + } + }); + } } diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java index 3638853507b6..1711e9159d80 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java @@ -513,7 +513,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(2); toListResult.add(hasPendingWrites); toListResult.add(isFromCache); @@ -604,7 +604,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(3); toListResult.add(path); toListResult.add(data); @@ -725,7 +725,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(4); toListResult.add(type == null ? null : type.index); toListResult.add((document == null) ? null : document.toList()); diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java new file mode 100644 index 000000000000..cb162d253310 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java @@ -0,0 +1,152 @@ +/* + * Copyright 2026, the Chromium project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +package io.flutter.plugins.firebase.firestore.utils; + +import androidx.annotation.NonNull; +import com.google.firebase.Timestamp; +import com.google.firebase.firestore.Blob; +import com.google.firebase.firestore.DocumentReference; +import com.google.firebase.firestore.GeoPoint; +import com.google.firebase.firestore.VectorValue; +import com.google.firebase.firestore.pipeline.BooleanExpression; +import com.google.firebase.firestore.pipeline.Expression; +import java.util.List; +import java.util.Map; + +/** Helper utilities for parsing expressions and handling common patterns. */ +class ExpressionHelpers { + + /** + * Parses an "and" expression from a list of expression maps. Uses Expression.and() with varargs + * signature. + * + * @param exprMaps List of expression maps to combine with AND + * @param parser Reference to ExpressionParsers for recursive parsing + */ + @SuppressWarnings("unchecked") + static BooleanExpression parseAndExpression( + @NonNull List> exprMaps, @NonNull ExpressionParsers parser) { + if (exprMaps == null || exprMaps.isEmpty()) { + throw new IllegalArgumentException("'and' requires at least one expression"); + } + + BooleanExpression first = parser.parseBooleanExpression(exprMaps.get(0)); + if (exprMaps.size() == 1) { + return first; + } + + BooleanExpression[] rest = new BooleanExpression[exprMaps.size() - 1]; + for (int i = 1; i < exprMaps.size(); i++) { + rest[i - 1] = parser.parseBooleanExpression(exprMaps.get(i)); + } + return Expression.and(first, rest); + } + + /** + * Parses an "or" expression from a list of expression maps. Uses Expression.or() with varargs + * signature. + * + * @param exprMaps List of expression maps to combine with OR + * @param parser Reference to ExpressionParsers for recursive parsing + */ + @SuppressWarnings("unchecked") + static BooleanExpression parseOrExpression( + @NonNull List> exprMaps, @NonNull ExpressionParsers parser) { + if (exprMaps == null || exprMaps.isEmpty()) { + throw new IllegalArgumentException("'or' requires at least one expression"); + } + + BooleanExpression first = parser.parseBooleanExpression(exprMaps.get(0)); + if (exprMaps.size() == 1) { + return first; + } + + BooleanExpression[] rest = new BooleanExpression[exprMaps.size() - 1]; + for (int i = 1; i < exprMaps.size(); i++) { + rest[i - 1] = parser.parseBooleanExpression(exprMaps.get(i)); + } + return Expression.or(first, rest); + } + + /** + * Parses a constant value based on its type to match Android SDK constant() overloads. Valid + * types: String, Number, Boolean, Date, Timestamp, GeoPoint, byte[], Blob, DocumentReference, + * VectorValue + */ + static Expression parseConstantValue(@NonNull Object value) { + + if (value instanceof String) { + return Expression.constant((String) value); + } else if (value instanceof Number) { + return Expression.constant((Number) value); + } else if (value instanceof Boolean) { + return Expression.constant((Boolean) value); + } else if (value instanceof java.util.Date) { + return Expression.constant((java.util.Date) value); + } else if (value instanceof Timestamp) { + return Expression.constant((Timestamp) value); + } else if (value instanceof GeoPoint) { + return Expression.constant((GeoPoint) value); + } else if (value instanceof byte[]) { + return Expression.constant((byte[]) value); + } else if (value instanceof List) { + // Handle List from Dart which comes as List or List + // This represents byte[] (byte array) for constant expressions + @SuppressWarnings("unchecked") + List list = (List) value; + // Check if all elements are numbers (for byte array) + boolean isByteArray = true; + for (Object item : list) { + if (!(item instanceof Number)) { + isByteArray = false; + break; + } + } + if (isByteArray && !list.isEmpty()) { + byte[] byteArray = new byte[list.size()]; + for (int i = 0; i < list.size(); i++) { + byteArray[i] = ((Number) list.get(i)).byteValue(); + } + return Expression.constant(byteArray); + } + // If not a byte array, fall through to error + } else if (value instanceof Blob) { + return Expression.constant((Blob) value); + } else if (value instanceof DocumentReference) { + return Expression.constant((DocumentReference) value); + } else if (value instanceof VectorValue) { + return Expression.constant((VectorValue) value); + } + + throw new IllegalArgumentException( + "Constant value must be one of: String, Number, Boolean, Date, Timestamp, " + + "GeoPoint, byte[], Blob, DocumentReference, or VectorValue. Got: " + + value.getClass().getName()); + } + + /** Safely extracts a single expression from args map. */ + @SuppressWarnings("unchecked") + static Map extractExpression( + @NonNull Map args, @NonNull String key) { + Map exprMap = (Map) args.get(key); + if (exprMap == null) { + throw new IllegalArgumentException("Missing required expression argument: " + key); + } + return exprMap; + } + + /** Safely extracts a list of expressions from args map. */ + @SuppressWarnings("unchecked") + static List> extractExpressionList( + @NonNull Map args, @NonNull String key) { + List> exprMaps = (List>) args.get(key); + if (exprMaps == null || exprMaps.isEmpty()) { + throw new IllegalArgumentException("Missing or empty expression list: " + key); + } + return exprMaps; + } +} diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java new file mode 100644 index 000000000000..150d932ae6d3 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java @@ -0,0 +1,531 @@ +/* + * Copyright 2026, the Chromium project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +package io.flutter.plugins.firebase.firestore.utils; + +import android.util.Log; +import androidx.annotation.NonNull; +import com.google.firebase.firestore.pipeline.AggregateFunction; +import com.google.firebase.firestore.pipeline.AggregateOptions; +import com.google.firebase.firestore.pipeline.AggregateStage; +import com.google.firebase.firestore.pipeline.AliasedAggregate; +import com.google.firebase.firestore.pipeline.BooleanExpression; +import com.google.firebase.firestore.pipeline.Expression; +import com.google.firebase.firestore.pipeline.FindNearestStage; +import com.google.firebase.firestore.pipeline.Selectable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Handles parsing of all expression types from Dart map representations to Android SDK objects. */ +class ExpressionParsers { + private static final String TAG = "ExpressionParsers"; + + /** Binary operation on two expressions. Used instead of BiFunction for API 23 compatibility. */ + private interface BinaryExpressionOp { + R apply(Expression left, Expression right); + } + + /** Parses an expression from a map representation. */ + @SuppressWarnings("unchecked") + Expression parseExpression(@NonNull Map expressionMap) { + String name = (String) expressionMap.get("name"); + if (name == null) { + // Might be a field reference directly (legacy format) + if (expressionMap.containsKey("field_name")) { + String fieldName = (String) expressionMap.get("field_name"); + return Expression.field(fieldName); + } + // Check for field in args (current format) + Map argsCheck = (Map) expressionMap.get("args"); + if (argsCheck != null && argsCheck.containsKey("field")) { + String fieldName = (String) argsCheck.get("field"); + return Expression.field(fieldName); + } + throw new IllegalArgumentException("Expression must have a 'name' field"); + } + + Map args = (Map) expressionMap.get("args"); + if (args == null) { + args = new HashMap<>(); + } + + switch (name) { + case "field": + { + String fieldName = (String) args.get("field"); + if (fieldName == null) { + throw new IllegalArgumentException("Field expression must have a 'field' argument"); + } + return Expression.field(fieldName); + } + case "constant": + { + Object value = args.get("value"); + return ExpressionHelpers.parseConstantValue(value); + } + case "alias": + { + Map exprMap = (Map) args.get("expression"); + String alias = (String) args.get("alias"); + Expression expr = parseExpression(exprMap); + return expr.alias(alias); + } + // Comparison operations + case "equal": + return parseBinaryComparison(args, (left, right) -> left.equal(right)); + case "not_equal": + return parseBinaryComparison(args, (left, right) -> left.notEqual(right)); + case "greater_than": + return parseBinaryComparison(args, (left, right) -> left.greaterThan(right)); + case "greater_than_or_equal": + return parseBinaryComparison(args, (left, right) -> left.greaterThanOrEqual(right)); + case "less_than": + return parseBinaryComparison(args, (left, right) -> left.lessThan(right)); + case "less_than_or_equal": + return parseBinaryComparison(args, (left, right) -> left.lessThanOrEqual(right)); + // Arithmetic operations + case "add": + return parseBinaryOperation(args, (left, right) -> left.add(right)); + case "subtract": + return parseBinaryOperation(args, (left, right) -> left.subtract(right)); + case "multiply": + return parseBinaryOperation(args, (left, right) -> left.multiply(right)); + case "divide": + return parseBinaryOperation(args, (left, right) -> left.divide(right)); + case "modulo": + return parseBinaryOperation(args, (left, right) -> left.mod(right)); + // Logic operations + case "and": + { + List> exprMaps = (List>) args.get("expressions"); + return ExpressionHelpers.parseAndExpression(exprMaps, this); + } + case "or": + { + List> exprMaps = (List>) args.get("expressions"); + return ExpressionHelpers.parseOrExpression(exprMaps, this); + } + case "not": + { + Map exprMap = (Map) args.get("expression"); + BooleanExpression expr = parseBooleanExpression(exprMap); + return Expression.not(expr); + } + default: + Log.w(TAG, "Unsupported expression type: " + name); + throw new UnsupportedOperationException("Expression type not yet implemented: " + name); + } + } + + /** Helper to parse binary comparison operations (equal, not_equal, greater_than, etc.). */ + @SuppressWarnings("unchecked") + private BooleanExpression parseBinaryComparison( + @NonNull Map args, @NonNull BinaryExpressionOp operation) { + Map leftMap = (Map) args.get("left"); + Map rightMap = (Map) args.get("right"); + Expression left = parseExpression(leftMap); + Expression right = parseExpression(rightMap); + return operation.apply(left, right); + } + + /** Helper to parse binary arithmetic operations (add, subtract, multiply, etc.). */ + @SuppressWarnings("unchecked") + private Expression parseBinaryOperation( + @NonNull Map args, @NonNull BinaryExpressionOp operation) { + Map leftMap = (Map) args.get("left"); + Map rightMap = (Map) args.get("right"); + Expression left = parseExpression(leftMap); + Expression right = parseExpression(rightMap); + return operation.apply(left, right); + } + + /** + * Parses a boolean expression from a map representation. Boolean expressions are used in where + * clauses and return BooleanExpression. + */ + @SuppressWarnings("unchecked") + BooleanExpression parseBooleanExpression(@NonNull Map expressionMap) { + String name = (String) expressionMap.get("name"); + if (name == null) { + throw new IllegalArgumentException("BooleanExpression must have a 'name' field"); + } + + Map args = (Map) expressionMap.get("args"); + if (args == null) { + args = new HashMap<>(); + } + + switch (name) { + // Comparison operations - these return BooleanExpression + case "equal": + return parseBinaryComparison(args, (left, right) -> left.equal(right)); + case "not_equal": + return parseBinaryComparison(args, (left, right) -> left.notEqual(right)); + case "greater_than": + return parseBinaryComparison(args, (left, right) -> left.greaterThan(right)); + case "greater_than_or_equal": + return parseBinaryComparison(args, (left, right) -> left.greaterThanOrEqual(right)); + case "less_than": + return parseBinaryComparison(args, (left, right) -> left.lessThan(right)); + case "less_than_or_equal": + return parseBinaryComparison(args, (left, right) -> left.lessThanOrEqual(right)); + // Logical operations - these return BooleanExpression + case "and": + { + List> exprMaps = (List>) args.get("expressions"); + return ExpressionHelpers.parseAndExpression(exprMaps, this); + } + case "or": + { + List> exprMaps = (List>) args.get("expressions"); + if (exprMaps == null || exprMaps.isEmpty()) { + throw new IllegalArgumentException("'or' requires at least one expression"); + } + if (exprMaps.size() == 1) { + return parseBooleanExpression(exprMaps.get(0)); + } + // BooleanExpression.or() takes exactly 2 parameters, so we chain them + BooleanExpression result = parseBooleanExpression(exprMaps.get(0)); + for (int i = 1; i < exprMaps.size(); i++) { + BooleanExpression next = parseBooleanExpression(exprMaps.get(i)); + result = BooleanExpression.or(result, next); + } + return result; + } + case "not": + { + Map exprMap = (Map) args.get("expression"); + BooleanExpression expr = parseBooleanExpression(exprMap); + return expr.not(); + } + // Boolean-specific expressions + case "is_absent": + { + Map exprMap = (Map) args.get("expression"); + Expression expr = parseExpression(exprMap); + return expr.isAbsent(); + } + case "is_error": + { + Map exprMap = (Map) args.get("expression"); + Expression expr = parseExpression(exprMap); + return expr.isError(); + } + case "exists": + { + Map exprMap = (Map) args.get("expression"); + Expression expr = parseExpression(exprMap); + return expr.exists(); + } + case "array_contains": + { + Map arrayMap = (Map) args.get("array"); + Map elementMap = (Map) args.get("element"); + Expression array = parseExpression(arrayMap); + Expression element = parseExpression(elementMap); + return array.arrayContains(element); + } + case "array_contains_all": + { + Map arrayMap = (Map) args.get("array"); + Map arrayExprMap = (Map) args.get("array_expression"); + Expression array = parseExpression(arrayMap); + Expression arrayExpr = parseExpression(arrayExprMap); + return array.arrayContainsAll(arrayExpr); + } + case "array_contains_any": + { + Map arrayMap = (Map) args.get("array"); + Map arrayExprMap = (Map) args.get("array_expression"); + Expression array = parseExpression(arrayMap); + Expression arrayExpr = parseExpression(arrayExprMap); + return array.arrayContainsAny(arrayExpr); + } + case "equal_any": + { + Map valueMap = (Map) args.get("value"); + List> valuesMaps = (List>) args.get("values"); + Expression value = parseExpression(valueMap); + Expression[] values = new Expression[valuesMaps.size()]; + for (int i = 0; i < valuesMaps.size(); i++) { + values[i] = parseExpression(valuesMaps.get(i)); + } + return value.equalAny(List.of(values)); + } + case "not_equal_any": + { + Map valueMap = (Map) args.get("value"); + List> valuesMaps = (List>) args.get("values"); + Expression value = parseExpression(valueMap); + Expression[] values = new Expression[valuesMaps.size()]; + for (int i = 0; i < valuesMaps.size(); i++) { + values[i] = parseExpression(valuesMaps.get(i)); + } + return value.notEqualAny(List.of(values)); + } + case "as_boolean": + { + Map exprMap = (Map) args.get("expression"); + Expression expr = parseExpression(exprMap); + return expr.asBoolean(); + } + // Handle filter expressions (PipelineFilter) + case "filter": + return parseFilterExpression(args); + default: + // Try parsing as a regular expression first, then cast to BooleanExpression if possible + Expression expr = parseExpression(expressionMap); + if (expr instanceof BooleanExpression) { + return (BooleanExpression) expr; + } + Log.w(TAG, "Expression type '" + name + "' is not a BooleanExpression, attempting cast"); + throw new IllegalArgumentException( + "Expression type '" + name + "' cannot be used as a BooleanExpression"); + } + } + + /** + * Parses a filter expression (PipelineFilter) which can have operator-based or field-based forms. + */ + @SuppressWarnings("unchecked") + private BooleanExpression parseFilterExpression(@NonNull Map args) { + // PipelineFilter can have various forms - check for operator-based or field-based + if (args.containsKey("operator")) { + String operator = (String) args.get("operator"); + List> exprMaps = (List>) args.get("expressions"); + if ("and".equals(operator)) { + return ExpressionHelpers.parseAndExpression(exprMaps, this); + } else if ("or".equals(operator)) { + if (exprMaps == null || exprMaps.isEmpty()) { + throw new IllegalArgumentException("'or' requires at least one expression"); + } + if (exprMaps.size() == 1) { + return parseBooleanExpression(exprMaps.get(0)); + } + // BooleanExpression.or() takes exactly 2 parameters, so we chain them + BooleanExpression result = parseBooleanExpression(exprMaps.get(0)); + for (int i = 1; i < exprMaps.size(); i++) { + BooleanExpression next = parseBooleanExpression(exprMaps.get(i)); + result = BooleanExpression.or(result, next); + } + return result; + } + } + // Field-based filter - parse field and create appropriate comparison + String fieldName = (String) args.get("field"); + Expression fieldExpr = Expression.field(fieldName); + + return parseFieldBasedFilter(fieldExpr, args); + } + + /** Parses field-based filter comparisons (isEqualTo, isGreaterThan, etc.). */ + @SuppressWarnings("unchecked") + private BooleanExpression parseFieldBasedFilter( + @NonNull Expression fieldExpr, @NonNull Map args) { + if (args.containsKey("isEqualTo")) { + Object value = args.get("isEqualTo"); + return value instanceof Map + ? fieldExpr.equal(parseExpression((Map) value)) + : fieldExpr.equal(value); + } + if (args.containsKey("isNotEqualTo")) { + Object value = args.get("isNotEqualTo"); + return value instanceof Map + ? fieldExpr.notEqual(parseExpression((Map) value)) + : fieldExpr.notEqual(value); + } + if (args.containsKey("isGreaterThan")) { + Object value = args.get("isGreaterThan"); + return value instanceof Map + ? fieldExpr.greaterThan(parseExpression((Map) value)) + : fieldExpr.greaterThan(value); + } + if (args.containsKey("isGreaterThanOrEqualTo")) { + Object value = args.get("isGreaterThanOrEqualTo"); + return value instanceof Map + ? fieldExpr.greaterThanOrEqual(parseExpression((Map) value)) + : fieldExpr.greaterThanOrEqual(value); + } + if (args.containsKey("isLessThan")) { + Object value = args.get("isLessThan"); + return value instanceof Map + ? fieldExpr.lessThan(parseExpression((Map) value)) + : fieldExpr.lessThan(value); + } + if (args.containsKey("isLessThanOrEqualTo")) { + Object value = args.get("isLessThanOrEqualTo"); + return value instanceof Map + ? fieldExpr.lessThanOrEqual(parseExpression((Map) value)) + : fieldExpr.lessThanOrEqual(value); + } + if (args.containsKey("arrayContains")) { + Object value = args.get("arrayContains"); + return value instanceof Map + ? fieldExpr.arrayContains(parseExpression((Map) value)) + : fieldExpr.arrayContains(value); + } + throw new IllegalArgumentException("Unsupported filter expression format"); + } + + /** + * Parses a Selectable from a map representation. Selectables are Field or AliasedExpression + * types. + */ + @SuppressWarnings("unchecked") + Selectable parseSelectable(@NonNull Map expressionMap) { + Expression expr = parseExpression(expressionMap); + if (!(expr instanceof Selectable)) { + throw new IllegalArgumentException( + "Expression must be a Selectable (Field or AliasedExpression). Got: " + + expressionMap.get("name")); + } + return (Selectable) expr; + } + + /** Parses an aggregate function from a map representation. */ + @SuppressWarnings("unchecked") + AggregateFunction parseAggregateFunction(@NonNull Map aggregateMap) { + String functionName = (String) aggregateMap.get("function"); + if (functionName == null) { + // Try "name" as fallback + functionName = (String) aggregateMap.get("name"); + } + Map args = (Map) aggregateMap.get("args"); + + Map exprMap = (Map) args.get("expression"); + Expression expr = parseExpression(exprMap); + + switch (functionName) { + case "sum": + return AggregateFunction.sum(expr); + case "average": + return AggregateFunction.average(expr); + case "count": + return AggregateFunction.count(expr); + case "count_distinct": + return AggregateFunction.countDistinct(expr); + case "minimum": + return AggregateFunction.minimum(expr); + case "maximum": + return AggregateFunction.maximum(expr); + default: + throw new IllegalArgumentException("Unknown aggregate function: " + functionName); + } + } + + /** + * Parses an AliasedAggregate from a Dart AliasedAggregateFunction map representation. Since Dart + * API only accepts AliasedAggregateFunction, we can directly construct AliasedAggregate. + */ + @SuppressWarnings("unchecked") + AliasedAggregate parseAliasedAggregate(@NonNull Map aggregateMap) { + // Check if this is an aliased aggregate function (Dart AliasedAggregateFunction format) + String name = (String) aggregateMap.get("name"); + if ("alias".equals(name)) { + Map args = (Map) aggregateMap.get("args"); + String alias = (String) args.get("alias"); + Map aggregateFunctionMap = + (Map) args.get("aggregate_function"); + + // Parse the underlying aggregate function + AggregateFunction function = parseAggregateFunction(aggregateFunctionMap); + + // Apply the alias to get AliasedAggregate + return function.alias(alias); + } + + // If not in alias format, it might be a direct aggregate function with alias field + // This shouldn't happen with the new Dart API, but handle for backward compatibility + String alias = (String) aggregateMap.get("alias"); + if (alias != null) { + AggregateFunction function = parseAggregateFunction(aggregateMap); + return function.alias(alias); + } + + throw new IllegalArgumentException( + "Aggregate function must have an alias. Expected AliasedAggregateFunction format."); + } + + /** Parses an AggregateStage from a map representation. */ + @SuppressWarnings("unchecked") + AggregateStage parseAggregateStage(@NonNull Map stageMap) { + // Parse accumulators (required) + List> accumulatorMaps = + (List>) stageMap.get("accumulators"); + if (accumulatorMaps == null || accumulatorMaps.isEmpty()) { + throw new IllegalArgumentException("AggregateStage must have at least one accumulator"); + } + + // Parse accumulators as AliasedAggregate + AliasedAggregate[] accumulators = new AliasedAggregate[accumulatorMaps.size()]; + for (int i = 0; i < accumulatorMaps.size(); i++) { + accumulators[i] = parseAliasedAggregate(accumulatorMaps.get(i)); + } + + // Build AggregateStage with accumulators + AggregateStage aggregateStage; + if (accumulators.length == 1) { + aggregateStage = AggregateStage.withAccumulators(accumulators[0]); + } else { + AliasedAggregate[] rest = new AliasedAggregate[accumulators.length - 1]; + System.arraycopy(accumulators, 1, rest, 0, rest.length); + aggregateStage = AggregateStage.withAccumulators(accumulators[0], rest); + } + + // Parse optional groups and add them using withGroups() + // withGroups(group: Selectable, vararg additionalGroups: Any) + List> groupMaps = (List>) stageMap.get("groups"); + if (groupMaps != null && !groupMaps.isEmpty()) { + // Parse first group as Selectable (required) + Selectable firstGroup = parseSelectable(groupMaps.get(0)); + + if (groupMaps.size() == 1) { + // Only one group + aggregateStage = aggregateStage.withGroups(firstGroup); + } else { + // Multiple groups - parse remaining as Any[] (varargs) + Object[] additionalGroups = new Object[groupMaps.size() - 1]; + for (int i = 1; i < groupMaps.size(); i++) { + // Parse as Expression first, then convert to Object (can be Selectable or Any) + Expression expr = parseExpression(groupMaps.get(i)); + additionalGroups[i - 1] = expr; + } + aggregateStage = aggregateStage.withGroups(firstGroup, additionalGroups); + } + } + + return aggregateStage; + } + + /** Parses AggregateOptions from a map representation. */ + @SuppressWarnings("unchecked") + AggregateOptions parseAggregateOptions(@NonNull Map optionsMap) { + // For now, AggregateOptions is empty, but this method is ready for future options + return new AggregateOptions(); + } + + /** + * Converts a Dart DistanceMeasure enum name to Android FindNearestStage.DistanceMeasure enum. + * Dart enum values: cosine, euclidean, dotProduct Android enum values: COSINE, EUCLIDEAN, + * DOT_PRODUCT + */ + FindNearestStage.DistanceMeasure parseDistanceMeasure(@NonNull String dartEnumName) { + switch (dartEnumName) { + case "cosine": + return FindNearestStage.DistanceMeasure.COSINE; + case "euclidean": + return FindNearestStage.DistanceMeasure.EUCLIDEAN; + case "dotProduct": + return FindNearestStage.DistanceMeasure.DOT_PRODUCT; + default: + throw new IllegalArgumentException( + "Unknown distance measure: " + + dartEnumName + + ". Expected: cosine, euclidean, or dotProduct"); + } + } +} diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineParser.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineParser.java new file mode 100644 index 000000000000..609f34be03ca --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineParser.java @@ -0,0 +1,114 @@ +/* + * Copyright 2026, the Chromium project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +package io.flutter.plugins.firebase.firestore.utils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.Tasks; +import com.google.firebase.firestore.DocumentReference; +import com.google.firebase.firestore.FirebaseFirestore; +import com.google.firebase.firestore.Pipeline; +import com.google.firebase.firestore.Pipeline.Snapshot; +import com.google.firebase.firestore.PipelineSource; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class PipelineParser { + private static final String TAG = "PipelineParser"; + private static final ExpressionParsers expressionParsers = new ExpressionParsers(); + private static final PipelineStageHandlers stageHandlers = + new PipelineStageHandlers(expressionParsers); + + /** + * Executes a pipeline from a list of stage maps. + * + * @param firestore The Firestore instance + * @param stages List of stage maps, each with 'stage' and 'args' fields + * @param options Optional execution options + * @return The pipeline snapshot result + */ + public static Snapshot executePipeline( + @NonNull FirebaseFirestore firestore, + @NonNull List> stages, + @Nullable Map options) + throws Exception { + if (stages == null || stages.isEmpty()) { + throw new IllegalArgumentException("Pipeline must have at least one stage"); + } + + PipelineSource pipelineSource = firestore.pipeline(); + Pipeline pipeline = null; + + // Process each stage in order + for (int i = 0; i < stages.size(); i++) { + Map stageMap = stages.get(i); + String stageName = (String) stageMap.get("stage"); + if (stageName == null) { + throw new IllegalArgumentException("Stage must have a 'stage' field"); + } + + @SuppressWarnings("unchecked") + Map args = (Map) stageMap.get("args"); + + if (i == 0) { + // First stage must be a source stage (collection, collection_group, documents, database) + pipeline = applySourceStage(pipelineSource, stageName, args, firestore); + } else { + // Subsequent stages are methods on the Pipeline instance + pipeline = stageHandlers.applyStage(pipeline, stageName, args, firestore); + } + } + + // Execute the pipeline + Task task = pipeline.execute(); + return Tasks.await(task); + } + + /** + * Applies a source stage (collection, collection_group, documents, database) to PipelineSource. + * These are the only stages that can be the first stage and return a Pipeline instance. + */ + @SuppressWarnings("unchecked") + private static Pipeline applySourceStage( + @NonNull PipelineSource pipelineSource, + @NonNull String stageName, + @Nullable Map args, + @NonNull FirebaseFirestore firestore) { + switch (stageName) { + case "collection": + { + String path = (String) args.get("path"); + return pipelineSource.collection(path); + } + case "collection_group": + { + String path = (String) args.get("path"); + return pipelineSource.collectionGroup(path); + } + case "database": + { + return pipelineSource.database(); + } + case "documents": + { + List> docMaps = (List>) args; + List docRefs = new ArrayList<>(); + for (Map docMap : docMaps) { + String docPath = (String) docMap.get("path"); + docRefs.add(firestore.document(docPath)); + } + return pipelineSource.documents(docRefs.toArray(new DocumentReference[0])); + } + default: + throw new IllegalArgumentException( + "First stage must be one of: collection, collection_group, documents, database. Got: " + + stageName); + } + } +} diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java new file mode 100644 index 000000000000..7aa36fba7be4 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java @@ -0,0 +1,311 @@ +/* + * Copyright 2026, the Chromium project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +package io.flutter.plugins.firebase.firestore.utils; + +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.firebase.firestore.FirebaseFirestore; +import com.google.firebase.firestore.Pipeline; +import com.google.firebase.firestore.pipeline.AggregateOptions; +import com.google.firebase.firestore.pipeline.AggregateStage; +import com.google.firebase.firestore.pipeline.AliasedAggregate; +import com.google.firebase.firestore.pipeline.BooleanExpression; +import com.google.firebase.firestore.pipeline.Expression; +import com.google.firebase.firestore.pipeline.Field; +import com.google.firebase.firestore.pipeline.FindNearestOptions; +import com.google.firebase.firestore.pipeline.FindNearestStage; +import com.google.firebase.firestore.pipeline.Ordering; +import com.google.firebase.firestore.pipeline.Selectable; +import com.google.firebase.firestore.pipeline.UnnestOptions; +import java.util.List; +import java.util.Map; + +/** Handles parsing and applying pipeline stages to Pipeline instances. */ +class PipelineStageHandlers { + private static final String TAG = "PipelineStageHandlers"; + private final ExpressionParsers parsers; + + PipelineStageHandlers(@NonNull ExpressionParsers parsers) { + this.parsers = parsers; + } + + /** Applies a pipeline stage to a Pipeline instance. */ + @SuppressWarnings("unchecked") + Pipeline applyStage( + @NonNull Pipeline pipeline, + @NonNull String stageName, + @Nullable Map args, + @NonNull FirebaseFirestore firestore) { + switch (stageName) { + case "where": + return handleWhere(pipeline, args); + case "limit": + return handleLimit(pipeline, args); + case "offset": + return handleOffset(pipeline, args); + case "sort": + return handleSort(pipeline, args); + case "select": + return handleSelect(pipeline, args); + case "add_fields": + return handleAddFields(pipeline, args); + case "remove_fields": + return handleRemoveFields(pipeline, args); + case "distinct": + return handleDistinct(pipeline, args); + case "aggregate": + return handleAggregate(pipeline, args); + case "unnest": + return handleUnnest(pipeline, args); + case "replace_with": + return handleReplaceWith(pipeline, args); + case "union": + return handleUnion(pipeline, args); + case "sample": + return handleSample(pipeline, args); + case "find_nearest": + return handleFindNearest(pipeline, args); + default: + throw new IllegalArgumentException("Unknown pipeline stage: " + stageName); + } + } + + private Pipeline handleWhere(@NonNull Pipeline pipeline, @Nullable Map args) { + Map expressionMap = (Map) args.get("expression"); + BooleanExpression booleanExpression = parsers.parseBooleanExpression(expressionMap); + return pipeline.where(booleanExpression); + } + + private Pipeline handleLimit(@NonNull Pipeline pipeline, @Nullable Map args) { + Number limit = (Number) args.get("limit"); + return pipeline.limit(limit.intValue()); + } + + private Pipeline handleOffset(@NonNull Pipeline pipeline, @Nullable Map args) { + Number offset = (Number) args.get("offset"); + return pipeline.offset(offset.intValue()); + } + + private Pipeline handleSort(@NonNull Pipeline pipeline, @Nullable Map args) { + Map orderingMap = (Map) args.get("expression"); + Expression expression = parsers.parseExpression(orderingMap); + String direction = (String) args.get("order_direction"); + Ordering ordering = "asc".equals(direction) ? expression.ascending() : expression.descending(); + return pipeline.sort(ordering); + } + + private Pipeline handleSelect(@NonNull Pipeline pipeline, @Nullable Map args) { + List> expressionMaps = (List>) args.get("expressions"); + + if (expressionMaps == null || expressionMaps.isEmpty()) { + throw new IllegalArgumentException("'select' requires at least one expression"); + } + + // Parse first expression as Selectable + Selectable firstSelection = parsers.parseSelectable(expressionMaps.get(0)); + + // Parse remaining expressions as varargs + if (expressionMaps.size() == 1) { + return pipeline.select(firstSelection); + } + + Object[] additionalSelections = new Object[expressionMaps.size() - 1]; + for (int i = 1; i < expressionMaps.size(); i++) { + Expression expr = parsers.parseExpression(expressionMaps.get(i)); + // Additional selections can be Selectable or any Object + additionalSelections[i - 1] = expr; + } + + return pipeline.select(firstSelection, additionalSelections); + } + + private Pipeline handleAddFields(@NonNull Pipeline pipeline, @Nullable Map args) { + List> expressionMaps = (List>) args.get("expressions"); + + if (expressionMaps == null || expressionMaps.isEmpty()) { + throw new IllegalArgumentException("'add_fields' requires at least one expression"); + } + + // Parse first expression as Selectable + Selectable firstField = parsers.parseSelectable(expressionMaps.get(0)); + + // Parse remaining expressions as Selectable varargs + if (expressionMaps.size() == 1) { + return pipeline.addFields(firstField); + } + + Selectable[] additionalFields = new Selectable[expressionMaps.size() - 1]; + for (int i = 1; i < expressionMaps.size(); i++) { + additionalFields[i - 1] = parsers.parseSelectable(expressionMaps.get(i)); + } + + return pipeline.addFields(firstField, additionalFields); + } + + private Pipeline handleRemoveFields( + @NonNull Pipeline pipeline, @Nullable Map args) { + List fieldPaths = (List) args.get("field_paths"); + + if (fieldPaths == null || fieldPaths.isEmpty()) { + throw new IllegalArgumentException("'remove_fields' requires at least one field path"); + } + + // Convert first field path string to Field + Field firstField = Expression.field(fieldPaths.get(0)); + + // Convert remaining field paths to Field varargs + if (fieldPaths.size() == 1) { + return pipeline.removeFields(firstField); + } + + Field[] additionalFields = new Field[fieldPaths.size() - 1]; + for (int i = 1; i < fieldPaths.size(); i++) { + additionalFields[i - 1] = Expression.field(fieldPaths.get(i)); + } + + return pipeline.removeFields(firstField, additionalFields); + } + + private Pipeline handleDistinct(@NonNull Pipeline pipeline, @Nullable Map args) { + List> expressionMaps = (List>) args.get("expressions"); + + if (expressionMaps == null || expressionMaps.isEmpty()) { + throw new IllegalArgumentException("'distinct' requires at least one expression"); + } + + // Parse first expression as Selectable + Selectable firstGroup = parsers.parseSelectable(expressionMaps.get(0)); + + // Parse remaining expressions as varargs (can be Selectable or Any) + if (expressionMaps.size() == 1) { + return pipeline.distinct(firstGroup); + } + + Object[] additionalGroups = new Object[expressionMaps.size() - 1]; + for (int i = 1; i < expressionMaps.size(); i++) { + Expression expr = parsers.parseExpression(expressionMaps.get(i)); + // Additional groups can be Selectable or any Object + additionalGroups[i - 1] = expr; + } + + return pipeline.distinct(firstGroup, additionalGroups); + } + + @SuppressWarnings("unchecked") + private Pipeline handleAggregate(@NonNull Pipeline pipeline, @Nullable Map args) { + // Check if this is using aggregate_stage (new API) or aggregate_functions (legacy API) + if (args.containsKey("aggregate_stage")) { + // New API: aggregateStage with optional options + Map aggregateStageMap = (Map) args.get("aggregate_stage"); + AggregateStage aggregateStage = parsers.parseAggregateStage(aggregateStageMap); + + // Parse optional options + Map optionsMap = (Map) args.get("options"); + if (optionsMap != null && !optionsMap.isEmpty()) { + AggregateOptions options = parsers.parseAggregateOptions(optionsMap); + return pipeline.aggregate(aggregateStage, options); + } else { + return pipeline.aggregate(aggregateStage); + } + } else { + // Legacy API: aggregate_functions (varargs) + List> aggregateMaps = + (List>) args.get("aggregate_functions"); + + if (aggregateMaps == null || aggregateMaps.isEmpty()) { + throw new IllegalArgumentException( + "'aggregate' requires at least one aggregate function or an aggregate_stage"); + } + + // Parse first aggregate function as AliasedAggregate + AliasedAggregate firstAccumulator = parsers.parseAliasedAggregate(aggregateMaps.get(0)); + + // Parse remaining aggregate functions as AliasedAggregate varargs + if (aggregateMaps.size() == 1) { + return pipeline.aggregate(firstAccumulator); + } + + AliasedAggregate[] additionalAccumulators = new AliasedAggregate[aggregateMaps.size() - 1]; + for (int i = 1; i < aggregateMaps.size(); i++) { + additionalAccumulators[i - 1] = parsers.parseAliasedAggregate(aggregateMaps.get(i)); + } + + return pipeline.aggregate(firstAccumulator, additionalAccumulators); + } + } + + private Pipeline handleUnnest(@NonNull Pipeline pipeline, @Nullable Map args) { + Map expressionMap = (Map) args.get("expression"); + Selectable expression = parsers.parseSelectable(expressionMap); + String indexField = (String) args.get("index_field"); + if (indexField != null) { + return pipeline.unnest(expression, new UnnestOptions().withIndexField(indexField)); + } else { + return pipeline.unnest(expression); + } + } + + private Pipeline handleReplaceWith( + @NonNull Pipeline pipeline, @Nullable Map args) { + Map expressionMap = (Map) args.get("expression"); + Expression expression = parsers.parseExpression(expressionMap); + return pipeline.replaceWith(expression); + } + + private Pipeline handleUnion(@NonNull Pipeline pipeline, @Nullable Map args) { + // Union requires a nested pipeline + List> nestedStages = (List>) args.get("pipeline"); + // Note: This would require creating a nested pipeline, which may not be directly + // supported. This is a placeholder for now. + Log.w(TAG, "Union stage not yet fully implemented"); + throw new UnsupportedOperationException("Union stage not yet implemented"); + } + + private Pipeline handleSample(@NonNull Pipeline pipeline, @Nullable Map args) { + // Sample stage parsing + Map sampleMap = (Map) args; + // Parse sample configuration + Log.w(TAG, "Sample stage not yet fully implemented"); + throw new UnsupportedOperationException("Sample stage not yet implemented"); + } + + @SuppressWarnings("unchecked") + private Pipeline handleFindNearest( + @NonNull Pipeline pipeline, @Nullable Map args) { + String vectorField = (String) args.get("vector_field"); + List vectorValue = (List) args.get("vector_value"); + String distanceMeasureStr = (String) args.get("distance_measure"); + Number limitObj = (Number) args.get("limit"); + + if (distanceMeasureStr == null) { + throw new IllegalArgumentException("'find_nearest' requires a 'distance_measure' argument"); + } + + // Convert Dart enum name to Android enum value + FindNearestStage.DistanceMeasure distanceMeasure = + parsers.parseDistanceMeasure(distanceMeasureStr); + + // Convert vector value to double array + double[] vectorArray = new double[vectorValue.size()]; + for (int i = 0; i < vectorValue.size(); i++) { + vectorArray[i] = vectorValue.get(i).doubleValue(); + } + + Field fieldExpr = Expression.field(vectorField); + + if (limitObj != null) { + return pipeline.findNearest( + vectorField, + Expression.vector(vectorArray), + distanceMeasure, + new FindNearestOptions().withLimit(limitObj.intValue())); + } else { + return pipeline.findNearest(fieldExpr, vectorArray, distanceMeasure); + } + } +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart index 956685e3e8a7..eea1c5fa23ae 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart @@ -55,69 +55,58 @@ class Pipeline { /// Adds fields to documents using expressions Pipeline addFields( - Expression expression1, [ - Expression? expression2, - Expression? expression3, - Expression? expression4, - Expression? expression5, - Expression? expression6, - Expression? expression7, - Expression? expression8, - Expression? expression9, - Expression? expression10, - Expression? expression11, - Expression? expression12, - Expression? expression13, - Expression? expression14, - Expression? expression15, - Expression? expression16, - Expression? expression17, - Expression? expression18, - Expression? expression19, - Expression? expression20, - Expression? expression21, - Expression? expression22, - Expression? expression23, - Expression? expression24, - Expression? expression25, - Expression? expression26, - Expression? expression27, - Expression? expression28, - Expression? expression29, - Expression? expression30, + Selectable selectable1, [ + Selectable? selectable2, + Selectable? selectable3, + Selectable? selectable4, + Selectable? selectable5, + Selectable? selectable6, + Selectable? selectable7, + Selectable? selectable8, + Selectable? selectable9, + Selectable? selectable10, + Selectable? selectable11, + Selectable? selectable12, + Selectable? selectable13, + Selectable? selectable14, + Selectable? selectable15, + Selectable? selectable16, + Selectable? selectable17, + Selectable? selectable18, + Selectable? selectable19, + Selectable? selectable20, + Selectable? selectable21, + Selectable? selectable22, + Selectable? selectable23, + Selectable? selectable24, + Selectable? selectable25, + Selectable? selectable26, + Selectable? selectable27, + Selectable? selectable28, + Selectable? selectable29, + Selectable? selectable30, ]) { - final expressions = [expression1]; - if (expression2 != null) expressions.add(expression2); - if (expression3 != null) expressions.add(expression3); - if (expression4 != null) expressions.add(expression4); - if (expression5 != null) expressions.add(expression5); - if (expression6 != null) expressions.add(expression6); - if (expression7 != null) expressions.add(expression7); - if (expression8 != null) expressions.add(expression8); - if (expression9 != null) expressions.add(expression9); - if (expression10 != null) expressions.add(expression10); - if (expression11 != null) expressions.add(expression11); - if (expression12 != null) expressions.add(expression12); - if (expression13 != null) expressions.add(expression13); - if (expression14 != null) expressions.add(expression14); - if (expression15 != null) expressions.add(expression15); - if (expression16 != null) expressions.add(expression16); - if (expression17 != null) expressions.add(expression17); - if (expression18 != null) expressions.add(expression18); - if (expression19 != null) expressions.add(expression19); - if (expression20 != null) expressions.add(expression20); - if (expression21 != null) expressions.add(expression21); - if (expression22 != null) expressions.add(expression22); - if (expression23 != null) expressions.add(expression23); - if (expression24 != null) expressions.add(expression24); - if (expression25 != null) expressions.add(expression25); - if (expression26 != null) expressions.add(expression26); - if (expression27 != null) expressions.add(expression27); - if (expression28 != null) expressions.add(expression28); - if (expression29 != null) expressions.add(expression29); - if (expression30 != null) expressions.add(expression30); - - final stage = _AddFieldsStage(expressions); + final selectables = [selectable1]; + if (selectable2 != null) selectables.add(selectable2); + if (selectable3 != null) selectables.add(selectable3); + if (selectable4 != null) selectables.add(selectable4); + if (selectable5 != null) selectables.add(selectable5); + if (selectable6 != null) selectables.add(selectable6); + if (selectable7 != null) selectables.add(selectable7); + if (selectable8 != null) selectables.add(selectable8); + if (selectable9 != null) selectables.add(selectable9); + if (selectable10 != null) selectables.add(selectable10); + if (selectable21 != null) selectables.add(selectable21); + if (selectable22 != null) selectables.add(selectable22); + if (selectable23 != null) selectables.add(selectable23); + if (selectable24 != null) selectables.add(selectable24); + if (selectable25 != null) selectables.add(selectable25); + if (selectable26 != null) selectables.add(selectable26); + if (selectable27 != null) selectables.add(selectable27); + if (selectable28 != null) selectables.add(selectable28); + if (selectable29 != null) selectables.add(selectable29); + if (selectable30 != null) selectables.add(selectable30); + final stage = _AddFieldsStage(selectables); return Pipeline._( _firestore, _delegate.addStage(stage.toMap()), @@ -126,38 +115,38 @@ class Pipeline { /// Aggregates data using aggregate functions Pipeline aggregate( - PipelineAggregateFunction aggregateFunction1, [ - PipelineAggregateFunction? aggregateFunction2, - PipelineAggregateFunction? aggregateFunction3, - PipelineAggregateFunction? aggregateFunction4, - PipelineAggregateFunction? aggregateFunction5, - PipelineAggregateFunction? aggregateFunction6, - PipelineAggregateFunction? aggregateFunction7, - PipelineAggregateFunction? aggregateFunction8, - PipelineAggregateFunction? aggregateFunction9, - PipelineAggregateFunction? aggregateFunction10, - PipelineAggregateFunction? aggregateFunction11, - PipelineAggregateFunction? aggregateFunction12, - PipelineAggregateFunction? aggregateFunction13, - PipelineAggregateFunction? aggregateFunction14, - PipelineAggregateFunction? aggregateFunction15, - PipelineAggregateFunction? aggregateFunction16, - PipelineAggregateFunction? aggregateFunction17, - PipelineAggregateFunction? aggregateFunction18, - PipelineAggregateFunction? aggregateFunction19, - PipelineAggregateFunction? aggregateFunction20, - PipelineAggregateFunction? aggregateFunction21, - PipelineAggregateFunction? aggregateFunction22, - PipelineAggregateFunction? aggregateFunction23, - PipelineAggregateFunction? aggregateFunction24, - PipelineAggregateFunction? aggregateFunction25, - PipelineAggregateFunction? aggregateFunction26, - PipelineAggregateFunction? aggregateFunction27, - PipelineAggregateFunction? aggregateFunction28, - PipelineAggregateFunction? aggregateFunction29, - PipelineAggregateFunction? aggregateFunction30, + AliasedAggregateFunction aggregateFunction1, [ + AliasedAggregateFunction? aggregateFunction2, + AliasedAggregateFunction? aggregateFunction3, + AliasedAggregateFunction? aggregateFunction4, + AliasedAggregateFunction? aggregateFunction5, + AliasedAggregateFunction? aggregateFunction6, + AliasedAggregateFunction? aggregateFunction7, + AliasedAggregateFunction? aggregateFunction8, + AliasedAggregateFunction? aggregateFunction9, + AliasedAggregateFunction? aggregateFunction10, + AliasedAggregateFunction? aggregateFunction11, + AliasedAggregateFunction? aggregateFunction12, + AliasedAggregateFunction? aggregateFunction13, + AliasedAggregateFunction? aggregateFunction14, + AliasedAggregateFunction? aggregateFunction15, + AliasedAggregateFunction? aggregateFunction16, + AliasedAggregateFunction? aggregateFunction17, + AliasedAggregateFunction? aggregateFunction18, + AliasedAggregateFunction? aggregateFunction19, + AliasedAggregateFunction? aggregateFunction20, + AliasedAggregateFunction? aggregateFunction21, + AliasedAggregateFunction? aggregateFunction22, + AliasedAggregateFunction? aggregateFunction23, + AliasedAggregateFunction? aggregateFunction24, + AliasedAggregateFunction? aggregateFunction25, + AliasedAggregateFunction? aggregateFunction26, + AliasedAggregateFunction? aggregateFunction27, + AliasedAggregateFunction? aggregateFunction28, + AliasedAggregateFunction? aggregateFunction29, + AliasedAggregateFunction? aggregateFunction30, ]) { - final functions = [aggregateFunction1]; + final functions = [aggregateFunction1]; if (aggregateFunction2 != null) functions.add(aggregateFunction2); if (aggregateFunction3 != null) functions.add(aggregateFunction3); if (aggregateFunction4 != null) functions.add(aggregateFunction4); @@ -195,40 +184,92 @@ class Pipeline { ); } + /// Performs optionally grouped aggregation operations on the documents from previous stages. + /// + /// This method allows you to calculate aggregate values over a set of documents, optionally + /// grouped by one or more fields or expressions. You can specify: + /// + /// - **Grouping Fields or Expressions**: One or more fields or functions to group the documents by. + /// For each distinct combination of values in these fields, a separate group is created. + /// If no grouping fields are provided, a single group containing all documents is used. + /// + /// - **Aggregate Functions**: One or more accumulation operations to perform within each group. + /// These are defined using [AliasedAggregateFunction] expressions, which are typically created + /// by calling `.as('alias')` on [PipelineAggregateFunction] instances. Each aggregation calculates + /// a value (e.g., sum, average, count) based on the documents within its group. + /// + /// Example: + /// ```dart + /// pipeline.aggregateStage( + /// AggregateStage( + /// accumulators: [ + /// Expression.field('likes').sum().as('total_likes'), + /// Expression.field('likes').average().as('avg_likes'), + /// ], + /// groups: [Expression.field('category')], + /// ), + /// ); + /// ``` + /// + /// With options: + /// ```dart + /// pipeline.aggregateStage( + /// AggregateStage( + /// accumulators: [ + /// Expression.field('likes').sum().as('total_likes'), + /// ], + /// ), + /// options: AggregateOptions(), + /// ); + /// ``` + Pipeline aggregateStage( + AggregateStage aggregateStage, { + AggregateOptions? options, + }) { + final stage = _AggregateStageWithOptions( + aggregateStage, + options ?? AggregateOptions(), + ); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); + } + /// Gets distinct values based on expressions Pipeline distinct( - Expression expression1, [ - Expression? expression2, - Expression? expression3, - Expression? expression4, - Expression? expression5, - Expression? expression6, - Expression? expression7, - Expression? expression8, - Expression? expression9, - Expression? expression10, - Expression? expression11, - Expression? expression12, - Expression? expression13, - Expression? expression14, - Expression? expression15, - Expression? expression16, - Expression? expression17, - Expression? expression18, - Expression? expression19, - Expression? expression20, - Expression? expression21, - Expression? expression22, - Expression? expression23, - Expression? expression24, - Expression? expression25, - Expression? expression26, - Expression? expression27, - Expression? expression28, - Expression? expression29, - Expression? expression30, + Selectable expression1, [ + Selectable? expression2, + Selectable? expression3, + Selectable? expression4, + Selectable? expression5, + Selectable? expression6, + Selectable? expression7, + Selectable? expression8, + Selectable? expression9, + Selectable? expression10, + Selectable? expression11, + Selectable? expression12, + Selectable? expression13, + Selectable? expression14, + Selectable? expression15, + Selectable? expression16, + Selectable? expression17, + Selectable? expression18, + Selectable? expression19, + Selectable? expression20, + Selectable? expression21, + Selectable? expression22, + Selectable? expression23, + Selectable? expression24, + Selectable? expression25, + Selectable? expression26, + Selectable? expression27, + Selectable? expression28, + Selectable? expression29, + Selectable? expression30, ]) { - final expressions = [expression1]; + final expressions = [expression1]; if (expression2 != null) expressions.add(expression2); if (expression3 != null) expressions.add(expression3); if (expression4 != null) expressions.add(expression4); @@ -469,7 +510,7 @@ class Pipeline { } /// Unnests arrays into separate documents - Pipeline unnest(Expression expression, [String? indexField]) { + Pipeline unnest(Selectable expression, [String? indexField]) { final stage = _UnnestStage(expression, indexField); return Pipeline._( _firestore, diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart index f0946ac18995..403d26b7f3a4 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart @@ -6,27 +6,45 @@ part of '../cloud_firestore.dart'; /// Base class for aggregate functions used in pipelines abstract class PipelineAggregateFunction implements PipelineSerializable { - String? _alias; - /// Assigns an alias to this aggregate function - PipelineAggregateFunction as(String alias) { - _alias = alias; - return this; + AliasedAggregateFunction as(String alias) { + return AliasedAggregateFunction( + alias: alias, + aggregateFunction: this, + ); } - String? get alias => _alias; - String get name; @override Map toMap() { - final map = { + return { 'name': name, }; - if (_alias != null) { - map['alias'] = _alias; - } - return map; + } +} + +/// Represents an aggregate function with an alias +class AliasedAggregateFunction implements PipelineSerializable { + final String _alias; + final PipelineAggregateFunction aggregateFunction; + + AliasedAggregateFunction({ + required String alias, + required this.aggregateFunction, + }) : _alias = alias; + + String get alias => _alias; + + @override + Map toMap() { + return { + 'name': 'alias', + 'args': { + 'alias': _alias, + 'aggregate_function': aggregateFunction.toMap(), + }, + }; } } @@ -56,3 +74,38 @@ class Count extends PipelineAggregateFunction { return map; } } + +/// Represents an aggregate stage with functions and optional grouping +class AggregateStage implements PipelineSerializable { + final List accumulators; + final List? groups; + + AggregateStage({ + required this.accumulators, + this.groups, + }); + + @override + Map toMap() { + final map = { + 'accumulators': accumulators.map((acc) => acc.toMap()).toList(), + }; + if (groups != null && groups!.isNotEmpty) { + map['groups'] = groups!.map((group) => group.toMap()).toList(); + } + return map; + } +} + +/// Options for aggregate operations +class AggregateOptions implements PipelineSerializable { + // Add any aggregate-specific options here as needed + // For now, this is a placeholder for future options + + AggregateOptions(); + + @override + Map toMap() { + return {}; + } +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart index 2c295e0f2e6f..b8a9fc5d9200 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart @@ -553,7 +553,32 @@ abstract class Expression implements PipelineSerializable { static Expression constantVector(VectorValue value) => Constant(value); /// Creates a constant expression from any value (convenience) - static Expression constant(Object? value) => Constant(value!); + /// + /// Valid types: String, num, bool, DateTime, Timestamp, GeoPoint, List (byte[]), + /// Blob, DocumentReference, VectorValue + static Expression constant(Object? value) { + if (value == null) { + return Constant(null); + } + // Validate that the value is one of the accepted types + if (value is! String && + value is! num && + value is! bool && + value is! DateTime && + value is! Timestamp && + value is! GeoPoint && + value is! List && + value is! Blob && + value is! DocumentReference && + value is! VectorValue) { + throw ArgumentError( + 'Constant value must be one of: String, num, bool, DateTime, Timestamp, ' + 'GeoPoint, List (byte[]), Blob, DocumentReference, or VectorValue. ' + 'Got: ${value.runtimeType}', + ); + } + return Constant(value); + } /// Creates a field reference expression from a field path string static Field field(String fieldPath) => Field(fieldPath); @@ -734,6 +759,11 @@ abstract class Expression implements PipelineSerializable { return _IsErrorExpression(expr); } + /// Negates a boolean expression + static BooleanExpression not(BooleanExpression expression) { + return _NotExpression(expression); + } + /// Joins array elements with a delimiter static Expression joinStatic( Expression arrayExpression, @@ -1191,10 +1221,33 @@ class _NullExpression extends Expression { } /// Represents a constant value in a pipeline expression +/// +/// Valid types: String, num, bool, DateTime, Timestamp, GeoPoint, List (byte[]), +/// Blob, DocumentReference, VectorValue, or null class Constant extends Expression { - final Object value; - - Constant(this.value); + final Object? value; + + Constant(this.value) { + if (value != null) { + // Validate that the value is one of the accepted types + if (value is! String && + value is! num && + value is! bool && + value is! DateTime && + value is! Timestamp && + value is! GeoPoint && + value is! List && + value is! Blob && + value is! DocumentReference && + value is! VectorValue) { + throw ArgumentError( + 'Constant value must be one of: String, num, bool, DateTime, Timestamp, ' + 'GeoPoint, List (byte[]), Blob, DocumentReference, or VectorValue. ' + 'Got: ${value.runtimeType}', + ); + } + } + } @override String get name => 'constant'; @@ -2237,6 +2290,26 @@ class _ExistsExpression extends BooleanExpression { } } +/// Represents a not (negation) function expression +class _NotExpression extends BooleanExpression { + final BooleanExpression expression; + + _NotExpression(this.expression); + + @override + String get name => 'not'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + /// Represents a conditional (ternary) function expression class _ConditionalExpression extends FunctionExpression { final BooleanExpression condition; diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart index 7532ddb8e544..14bbdf735e81 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart @@ -108,7 +108,7 @@ final class _AddFieldsStage extends PipelineStage { /// Stage for aggregating data final class _AggregateStage extends PipelineStage { - final List aggregateFunctions; + final List aggregateFunctions; _AggregateStage(this.aggregateFunctions); @@ -127,9 +127,33 @@ final class _AggregateStage extends PipelineStage { } } +/// Stage for aggregating data with options and grouping +final class _AggregateStageWithOptions extends PipelineStage { + final AggregateStage aggregateStage; + final AggregateOptions? options; + + _AggregateStageWithOptions(this.aggregateStage, this.options); + + @override + String get name => 'aggregate'; + + @override + Map toMap() { + final map = aggregateStage.toMap(); + final optionsMap = options?.toMap(); + return { + 'stage': name, + 'args': { + 'aggregate_stage': map, + 'options': optionsMap, + }, + }; + } +} + /// Stage for getting distinct values final class _DistinctStage extends PipelineStage { - final List expressions; + final List expressions; _DistinctStage(this.expressions); @@ -323,7 +347,7 @@ final class _SortStage extends PipelineStage { /// Stage for unnesting arrays final class _UnnestStage extends PipelineStage { - final Expression expression; + final Selectable expression; final String? indexField; _UnnestStage(this.expression, this.indexField); From 7d9035350bc50e8d4e75e7695acb1b05c0d36301 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Wed, 18 Feb 2026 15:37:30 +0000 Subject: [PATCH 07/72] chore: enhance PigeonPipelineResult to support nullable fields and additional data mapping --- .../FlutterFirebaseFirestorePlugin.java | 9 +- .../GeneratedAndroidFirebaseFirestore.java | 58 +++++++----- .../firestore/utils/ExpressionParsers.java | 11 ++- .../cloud_firestore/FirestoreMessages.g.m | 13 +-- .../Public/FirestoreMessages.g.h | 17 ++-- .../lib/pipeline_snapshot.dart | 16 ++-- .../cloud_firestore/lib/src/pipeline.dart | 4 +- .../cloud_firestore/windows/messages.g.cpp | 88 +++++++++++++++---- .../cloud_firestore/windows/messages.g.h | 30 +++++-- .../method_channel_pipeline_snapshot.dart | 29 ++++-- .../lib/src/pigeon/messages.pigeon.dart | 24 +++-- .../platform_interface_pipeline_snapshot.dart | 8 +- .../pigeons/messages.dart | 15 ++-- 13 files changed, 224 insertions(+), 98 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java index 297f70fb24d1..4a17da9d532f 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java @@ -1012,7 +1012,9 @@ public void executePipeline( for (PipelineResult pipelineResult : snapshot.getResults()) { GeneratedAndroidFirebaseFirestore.PigeonPipelineResult.Builder resultBuilder = new GeneratedAndroidFirebaseFirestore.PigeonPipelineResult.Builder(); - resultBuilder.setDocumentPath(pipelineResult.getRef().getPath()); + if (pipelineResult.getRef() != null) { + resultBuilder.setDocumentPath(pipelineResult.getRef().getPath()); + } // Convert timestamps (assuming they're in milliseconds) if (pipelineResult.getCreateTime() != null) { @@ -1027,6 +1029,11 @@ public void executePipeline( resultBuilder.setUpdateTime(0L); } + Map data = pipelineResult.getData(); + if (data != null) { + resultBuilder.setData(data); + } + pipelineResults.add(resultBuilder.build()); } diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java index 1711e9159d80..892793f339d8 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java @@ -859,86 +859,94 @@ ArrayList toList() { /** Generated class from Pigeon that represents data sent in messages. */ public static final class PigeonPipelineResult { - private @NonNull String documentPath; + private @Nullable String documentPath; - public @NonNull String getDocumentPath() { + public @Nullable String getDocumentPath() { return documentPath; } - public void setDocumentPath(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"documentPath\" is null."); - } + public void setDocumentPath(@Nullable String setterArg) { this.documentPath = setterArg; } - private @NonNull Long createTime; + private @Nullable Long createTime; - public @NonNull Long getCreateTime() { + public @Nullable Long getCreateTime() { return createTime; } - public void setCreateTime(@NonNull Long setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"createTime\" is null."); - } + public void setCreateTime(@Nullable Long setterArg) { this.createTime = setterArg; } - private @NonNull Long updateTime; + private @Nullable Long updateTime; - public @NonNull Long getUpdateTime() { + public @Nullable Long getUpdateTime() { return updateTime; } - public void setUpdateTime(@NonNull Long setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"updateTime\" is null."); - } + public void setUpdateTime(@Nullable Long setterArg) { this.updateTime = setterArg; } - /** Constructor is non-public to enforce null safety; use Builder. */ - PigeonPipelineResult() {} + /** All fields in the result (from PipelineResult.data() on Android). */ + private @Nullable Map data; + + public @Nullable Map getData() { + return data; + } + + public void setData(@Nullable Map setterArg) { + this.data = setterArg; + } public static final class Builder { private @Nullable String documentPath; - public @NonNull Builder setDocumentPath(@NonNull String setterArg) { + public @NonNull Builder setDocumentPath(@Nullable String setterArg) { this.documentPath = setterArg; return this; } private @Nullable Long createTime; - public @NonNull Builder setCreateTime(@NonNull Long setterArg) { + public @NonNull Builder setCreateTime(@Nullable Long setterArg) { this.createTime = setterArg; return this; } private @Nullable Long updateTime; - public @NonNull Builder setUpdateTime(@NonNull Long setterArg) { + public @NonNull Builder setUpdateTime(@Nullable Long setterArg) { this.updateTime = setterArg; return this; } + private @Nullable Map data; + + public @NonNull Builder setData(@Nullable Map setterArg) { + this.data = setterArg; + return this; + } + public @NonNull PigeonPipelineResult build() { PigeonPipelineResult pigeonReturn = new PigeonPipelineResult(); pigeonReturn.setDocumentPath(documentPath); pigeonReturn.setCreateTime(createTime); pigeonReturn.setUpdateTime(updateTime); + pigeonReturn.setData(data); return pigeonReturn; } } @NonNull ArrayList toList() { - ArrayList toListResult = new ArrayList(3); + ArrayList toListResult = new ArrayList(4); toListResult.add(documentPath); toListResult.add(createTime); toListResult.add(updateTime); + toListResult.add(data); return toListResult; } @@ -956,6 +964,8 @@ ArrayList toList() { (updateTime == null) ? null : ((updateTime instanceof Integer) ? (Integer) updateTime : (Long) updateTime)); + Object data = list.get(3); + pigeonResult.setData((Map) data); return pigeonResult; } } diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java index 150d932ae6d3..1f80f671a97b 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java @@ -395,9 +395,12 @@ AggregateFunction parseAggregateFunction(@NonNull Map aggregateM functionName = (String) aggregateMap.get("name"); } Map args = (Map) aggregateMap.get("args"); - - Map exprMap = (Map) args.get("expression"); - Expression expr = parseExpression(exprMap); + Map exprMap; + Expression expr = null; + if (args != null) { + exprMap = (Map) args.get("expression"); + expr = parseExpression(exprMap); + } switch (functionName) { case "sum": @@ -412,6 +415,8 @@ AggregateFunction parseAggregateFunction(@NonNull Map aggregateM return AggregateFunction.minimum(expr); case "maximum": return AggregateFunction.maximum(expr); + case "count_all": + return AggregateFunction.countAll(); default: throw new IllegalArgumentException("Unknown aggregate function: " + functionName); } diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m index 2818f5742f4b..0672709a2921 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m @@ -421,23 +421,23 @@ - (NSArray *)toList { @end @implementation PigeonPipelineResult -+ (instancetype)makeWithDocumentPath:(NSString *)documentPath - createTime:(NSNumber *)createTime - updateTime:(NSNumber *)updateTime { ++ (instancetype)makeWithDocumentPath:(nullable NSString *)documentPath + createTime:(nullable NSNumber *)createTime + updateTime:(nullable NSNumber *)updateTime + data:(nullable NSDictionary *)data { PigeonPipelineResult *pigeonResult = [[PigeonPipelineResult alloc] init]; pigeonResult.documentPath = documentPath; pigeonResult.createTime = createTime; pigeonResult.updateTime = updateTime; + pigeonResult.data = data; return pigeonResult; } + (PigeonPipelineResult *)fromList:(NSArray *)list { PigeonPipelineResult *pigeonResult = [[PigeonPipelineResult alloc] init]; pigeonResult.documentPath = GetNullableObjectAtIndex(list, 0); - NSAssert(pigeonResult.documentPath != nil, @""); pigeonResult.createTime = GetNullableObjectAtIndex(list, 1); - NSAssert(pigeonResult.createTime != nil, @""); pigeonResult.updateTime = GetNullableObjectAtIndex(list, 2); - NSAssert(pigeonResult.updateTime != nil, @""); + pigeonResult.data = GetNullableObjectAtIndex(list, 3); return pigeonResult; } + (nullable PigeonPipelineResult *)nullableFromList:(NSArray *)list { @@ -448,6 +448,7 @@ - (NSArray *)toList { (self.documentPath ?: [NSNull null]), (self.createTime ?: [NSNull null]), (self.updateTime ?: [NSNull null]), + (self.data ?: [NSNull null]), ]; } @end diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h index 1f43fa87bf89..28885bdc9132 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h @@ -246,14 +246,15 @@ typedef NS_ENUM(NSUInteger, AggregateType) { @end @interface PigeonPipelineResult : NSObject -/// `init` unavailable to enforce nonnull fields, see the `make` class method. -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithDocumentPath:(NSString *)documentPath - createTime:(NSNumber *)createTime - updateTime:(NSNumber *)updateTime; -@property(nonatomic, copy) NSString *documentPath; -@property(nonatomic, strong) NSNumber *createTime; -@property(nonatomic, strong) NSNumber *updateTime; ++ (instancetype)makeWithDocumentPath:(nullable NSString *)documentPath + createTime:(nullable NSNumber *)createTime + updateTime:(nullable NSNumber *)updateTime + data:(nullable NSDictionary *)data; +@property(nonatomic, copy, nullable) NSString *documentPath; +@property(nonatomic, strong, nullable) NSNumber *createTime; +@property(nonatomic, strong, nullable) NSNumber *updateTime; +/// All fields in the result (from PipelineResult.data() on Android). +@property(nonatomic, strong, nullable) NSDictionary *data; @end @interface PigeonPipelineSnapshot : NSObject diff --git a/packages/cloud_firestore/cloud_firestore/lib/pipeline_snapshot.dart b/packages/cloud_firestore/cloud_firestore/lib/pipeline_snapshot.dart index f08cb6735987..ac1f287acb18 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/pipeline_snapshot.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/pipeline_snapshot.dart @@ -7,14 +7,20 @@ part of 'cloud_firestore.dart'; /// Result of executing a pipeline class PipelineResult { final DocumentReference> document; - final DateTime createTime; - final DateTime updateTime; + final DateTime? createTime; + final DateTime? updateTime; + final Map? _data; PipelineResult({ required this.document, - required this.createTime, - required this.updateTime, - }); + this.createTime, + this.updateTime, + Map? data, + }) : _data = data; + + /// Retrieves all fields in the result as a map. + /// Returns null if the result has no data. + Map? data() => _data; } /// Snapshot containing pipeline execution results diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart index eea1c5fa23ae..dce563ab21b7 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart @@ -36,7 +36,8 @@ class Pipeline { /// Converts platform snapshot to public snapshot PipelineSnapshot _convertPlatformSnapshot( - PipelineSnapshotPlatform platformSnapshot) { + PipelineSnapshotPlatform platformSnapshot, + ) { final results = platformSnapshot.results.map((platformResult) { return PipelineResult( document: _JsonDocumentReference( @@ -45,6 +46,7 @@ class Pipeline { ), createTime: platformResult.createTime, updateTime: platformResult.updateTime, + data: platformResult.data, ); }).toList(); diff --git a/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp b/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp index a70d4a7b95d5..b52f92e5c3a2 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp +++ b/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp @@ -399,46 +399,102 @@ PigeonQuerySnapshot PigeonQuerySnapshot::FromEncodableList( // PigeonPipelineResult -PigeonPipelineResult::PigeonPipelineResult(const std::string& document_path, - int64_t create_time, - int64_t update_time) - : document_path_(document_path), - create_time_(create_time), - update_time_(update_time) {} +PigeonPipelineResult::PigeonPipelineResult() {} -const std::string& PigeonPipelineResult::document_path() const { - return document_path_; +PigeonPipelineResult::PigeonPipelineResult(const std::string* document_path, + const int64_t* create_time, + const int64_t* update_time, + const EncodableMap* data) + : document_path_(document_path ? std::optional(*document_path) + : std::nullopt), + create_time_(create_time ? std::optional(*create_time) + : std::nullopt), + update_time_(update_time ? std::optional(*update_time) + : std::nullopt), + data_(data ? std::optional(*data) : std::nullopt) {} + +const std::string* PigeonPipelineResult::document_path() const { + return document_path_ ? &(*document_path_) : nullptr; +} + +void PigeonPipelineResult::set_document_path( + const std::string_view* value_arg) { + document_path_ = + value_arg ? std::optional(*value_arg) : std::nullopt; } void PigeonPipelineResult::set_document_path(std::string_view value_arg) { document_path_ = value_arg; } -int64_t PigeonPipelineResult::create_time() const { return create_time_; } +const int64_t* PigeonPipelineResult::create_time() const { + return create_time_ ? &(*create_time_) : nullptr; +} + +void PigeonPipelineResult::set_create_time(const int64_t* value_arg) { + create_time_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} void PigeonPipelineResult::set_create_time(int64_t value_arg) { create_time_ = value_arg; } -int64_t PigeonPipelineResult::update_time() const { return update_time_; } +const int64_t* PigeonPipelineResult::update_time() const { + return update_time_ ? &(*update_time_) : nullptr; +} + +void PigeonPipelineResult::set_update_time(const int64_t* value_arg) { + update_time_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} void PigeonPipelineResult::set_update_time(int64_t value_arg) { update_time_ = value_arg; } +const EncodableMap* PigeonPipelineResult::data() const { + return data_ ? &(*data_) : nullptr; +} + +void PigeonPipelineResult::set_data(const EncodableMap* value_arg) { + data_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void PigeonPipelineResult::set_data(const EncodableMap& value_arg) { + data_ = value_arg; +} + EncodableList PigeonPipelineResult::ToEncodableList() const { EncodableList list; - list.reserve(3); - list.push_back(EncodableValue(document_path_)); - list.push_back(EncodableValue(create_time_)); - list.push_back(EncodableValue(update_time_)); + list.reserve(4); + list.push_back(document_path_ ? EncodableValue(*document_path_) + : EncodableValue()); + list.push_back(create_time_ ? EncodableValue(*create_time_) + : EncodableValue()); + list.push_back(update_time_ ? EncodableValue(*update_time_) + : EncodableValue()); + list.push_back(data_ ? EncodableValue(*data_) : EncodableValue()); return list; } PigeonPipelineResult PigeonPipelineResult::FromEncodableList( const EncodableList& list) { - PigeonPipelineResult decoded(std::get(list[0]), - list[1].LongValue(), list[2].LongValue()); + PigeonPipelineResult decoded; + auto& encodable_document_path = list[0]; + if (!encodable_document_path.IsNull()) { + decoded.set_document_path(std::get(encodable_document_path)); + } + auto& encodable_create_time = list[1]; + if (!encodable_create_time.IsNull()) { + decoded.set_create_time(encodable_create_time.LongValue()); + } + auto& encodable_update_time = list[2]; + if (!encodable_update_time.IsNull()) { + decoded.set_update_time(encodable_update_time.LongValue()); + } + auto& encodable_data = list[3]; + if (!encodable_data.IsNull()) { + decoded.set_data(std::get(encodable_data)); + } return decoded; } diff --git a/packages/cloud_firestore/cloud_firestore/windows/messages.g.h b/packages/cloud_firestore/cloud_firestore/windows/messages.g.h index 40e4369d79d8..3589afb9db14 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/messages.g.h +++ b/packages/cloud_firestore/cloud_firestore/windows/messages.g.h @@ -346,28 +346,42 @@ class PigeonQuerySnapshot { // Generated class from Pigeon that represents data sent in messages. class PigeonPipelineResult { public: + // Constructs an object setting all non-nullable fields. + PigeonPipelineResult(); + // Constructs an object setting all fields. - explicit PigeonPipelineResult(const std::string& document_path, - int64_t create_time, int64_t update_time); + explicit PigeonPipelineResult(const std::string* document_path, + const int64_t* create_time, + const int64_t* update_time, + const flutter::EncodableMap* data); - const std::string& document_path() const; + const std::string* document_path() const; + void set_document_path(const std::string_view* value_arg); void set_document_path(std::string_view value_arg); - int64_t create_time() const; + const int64_t* create_time() const; + void set_create_time(const int64_t* value_arg); void set_create_time(int64_t value_arg); - int64_t update_time() const; + const int64_t* update_time() const; + void set_update_time(const int64_t* value_arg); void set_update_time(int64_t value_arg); + // All fields in the result (from PipelineResult.data() on Android). + const flutter::EncodableMap* data() const; + void set_data(const flutter::EncodableMap* value_arg); + void set_data(const flutter::EncodableMap& value_arg); + private: static PigeonPipelineResult FromEncodableList( const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; friend class FirebaseFirestoreHostApi; friend class FirebaseFirestoreHostApiCodecSerializer; - std::string document_path_; - int64_t create_time_; - int64_t update_time_; + std::optional document_path_; + std::optional create_time_; + std::optional update_time_; + std::optional data_; }; // Generated class from Pigeon that represents data sent in messages. diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart index 51ff99ffcff9..2566fd79cf10 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart @@ -17,14 +17,19 @@ class MethodChannelPipelineSnapshot extends PipelineSnapshotPlatform { FirebaseFirestorePlatform firestore, FirestorePigeonFirebaseApp pigeonApp, PigeonPipelineSnapshot pigeonSnapshot, - ) : _results = (pigeonSnapshot.results ?? []) + ) : _results = pigeonSnapshot.results .whereType() .map((result) => MethodChannelPipelineResult( firestore, pigeonApp, result.documentPath, - DateTime.fromMillisecondsSinceEpoch(result.createTime), - DateTime.fromMillisecondsSinceEpoch(result.updateTime), + result.createTime != null + ? DateTime.fromMillisecondsSinceEpoch(result.createTime!) + : null, + result.updateTime != null + ? DateTime.fromMillisecondsSinceEpoch(result.updateTime!) + : null, + result.data?.cast(), )) .toList(), _executionTime = DateTime.fromMillisecondsSinceEpoch( @@ -43,28 +48,34 @@ class MethodChannelPipelineSnapshot extends PipelineSnapshotPlatform { /// communicate with Firebase plugins. class MethodChannelPipelineResult extends PipelineResultPlatform { final DocumentReferencePlatform _document; - final DateTime _createTime; - final DateTime _updateTime; + final DateTime? _createTime; + final DateTime? _updateTime; + final Map? _data; MethodChannelPipelineResult( FirebaseFirestorePlatform firestore, FirestorePigeonFirebaseApp pigeonApp, - String documentPath, + String? documentPath, this._createTime, this._updateTime, + Map? data, ) : _document = MethodChannelDocumentReference( firestore, - documentPath, + documentPath ?? '', pigeonApp, ), + _data = data, super(); @override DocumentReferencePlatform get document => _document; @override - DateTime get createTime => _createTime; + DateTime? get createTime => _createTime; @override - DateTime get updateTime => _updateTime; + DateTime? get updateTime => _updateTime; + + @override + Map? get data => _data; } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart index 0ea2ee91ccd5..ef835c2b1c83 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart @@ -302,31 +302,37 @@ class PigeonQuerySnapshot { class PigeonPipelineResult { PigeonPipelineResult({ - required this.documentPath, - required this.createTime, - required this.updateTime, + this.documentPath, + this.createTime, + this.updateTime, + this.data, }); - String documentPath; + String? documentPath; + + int? createTime; - int createTime; + int? updateTime; - int updateTime; + /// All fields in the result (from PipelineResult.data() on Android). + Map? data; Object encode() { return [ documentPath, createTime, updateTime, + data, ]; } static PigeonPipelineResult decode(Object result) { result as List; return PigeonPipelineResult( - documentPath: result[0]! as String, - createTime: result[1]! as int, - updateTime: result[2]! as int, + documentPath: result[0] as String?, + createTime: result[1] as int?, + updateTime: result[2] as int?, + data: (result[3] as Map?)?.cast(), ); } } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline_snapshot.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline_snapshot.dart index 8b8d3b6fda30..d4760207a80d 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline_snapshot.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline_snapshot.dart @@ -33,8 +33,12 @@ abstract class PipelineResultPlatform extends PlatformInterface { DocumentReferencePlatform get document; /// The creation time of the document - DateTime get createTime; + DateTime? get createTime; /// The update time of the document - DateTime get updateTime; + DateTime? get updateTime; + + /// All fields in the result (from PipelineResult.data() on the native SDK). + /// Returns null if the result has no data. + Map? get data; } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart index 33b146653a45..a71ecdec270c 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart @@ -120,14 +120,17 @@ class PigeonQuerySnapshot { class PigeonPipelineResult { const PigeonPipelineResult({ - required this.documentPath, - required this.createTime, - required this.updateTime, + this.documentPath, + this.createTime, + this.updateTime, + this.data, }); - final String documentPath; - final int createTime; // Timestamp in milliseconds since epoch - final int updateTime; // Timestamp in milliseconds since epoch + final String? documentPath; + final int? createTime; // Timestamp in milliseconds since epoch + final int? updateTime; // Timestamp in milliseconds since epoch + /// All fields in the result (from PipelineResult.data() on Android). + final Map? data; } class PigeonPipelineSnapshot { From 6fbd3e4f4b2e1dd65ef8298246cdf5014ff563ae Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Fri, 20 Feb 2026 15:10:50 +0000 Subject: [PATCH 08/72] chore: enhance pipeline stage handling --- .../GeneratedAndroidFirebaseFirestore.java | 41 ++++++++++++------- .../firestore/utils/ExpressionHelpers.java | 22 ---------- .../firestore/utils/PipelineParser.java | 22 +++++----- .../utils/PipelineStageHandlers.java | 31 +++++++++----- 4 files changed, 59 insertions(+), 57 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java index 892793f339d8..a3fd90023413 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java @@ -12,7 +12,6 @@ import io.flutter.plugin.common.BasicMessageChannel; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MessageCodec; -import io.flutter.plugin.common.StandardMessageCodec; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -219,6 +218,20 @@ private AggregateType(final int index) { } } + /** Sample mode for the pipeline sample stage. */ + public enum PipelineSampleMode { + /** Sample a fixed number of documents. */ + SIZE(0), + /** Sample a percentage of documents (0.0 to 1.0). */ + PERCENTAGE(1); + + final int index; + + private PipelineSampleMode(final int index) { + this.index = index; + } + } + /** Generated class from Pigeon that represents data sent in messages. */ public static final class PigeonFirebaseSettings { private @Nullable Boolean persistenceEnabled; @@ -326,7 +339,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(5); toListResult.add(persistenceEnabled); toListResult.add(host); @@ -434,7 +447,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(3); toListResult.add(appName); toListResult.add((settings == null) ? null : settings.toList()); @@ -834,7 +847,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(3); toListResult.add(documents); toListResult.add(documentChanges); @@ -941,7 +954,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(4); toListResult.add(documentPath); toListResult.add(createTime); @@ -1026,7 +1039,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(2); toListResult.add(results); toListResult.add(executionTime); @@ -1105,7 +1118,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(2); toListResult.add(source == null ? null : source.index); toListResult.add(serverTimestampBehavior == null ? null : serverTimestampBehavior.index); @@ -1170,7 +1183,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(2); toListResult.add(merge); toListResult.add(mergeFields); @@ -1279,7 +1292,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(4); toListResult.add(type == null ? null : type.index); toListResult.add(path); @@ -1411,7 +1424,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(5); toListResult.add(path); toListResult.add(data); @@ -1614,7 +1627,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(9); toListResult.add(where); toListResult.add(orderBy); @@ -1709,7 +1722,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(2); toListResult.add(type == null ? null : type.index); toListResult.add(field); @@ -1797,7 +1810,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(3); toListResult.add(type == null ? null : type.index); toListResult.add(field); @@ -1824,7 +1837,7 @@ public interface Result { void error(@NonNull Throwable error); } - private static class FirebaseFirestoreHostApiCodec extends StandardMessageCodec { + private static class FirebaseFirestoreHostApiCodec extends FlutterFirebaseFirestoreMessageCodec { public static final FirebaseFirestoreHostApiCodec INSTANCE = new FirebaseFirestoreHostApiCodec(); diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java index cb162d253310..b3ef31dbb038 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java @@ -127,26 +127,4 @@ static Expression parseConstantValue(@NonNull Object value) { + "GeoPoint, byte[], Blob, DocumentReference, or VectorValue. Got: " + value.getClass().getName()); } - - /** Safely extracts a single expression from args map. */ - @SuppressWarnings("unchecked") - static Map extractExpression( - @NonNull Map args, @NonNull String key) { - Map exprMap = (Map) args.get(key); - if (exprMap == null) { - throw new IllegalArgumentException("Missing required expression argument: " + key); - } - return exprMap; - } - - /** Safely extracts a list of expressions from args map. */ - @SuppressWarnings("unchecked") - static List> extractExpressionList( - @NonNull Map args, @NonNull String key) { - List> exprMaps = (List>) args.get(key); - if (exprMaps == null || exprMaps.isEmpty()) { - throw new IllegalArgumentException("Missing or empty expression list: " + key); - } - return exprMaps; - } } diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineParser.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineParser.java index 609f34be03ca..2e0296d92f74 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineParser.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineParser.java @@ -38,14 +38,21 @@ public static Snapshot executePipeline( @NonNull List> stages, @Nullable Map options) throws Exception { - if (stages == null || stages.isEmpty()) { - throw new IllegalArgumentException("Pipeline must have at least one stage"); - } + Pipeline pipeline = buildPipeline(firestore, stages); + Task task = pipeline.execute(); + return Tasks.await(task); + } + /** + * Builds a Pipeline from a list of stage maps without executing it. Used when a stage (e.g. + * union) requires another pipeline as an argument. + */ + @SuppressWarnings("unchecked") + public static Pipeline buildPipeline( + @NonNull FirebaseFirestore firestore, @NonNull List> stages) { PipelineSource pipelineSource = firestore.pipeline(); Pipeline pipeline = null; - // Process each stage in order for (int i = 0; i < stages.size(); i++) { Map stageMap = stages.get(i); String stageName = (String) stageMap.get("stage"); @@ -53,21 +60,16 @@ public static Snapshot executePipeline( throw new IllegalArgumentException("Stage must have a 'stage' field"); } - @SuppressWarnings("unchecked") Map args = (Map) stageMap.get("args"); if (i == 0) { - // First stage must be a source stage (collection, collection_group, documents, database) pipeline = applySourceStage(pipelineSource, stageName, args, firestore); } else { - // Subsequent stages are methods on the Pipeline instance pipeline = stageHandlers.applyStage(pipeline, stageName, args, firestore); } } - // Execute the pipeline - Task task = pipeline.execute(); - return Tasks.await(task); + return pipeline; } /** diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java index 7aa36fba7be4..3883e36225b2 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java @@ -6,7 +6,6 @@ package io.flutter.plugins.firebase.firestore.utils; -import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.firebase.firestore.FirebaseFirestore; @@ -20,6 +19,7 @@ import com.google.firebase.firestore.pipeline.FindNearestOptions; import com.google.firebase.firestore.pipeline.FindNearestStage; import com.google.firebase.firestore.pipeline.Ordering; +import com.google.firebase.firestore.pipeline.SampleStage; import com.google.firebase.firestore.pipeline.Selectable; import com.google.firebase.firestore.pipeline.UnnestOptions; import java.util.List; @@ -27,7 +27,6 @@ /** Handles parsing and applying pipeline stages to Pipeline instances. */ class PipelineStageHandlers { - private static final String TAG = "PipelineStageHandlers"; private final ExpressionParsers parsers; PipelineStageHandlers(@NonNull ExpressionParsers parsers) { @@ -65,7 +64,7 @@ Pipeline applyStage( case "replace_with": return handleReplaceWith(pipeline, args); case "union": - return handleUnion(pipeline, args); + return handleUnion(pipeline, args, firestore); case "sample": return handleSample(pipeline, args); case "find_nearest": @@ -257,21 +256,31 @@ private Pipeline handleReplaceWith( return pipeline.replaceWith(expression); } - private Pipeline handleUnion(@NonNull Pipeline pipeline, @Nullable Map args) { - // Union requires a nested pipeline + @SuppressWarnings("unchecked") + private Pipeline handleUnion( + @NonNull Pipeline pipeline, + @Nullable Map args, + @NonNull FirebaseFirestore firestore) { List> nestedStages = (List>) args.get("pipeline"); - // Note: This would require creating a nested pipeline, which may not be directly - // supported. This is a placeholder for now. - Log.w(TAG, "Union stage not yet fully implemented"); - throw new UnsupportedOperationException("Union stage not yet implemented"); + if (nestedStages == null || nestedStages.isEmpty()) { + throw new IllegalArgumentException("'union' requires a non-empty 'pipeline' argument"); + } + Pipeline otherPipeline = PipelineParser.buildPipeline(firestore, nestedStages); + return pipeline.union(otherPipeline); } private Pipeline handleSample(@NonNull Pipeline pipeline, @Nullable Map args) { // Sample stage parsing Map sampleMap = (Map) args; // Parse sample configuration - Log.w(TAG, "Sample stage not yet fully implemented"); - throw new UnsupportedOperationException("Sample stage not yet implemented"); + String type = (String) sampleMap.get("type"); + if (type == "percentage") { + double value = (double) sampleMap.get("value"); + return pipeline.sample(SampleStage.withPercentage(value)); + } else { + int value = (int) sampleMap.get("value"); + return pipeline.sample(SampleStage.withDocLimit(value)); + } } @SuppressWarnings("unchecked") From 3e557c62b6eea5df3b7880f0cf25fde6547b9a85 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Tue, 24 Feb 2026 15:16:01 +0000 Subject: [PATCH 09/72] chore: refactor sorting functionality in pipeline stages to support multiple orderings --- .../GeneratedAndroidFirebaseFirestore.java | 14 - .../utils/PipelineStageHandlers.java | 31 +- .../cloud_firestore/lib/src/firestore.dart | 2 +- .../cloud_firestore/lib/src/pipeline.dart | 69 +++- .../lib/src/pipeline_stage.dart | 14 +- .../lib/src/pigeon/messages.pigeon.dart | 311 ++++++++++-------- 6 files changed, 284 insertions(+), 157 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java index a3fd90023413..4e6e315d71fe 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java @@ -218,20 +218,6 @@ private AggregateType(final int index) { } } - /** Sample mode for the pipeline sample stage. */ - public enum PipelineSampleMode { - /** Sample a fixed number of documents. */ - SIZE(0), - /** Sample a percentage of documents (0.0 to 1.0). */ - PERCENTAGE(1); - - final int index; - - private PipelineSampleMode(final int index) { - this.index = index; - } - } - /** Generated class from Pigeon that represents data sent in messages. */ public static final class PigeonFirebaseSettings { private @Nullable Boolean persistenceEnabled; diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java index 3883e36225b2..78ef835e3006 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java @@ -90,12 +90,33 @@ private Pipeline handleOffset(@NonNull Pipeline pipeline, @Nullable Map args) { - Map orderingMap = (Map) args.get("expression"); - Expression expression = parsers.parseExpression(orderingMap); - String direction = (String) args.get("order_direction"); - Ordering ordering = "asc".equals(direction) ? expression.ascending() : expression.descending(); - return pipeline.sort(ordering); + List> orderingMaps = (List>) args.get("orderings"); + if (orderingMaps == null || orderingMaps.isEmpty()) { + throw new IllegalArgumentException("'sort' requires at least one ordering"); + } + + Map firstMap = orderingMaps.get(0); + Expression expression = + parsers.parseExpression((Map) firstMap.get("expression")); + String direction = (String) firstMap.get("order_direction"); + Ordering firstOrdering = + "asc".equals(direction) ? expression.ascending() : expression.descending(); + + if (orderingMaps.size() == 1) { + return pipeline.sort(firstOrdering); + } + + Ordering[] additionalOrderings = new Ordering[orderingMaps.size() - 1]; + for (int i = 1; i < orderingMaps.size(); i++) { + Map map = orderingMaps.get(i); + expression = parsers.parseExpression((Map) map.get("expression")); + direction = (String) map.get("order_direction"); + additionalOrderings[i - 1] = + "asc".equals(direction) ? expression.ascending() : expression.descending(); + } + return pipeline.sort(firstOrdering, additionalOrderings); } private Pipeline handleSelect(@NonNull Pipeline pipeline, @Nullable Map args) { diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart b/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart index 32fe4f3e16db..e5fcc3264cac 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart @@ -350,7 +350,7 @@ class FirebaseFirestore extends FirebasePluginPlatform { /// .pipeline() /// .collection('users') /// .where(PipelineFilter(Field('age'), isGreaterThan: 18)) - /// .sort(Field('name').ascending()) + /// .sort(Field('name').ascending(), Field('age').descending()) /// .limit(10) /// .execute(); /// ``` diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart index dce563ab21b7..3565b835bbc9 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart @@ -502,9 +502,72 @@ class Pipeline { ); } - /// Sorts results using an ordering specification - Pipeline sort(Ordering ordering) { - final stage = _SortStage(ordering); + /// Sorts results using one or more ordering specifications. + /// Orderings are applied in sequence (e.g. primary sort by first, then by second, etc.). + Pipeline sort( + Ordering order, [ + Ordering? order2, + Ordering? order3, + Ordering? order4, + Ordering? order5, + Ordering? order6, + Ordering? order7, + Ordering? order8, + Ordering? order9, + Ordering? order10, + Ordering? order11, + Ordering? order12, + Ordering? order13, + Ordering? order14, + Ordering? order15, + Ordering? order16, + Ordering? order17, + Ordering? order18, + Ordering? order19, + Ordering? order20, + Ordering? order21, + Ordering? order22, + Ordering? order23, + Ordering? order24, + Ordering? order25, + Ordering? order26, + Ordering? order27, + Ordering? order28, + Ordering? order29, + Ordering? order30, + ]) { + final orderings = [order]; + if (order2 != null) orderings.add(order2); + if (order3 != null) orderings.add(order3); + if (order4 != null) orderings.add(order4); + if (order5 != null) orderings.add(order5); + if (order6 != null) orderings.add(order6); + if (order7 != null) orderings.add(order7); + if (order8 != null) orderings.add(order8); + if (order9 != null) orderings.add(order9); + if (order10 != null) orderings.add(order10); + if (order11 != null) orderings.add(order11); + if (order12 != null) orderings.add(order12); + if (order13 != null) orderings.add(order13); + if (order14 != null) orderings.add(order14); + if (order15 != null) orderings.add(order15); + if (order16 != null) orderings.add(order16); + if (order17 != null) orderings.add(order17); + if (order18 != null) orderings.add(order18); + if (order19 != null) orderings.add(order19); + if (order20 != null) orderings.add(order20); + if (order21 != null) orderings.add(order21); + if (order22 != null) orderings.add(order22); + if (order23 != null) orderings.add(order23); + if (order24 != null) orderings.add(order24); + if (order25 != null) orderings.add(order25); + if (order26 != null) orderings.add(order26); + if (order27 != null) orderings.add(order27); + if (order28 != null) orderings.add(order28); + if (order29 != null) orderings.add(order29); + if (order30 != null) orderings.add(order30); + + final stage = _SortStage(orderings); return Pipeline._( _firestore, _delegate.addStage(stage.toMap()), diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart index 14bbdf735e81..11c4ec624da1 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart @@ -325,9 +325,9 @@ final class _SelectStage extends PipelineStage { /// Stage for sorting results final class _SortStage extends PipelineStage { - final Ordering ordering; + final List orderings; - _SortStage(this.ordering); + _SortStage(this.orderings); @override String get name => 'sort'; @@ -337,9 +337,13 @@ final class _SortStage extends PipelineStage { return { 'stage': name, 'args': { - 'expression': ordering.expression.toMap(), - 'order_direction': - ordering.direction == OrderDirection.asc ? 'asc' : 'desc', + 'orderings': orderings + .map((o) => { + 'expression': o.expression.toMap(), + 'order_direction': + o.direction == OrderDirection.asc ? 'asc' : 'desc', + }) + .toList(), }, }; } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart index ef835c2b1c83..224ded3d6bf6 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart @@ -8,6 +8,7 @@ import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; +import 'package:cloud_firestore_platform_interface/src/method_channel/utils/firestore_message_codec.dart'; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; @@ -617,7 +618,7 @@ class AggregateQueryResponse { } } -class _FirebaseFirestoreHostApiCodec extends StandardMessageCodec { +class _FirebaseFirestoreHostApiCodec extends FirestoreMessageCodec { const _FirebaseFirestoreHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { @@ -721,11 +722,14 @@ class FirebaseFirestoreHostApi { static const MessageCodec codec = _FirebaseFirestoreHostApiCodec(); Future loadBundle( - FirestorePigeonFirebaseApp arg_app, Uint8List arg_bundle) async { + FirestorePigeonFirebaseApp arg_app, + Uint8List arg_bundle, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app, arg_bundle]) as List?; if (replyList == null) { @@ -749,12 +753,16 @@ class FirebaseFirestoreHostApi { } } - Future namedQueryGet(FirestorePigeonFirebaseApp arg_app, - String arg_name, PigeonGetOptions arg_options) async { + Future namedQueryGet( + FirestorePigeonFirebaseApp arg_app, + String arg_name, + PigeonGetOptions arg_options, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel .send([arg_app, arg_name, arg_options]) as List?; if (replyList == null) { @@ -780,9 +788,10 @@ class FirebaseFirestoreHostApi { Future clearPersistence(FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -803,9 +812,10 @@ class FirebaseFirestoreHostApi { Future disableNetwork(FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -826,9 +836,10 @@ class FirebaseFirestoreHostApi { Future enableNetwork(FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -849,9 +860,10 @@ class FirebaseFirestoreHostApi { Future terminate(FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -872,9 +884,10 @@ class FirebaseFirestoreHostApi { Future waitForPendingWrites(FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -894,11 +907,14 @@ class FirebaseFirestoreHostApi { } Future setIndexConfiguration( - FirestorePigeonFirebaseApp arg_app, String arg_indexConfiguration) async { + FirestorePigeonFirebaseApp arg_app, + String arg_indexConfiguration, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel .send([arg_app, arg_indexConfiguration]) as List?; if (replyList == null) { @@ -919,9 +935,10 @@ class FirebaseFirestoreHostApi { Future setLoggingEnabled(bool arg_loggingEnabled) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_loggingEnabled]) as List?; if (replyList == null) { @@ -941,11 +958,13 @@ class FirebaseFirestoreHostApi { } Future snapshotsInSyncSetup( - FirestorePigeonFirebaseApp arg_app) async { + FirestorePigeonFirebaseApp arg_app, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -969,12 +988,16 @@ class FirebaseFirestoreHostApi { } } - Future transactionCreate(FirestorePigeonFirebaseApp arg_app, - int arg_timeout, int arg_maxAttempts) async { + Future transactionCreate( + FirestorePigeonFirebaseApp arg_app, + int arg_timeout, + int arg_maxAttempts, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app, arg_timeout, arg_maxAttempts]) as List?; @@ -1000,16 +1023,18 @@ class FirebaseFirestoreHostApi { } Future transactionStoreResult( - String arg_transactionId, - PigeonTransactionResult arg_resultType, - List? arg_commands) async { + String arg_transactionId, + PigeonTransactionResult arg_resultType, + List? arg_commands, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send( - [arg_transactionId, arg_resultType.index, arg_commands]) - as List?; + [arg_transactionId, arg_resultType.index, arg_commands], + ) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -1027,13 +1052,15 @@ class FirebaseFirestoreHostApi { } Future transactionGet( - FirestorePigeonFirebaseApp arg_app, - String arg_transactionId, - String arg_path) async { + FirestorePigeonFirebaseApp arg_app, + String arg_transactionId, + String arg_path, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app, arg_transactionId, arg_path]) as List?; @@ -1058,12 +1085,15 @@ class FirebaseFirestoreHostApi { } } - Future documentReferenceSet(FirestorePigeonFirebaseApp arg_app, - DocumentReferenceRequest arg_request) async { + Future documentReferenceSet( + FirestorePigeonFirebaseApp arg_app, + DocumentReferenceRequest arg_request, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app, arg_request]) as List?; if (replyList == null) { @@ -1082,12 +1112,15 @@ class FirebaseFirestoreHostApi { } } - Future documentReferenceUpdate(FirestorePigeonFirebaseApp arg_app, - DocumentReferenceRequest arg_request) async { + Future documentReferenceUpdate( + FirestorePigeonFirebaseApp arg_app, + DocumentReferenceRequest arg_request, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app, arg_request]) as List?; if (replyList == null) { @@ -1107,12 +1140,14 @@ class FirebaseFirestoreHostApi { } Future documentReferenceGet( - FirestorePigeonFirebaseApp arg_app, - DocumentReferenceRequest arg_request) async { + FirestorePigeonFirebaseApp arg_app, + DocumentReferenceRequest arg_request, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app, arg_request]) as List?; if (replyList == null) { @@ -1136,12 +1171,15 @@ class FirebaseFirestoreHostApi { } } - Future documentReferenceDelete(FirestorePigeonFirebaseApp arg_app, - DocumentReferenceRequest arg_request) async { + Future documentReferenceDelete( + FirestorePigeonFirebaseApp arg_app, + DocumentReferenceRequest arg_request, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app, arg_request]) as List?; if (replyList == null) { @@ -1161,21 +1199,23 @@ class FirebaseFirestoreHostApi { } Future queryGet( - FirestorePigeonFirebaseApp arg_app, - String arg_path, - bool arg_isCollectionGroup, - PigeonQueryParameters arg_parameters, - PigeonGetOptions arg_options) async { + FirestorePigeonFirebaseApp arg_app, + String arg_path, + bool arg_isCollectionGroup, + PigeonQueryParameters arg_parameters, + PigeonGetOptions arg_options, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([ arg_app, arg_path, arg_isCollectionGroup, arg_parameters, - arg_options + arg_options, ]) as List?; if (replyList == null) { throw PlatformException( @@ -1199,23 +1239,25 @@ class FirebaseFirestoreHostApi { } Future> aggregateQuery( - FirestorePigeonFirebaseApp arg_app, - String arg_path, - PigeonQueryParameters arg_parameters, - AggregateSource arg_source, - List arg_queries, - bool arg_isCollectionGroup) async { + FirestorePigeonFirebaseApp arg_app, + String arg_path, + PigeonQueryParameters arg_parameters, + AggregateSource arg_source, + List arg_queries, + bool arg_isCollectionGroup, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([ arg_app, arg_path, arg_parameters, arg_source.index, arg_queries, - arg_isCollectionGroup + arg_isCollectionGroup, ]) as List?; if (replyList == null) { throw PlatformException( @@ -1238,12 +1280,15 @@ class FirebaseFirestoreHostApi { } } - Future writeBatchCommit(FirestorePigeonFirebaseApp arg_app, - List arg_writes) async { + Future writeBatchCommit( + FirestorePigeonFirebaseApp arg_app, + List arg_writes, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app, arg_writes]) as List?; if (replyList == null) { @@ -1263,17 +1308,19 @@ class FirebaseFirestoreHostApi { } Future querySnapshot( - FirestorePigeonFirebaseApp arg_app, - String arg_path, - bool arg_isCollectionGroup, - PigeonQueryParameters arg_parameters, - PigeonGetOptions arg_options, - bool arg_includeMetadataChanges, - ListenSource arg_source) async { + FirestorePigeonFirebaseApp arg_app, + String arg_path, + bool arg_isCollectionGroup, + PigeonQueryParameters arg_parameters, + PigeonGetOptions arg_options, + bool arg_includeMetadataChanges, + ListenSource arg_source, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([ arg_app, arg_path, @@ -1281,7 +1328,7 @@ class FirebaseFirestoreHostApi { arg_parameters, arg_options, arg_includeMetadataChanges, - arg_source.index + arg_source.index, ]) as List?; if (replyList == null) { throw PlatformException( @@ -1305,19 +1352,21 @@ class FirebaseFirestoreHostApi { } Future documentReferenceSnapshot( - FirestorePigeonFirebaseApp arg_app, - DocumentReferenceRequest arg_parameters, - bool arg_includeMetadataChanges, - ListenSource arg_source) async { + FirestorePigeonFirebaseApp arg_app, + DocumentReferenceRequest arg_parameters, + bool arg_includeMetadataChanges, + ListenSource arg_source, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([ arg_app, arg_parameters, arg_includeMetadataChanges, - arg_source.index + arg_source.index, ]) as List?; if (replyList == null) { throw PlatformException( @@ -1341,12 +1390,14 @@ class FirebaseFirestoreHostApi { } Future persistenceCacheIndexManagerRequest( - FirestorePigeonFirebaseApp arg_app, - PersistenceCacheIndexManagerRequest arg_request) async { + FirestorePigeonFirebaseApp arg_app, + PersistenceCacheIndexManagerRequest arg_request, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel .send([arg_app, arg_request.index]) as List?; if (replyList == null) { @@ -1366,13 +1417,15 @@ class FirebaseFirestoreHostApi { } Future executePipeline( - FirestorePigeonFirebaseApp arg_app, - List?> arg_stages, - Map? arg_options) async { + FirestorePigeonFirebaseApp arg_app, + List?> arg_stages, + Map? arg_options, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel .send([arg_app, arg_stages, arg_options]) as List?; if (replyList == null) { From d3c2eeb110e6ddefcc584cd9a97480f466c2bbf4 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Tue, 24 Feb 2026 15:18:00 +0000 Subject: [PATCH 10/72] chore: implement iOS support for Firestore pipeline execution --- .../FLTFirebaseFirestorePlugin.m | 77 ++ .../cloud_firestore/FLTPipelineParser.m | 512 +++++++++++ .../cloud_firestore/FirestoreMessages.g.m | 14 +- .../Private/FLTPipelineParser.h | 23 + .../Public/FirestoreMessages.g.h | 2 +- .../cloud_firestore/windows/messages.g.cpp | 5 +- .../cloud_firestore/windows/messages.g.h | 13 +- .../pigeons/generate_pigeon.sh | 4 +- .../test/pigeon/test_api.dart | 829 +++++++++++------- 9 files changed, 1168 insertions(+), 311 deletions(-) create mode 100644 packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m create mode 100644 packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Private/FLTPipelineParser.h diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTFirebaseFirestorePlugin.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTFirebaseFirestorePlugin.m index b7fa740fe68c..ab826056264f 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTFirebaseFirestorePlugin.m +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTFirebaseFirestorePlugin.m @@ -15,6 +15,7 @@ #import "include/cloud_firestore/Private/FLTFirebaseFirestoreReader.h" #import "include/cloud_firestore/Private/FLTFirebaseFirestoreUtils.h" #import "include/cloud_firestore/Private/FLTLoadBundleStreamHandler.h" +#import "include/cloud_firestore/Private/FLTPipelineParser.h" #import "include/cloud_firestore/Private/FLTQuerySnapshotStreamHandler.h" #import "include/cloud_firestore/Private/FLTSnapshotsInSyncStreamHandler.h" #import "include/cloud_firestore/Private/FLTTransactionStreamHandler.h" @@ -73,6 +74,20 @@ - (NSString *)registerEventChannelWithPrefix:(NSString *)prefix static NSCache *_serverTimestampMap; +static id _Nullable FLTPipelineNullSafe(id value) { + return (value == nil || [value isKindOfClass:[NSNull class]]) ? nil : value; +} + +static NSNumber *_Nullable FLTPipelineTimestampToMs(id value) { + if (!value) return nil; + if ([value isKindOfClass:[NSNumber class]]) return value; + if ([value isKindOfClass:[FIRTimestamp class]]) { + FIRTimestamp *ts = value; + return @((int64_t)ts.seconds * 1000 + (int64_t)ts.nanoseconds / 1000000); + } + return nil; +} + @implementation FLTFirebaseFirestorePlugin { NSMutableDictionary *_eventChannels; NSMutableDictionary *> *_streamHandlers; @@ -883,4 +898,66 @@ - (void)aggregateQueryApp:(nonnull FirestorePigeonFirebaseApp *)app }]; } +- (void)executePipelineApp:(nonnull FirestorePigeonFirebaseApp *)app + stages:(nonnull NSArray *> *)stages + options:(nullable NSDictionary *)options + completion:(nonnull void (^)(PigeonPipelineSnapshot *_Nullable, + FlutterError *_Nullable))completion { + FIRFirestore *firestore = [self getFIRFirestoreFromAppNameFromPigeon:app]; + + [FLTPipelineParser + executePipelineWithFirestore:firestore + stages:stages + options:options + completion:^(id _Nullable snapshot, NSError *_Nullable error) { + if (error) { + completion(nil, [self convertToFlutterError:error]); + return; + } + if (snapshot == nil) { + completion( + nil, + [FlutterError errorWithCode:@"error" + message:@"Pipeline execution returned no result" + details:nil]); + return; + } + + NSMutableArray *pigeonResults = + [NSMutableArray array]; + NSArray *results = [snapshot results]; + if ([results isKindOfClass:[NSArray class]]) { + for (id result in results) { + id ref = [result reference]; + NSString *path = (ref && [ref respondsToSelector:@selector(path)]) + ? [ref path] + : FLTPipelineNullSafe([result documentID]); + NSNumber *createTime = + FLTPipelineTimestampToMs([result valueForKey:@"create_time"]); + NSNumber *updateTime = + FLTPipelineTimestampToMs([result valueForKey:@"update_time"]); + NSDictionary *data = FLTPipelineNullSafe([result data]); + PigeonPipelineResult *pigeonResult = + [PigeonPipelineResult makeWithDocumentPath:path + createTime:createTime + updateTime:updateTime + data:data]; + [pigeonResults addObject:pigeonResult]; + } + } + + NSNumber *executionTime = + FLTPipelineTimestampToMs([snapshot execution_time]); + if (executionTime == nil) { + executionTime = + @((int64_t)([[NSDate date] timeIntervalSince1970] * 1000)); + } + + PigeonPipelineSnapshot *pigeonSnapshot = + [PigeonPipelineSnapshot makeWithResults:pigeonResults + executionTime:executionTime]; + completion(pigeonSnapshot, nil); + }]; +} + @end diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m new file mode 100644 index 000000000000..620d8ee7bc2d --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m @@ -0,0 +1,512 @@ +/* + * Copyright 2026, the Chromium project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +#import "include/cloud_firestore/Private/FLTPipelineParser.h" + +#if TARGET_OS_OSX +#import +#else +@import FirebaseFirestore; +#import "FIRPipelineBridge.h" +#endif + +#import + +static NSString *const kPipelineNotAvailable = + @"Pipeline API is not available. Firestore Pipelines require Firebase iOS SDK with pipeline " + "support."; + +static NSError *pipelineUnavailableError(void) { + return [NSError errorWithDomain:@"FLTFirebaseFirestore" + code:-1 + userInfo:@{NSLocalizedDescriptionKey : kPipelineNotAvailable}]; +} + +#if __has_include("FIRPipelineBridge.h") +#define FLT_PIPELINE_AVAILABLE 1 + +static NSError *parseError(NSString *message) { + return [NSError errorWithDomain:@"FLTFirebaseFirestore" + code:-1 + userInfo:@{NSLocalizedDescriptionKey : message}]; +} + +@interface FLTPipelineExpressionParser : NSObject +- (FIRExprBridge *)parseExpression:(NSDictionary *)map error:(NSError **)error; +- (FIRExprBridge *)parseBooleanExpression:(NSDictionary *)map + error:(NSError **)error; +@end + +@implementation FLTPipelineExpressionParser + +- (FIRExprBridge *)parseExpression:(NSDictionary *)map error:(NSError **)error { + NSString *name = map[@"name"]; + if (!name) { + NSDictionary *args = map[@"args"]; + if ([args isKindOfClass:[NSDictionary class]] && args[@"field"]) { + return [[FIRFieldBridge alloc] initWithName:args[@"field"]]; + } + if (error) *error = parseError(@"Expression must have a 'name' field"); + return nil; + } + + NSDictionary *args = map[@"args"]; + if (![args isKindOfClass:[NSDictionary class]]) args = @{}; + + if ([name isEqualToString:@"field"]) { + NSString *field = args[@"field"]; + if (!field) { + if (error) *error = parseError(@"Field expression requires 'field' argument"); + return nil; + } + return [[FIRFieldBridge alloc] initWithName:field]; + } + + if ([name isEqualToString:@"constant"]) { + id value = args[@"value"]; + if (value == nil) { + if (error) *error = parseError(@"Constant requires 'value' argument"); + return nil; + } + return [[FIRConstantBridge alloc] init:value]; + } + + if ([name isEqualToString:@"alias"]) { + id exprMap = args[@"expression"]; + if (![exprMap isKindOfClass:[NSDictionary class]]) { + if (error) *error = parseError(@"Alias requires 'expression'"); + return nil; + } + // No explicit AliasedExpression type in ObjC; aliases are dict keys when building stages. + // Parse and return the inner expression; the caller uses args[@"alias"] as the dict key. + return [self parseExpression:exprMap error:error]; + } + + NSArray *binaryNames = @[ + @"equal", @"not_equal", @"greater_than", @"greater_than_or_equal", @"less_than", + @"less_than_or_equal", @"add", @"subtract", @"multiply", @"divide", @"modulo" + ]; + if ([binaryNames containsObject:name]) { + id leftMap = args[@"left"]; + id rightMap = args[@"right"]; + if (![leftMap isKindOfClass:[NSDictionary class]] || + ![rightMap isKindOfClass:[NSDictionary class]]) { + if (error) + *error = + parseError([NSString stringWithFormat:@"%@ requires left and right expressions", name]); + return nil; + } + FIRExprBridge *left = [self parseExpression:leftMap error:error]; + FIRExprBridge *right = [self parseExpression:rightMap error:error]; + if (!left || !right) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:name Args:@[ left, right ]]; + } + + if ([name isEqualToString:@"and"] || [name isEqualToString:@"or"]) { + NSArray *exprMaps = args[@"expressions"]; + if (![exprMaps isKindOfClass:[NSArray class]] || exprMaps.count == 0) { + if (error) + *error = + parseError([NSString stringWithFormat:@"%@ requires at least one expression", name]); + return nil; + } + FIRExprBridge *first = [self parseBooleanExpression:exprMaps[0] error:error]; + if (!first) return nil; + NSMutableArray *all = [NSMutableArray arrayWithObject:first]; + for (NSUInteger i = 1; i < exprMaps.count; i++) { + FIRExprBridge *next = [self parseBooleanExpression:exprMaps[i] error:error]; + if (!next) return nil; + [all addObject:next]; + } + return [[FIRFunctionExprBridge alloc] initWithName:name Args:all]; + } + + if ([name isEqualToString:@"not"]) { + id exprMap = args[@"expression"]; + if (![exprMap isKindOfClass:[NSDictionary class]]) { + if (error) *error = parseError(@"not requires expression"); + return nil; + } + FIRExprBridge *expr = [self parseBooleanExpression:exprMap error:error]; + if (!expr) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:@"not" Args:@[ expr ]]; + } + + if (error) *error = parseError([NSString stringWithFormat:@"Unsupported expression: %@", name]); + return nil; +} + +- (FIRExprBridge *)parseBooleanExpression:(NSDictionary *)map + error:(NSError **)error { + return [self parseExpression:map error:error]; +} + +@end + +@implementation FLTPipelineParser + ++ (void)executePipelineWithFirestore:(FIRFirestore *)firestore + stages:(NSArray *> *)stages + options:(nullable NSDictionary *)options + completion:(void (^)(id _Nullable snapshot, + NSError *_Nullable error))completion { + if (!stages || stages.count == 0) { + completion(nil, parseError(@"Pipeline requires at least one stage")); + return; + } + + FLTPipelineExpressionParser *exprParser = [[FLTPipelineExpressionParser alloc] init]; + NSMutableArray *stageBridges = [NSMutableArray array]; + NSError *parseErr = nil; + + for (NSUInteger i = 0; i < stages.count; i++) { + NSDictionary *stageMap = stages[i]; + if (![stageMap isKindOfClass:[NSDictionary class]]) { + completion(nil, parseError(@"Stage must be a map")); + return; + } + NSString *stageName = stageMap[@"stage"]; + if (![stageName isKindOfClass:[NSString class]]) { + completion(nil, parseError(@"Stage must have a 'stage' field")); + return; + } + NSDictionary *args = stageMap[@"args"]; + if (![args isKindOfClass:[NSDictionary class]]) args = @{}; + + FIRStageBridge *stage = nil; + + if (i == 0) { + if ([stageName isEqualToString:@"collection"]) { + NSString *path = args[@"path"]; + if (!path) { + completion(nil, parseError(@"collection requires 'path'")); + return; + } + FIRCollectionReference *ref = [firestore collectionWithPath:path]; + stage = [[FIRCollectionSourceStageBridge alloc] initWithRef:ref firestore:firestore]; + } else if ([stageName isEqualToString:@"collection_group"]) { + NSString *path = args[@"path"]; + if (!path) { + completion(nil, parseError(@"collection_group requires 'path'")); + return; + } + stage = [[FIRCollectionGroupSourceStageBridge alloc] initWithCollectionId:path]; + } else if ([stageName isEqualToString:@"database"]) { + stage = [[FIRDatabaseSourceStageBridge alloc] init]; + } else if ([stageName isEqualToString:@"documents"]) { + id argsOrArray = stageMap[@"args"]; + NSArray *docMaps = [argsOrArray isKindOfClass:[NSArray class]] ? argsOrArray : nil; + if (!docMaps || docMaps.count == 0) { + completion(nil, parseError(@"documents requires array of document refs")); + return; + } + NSMutableArray *refs = [NSMutableArray array]; + for (id docMap in docMaps) { + if (![docMap isKindOfClass:[NSDictionary class]]) continue; + NSString *path = ((NSDictionary *)docMap)[@"path"]; + if (path) [refs addObject:[firestore documentWithPath:path]]; + } + stage = [[FIRDocumentsSourceStageBridge alloc] initWithDocuments:refs firestore:firestore]; + } else { + completion(nil, parseError([NSString + stringWithFormat:@"First stage must be collection, collection_group, " + @"documents, or database. Got: %@", + stageName])); + return; + } + } else { + if ([stageName isEqualToString:@"where"]) { + id exprMap = args[@"expression"]; + if (![exprMap isKindOfClass:[NSDictionary class]]) { + completion(nil, parseError(@"where requires expression")); + return; + } + FIRExprBridge *expr = [exprParser parseBooleanExpression:exprMap error:&parseErr]; + if (!expr) { + completion(nil, parseErr); + return; + } + stage = [[FIRWhereStageBridge alloc] initWithExpr:expr]; + } else if ([stageName isEqualToString:@"limit"]) { + NSNumber *limit = args[@"limit"]; + if (![limit isKindOfClass:[NSNumber class]]) { + completion(nil, parseError(@"limit requires numeric limit")); + return; + } + stage = [[FIRLimitStageBridge alloc] initWithLimit:limit.intValue]; + } else if ([stageName isEqualToString:@"offset"]) { + NSNumber *offset = args[@"offset"]; + if (![offset isKindOfClass:[NSNumber class]]) { + completion(nil, parseError(@"offset requires numeric offset")); + return; + } + stage = [[FIROffsetStageBridge alloc] initWithOffset:offset.intValue]; + } else if ([stageName isEqualToString:@"sort"]) { + NSArray *orderingMaps = args[@"orderings"]; + if (![orderingMaps isKindOfClass:[NSArray class]] || orderingMaps.count == 0) { + completion(nil, parseError(@"sort requires at least one ordering")); + return; + } + NSMutableArray *orderings = [NSMutableArray array]; + for (id om in orderingMaps) { + if (![om isKindOfClass:[NSDictionary class]]) continue; + id exprMap = ((NSDictionary *)om)[@"expression"]; + NSString *dir = ((NSDictionary *)om)[@"order_direction"]; + if (![exprMap isKindOfClass:[NSDictionary class]]) continue; + FIRExprBridge *expr = [exprParser parseExpression:exprMap error:&parseErr]; + if (!expr) { + completion(nil, parseErr); + return; + } + NSString *direction = [dir isEqualToString:@"asc"] ? @"ascending" : @"descending"; + FIROrderingBridge *ordering = [[FIROrderingBridge alloc] initWithExpr:expr + Direction:direction]; + [orderings addObject:ordering]; + } + if (orderings.count == 0) { + completion(nil, parseError(@"sort requires at least one ordering")); + return; + } + stage = [[FIRSorStageBridge alloc] initWithOrderings:orderings]; + } else if ([stageName isEqualToString:@"select"]) { + NSArray *exprMaps = args[@"expressions"]; + if (![exprMaps isKindOfClass:[NSArray class]] || exprMaps.count == 0) { + completion(nil, parseError(@"select requires at least one expression")); + return; + } + NSMutableDictionary *fields = [NSMutableDictionary dictionary]; + for (id em in exprMaps) { + if (![em isKindOfClass:[NSDictionary class]]) continue; + FIRExprBridge *expr = [exprParser parseExpression:em error:&parseErr]; + if (!expr) { + completion(nil, parseErr); + return; + } + NSString *alias = [em valueForKeyPath:@"args.alias"]; + if (alias) { + fields[alias] = expr; + } else { + NSString *fn = em[@"name"]; + if ([fn isEqualToString:@"field"]) { + NSString *field = [em valueForKeyPath:@"args.field"]; + fields[field ?: @"_"] = expr; + } else { + fields[[NSString stringWithFormat:@"_%lu", (unsigned long)fields.count]] = expr; + } + } + } + stage = [[FIRSelectStageBridge alloc] initWithSelections:fields]; + } else if ([stageName isEqualToString:@"add_fields"]) { + NSArray *exprMaps = args[@"expressions"]; + if (![exprMaps isKindOfClass:[NSArray class]] || exprMaps.count == 0) { + completion(nil, parseError(@"add_fields requires at least one expression")); + return; + } + NSMutableDictionary *fields = [NSMutableDictionary dictionary]; + for (id em in exprMaps) { + if (![em isKindOfClass:[NSDictionary class]]) continue; + FIRExprBridge *expr = [exprParser parseExpression:em error:&parseErr]; + if (!expr) { + completion(nil, parseErr); + return; + } + NSString *alias = [em valueForKeyPath:@"args.alias"]; + if (!alias) { + completion(nil, parseError(@"add_fields expressions must have alias")); + return; + } + fields[alias] = expr; + } + stage = [[FIRAddFieldsStageBridge alloc] initWithFields:fields]; + } else if ([stageName isEqualToString:@"remove_fields"]) { + NSArray *paths = args[@"field_paths"]; + if (![paths isKindOfClass:[NSArray class]] || paths.count == 0) { + completion(nil, parseError(@"remove_fields requires field_paths")); + return; + } + stage = [[FIRRemoveFieldsStageBridge alloc] initWithFields:paths]; + } else if ([stageName isEqualToString:@"distinct"]) { + NSArray *exprMaps = args[@"expressions"]; + if (![exprMaps isKindOfClass:[NSArray class]] || exprMaps.count == 0) { + completion(nil, parseError(@"distinct requires at least one expression")); + return; + } + NSMutableDictionary *fields = [NSMutableDictionary dictionary]; + for (NSUInteger j = 0; j < exprMaps.count; j++) { + id em = exprMaps[j]; + if (![em isKindOfClass:[NSDictionary class]]) continue; + FIRExprBridge *expr = [exprParser parseExpression:em error:&parseErr]; + if (!expr) { + completion(nil, parseErr); + return; + } + fields[[NSString stringWithFormat:@"_%lu", (unsigned long)j]] = expr; + } + stage = [[FIRDistinctStageBridge alloc] initWithGroups:fields]; + } else if ([stageName isEqualToString:@"replace_with"]) { + id exprMap = args[@"expression"]; + if (![exprMap isKindOfClass:[NSDictionary class]]) { + completion(nil, parseError(@"replace_with requires expression")); + return; + } + FIRExprBridge *expr = [exprParser parseExpression:exprMap error:&parseErr]; + if (!expr) { + completion(nil, parseErr); + return; + } + stage = [[FIRReplaceWithStageBridge alloc] initWithExpr:expr]; + } else if ([stageName isEqualToString:@"union"]) { + NSArray *nestedStages = args[@"pipeline"]; + if (![nestedStages isKindOfClass:[NSArray class]] || nestedStages.count == 0) { + completion(nil, parseError(@"union requires non-empty pipeline")); + return; + } + id otherPipeline = [self buildPipelineWithFirestore:firestore + stages:nestedStages + error:&parseErr]; + if (!otherPipeline) { + completion(nil, parseErr); + return; + } + stage = [[FIRUnionStageBridge alloc] initWithOther:otherPipeline]; + } else if ([stageName isEqualToString:@"sample"]) { + NSString *type = args[@"type"]; + id val = args[@"value"]; + if ([type isEqualToString:@"percentage"]) { + double v = [val isKindOfClass:[NSNumber class]] ? [(NSNumber *)val doubleValue] : 0; + stage = [[FIRSampleStageBridge alloc] initWithPercentage:v]; + } else { + int v = [val isKindOfClass:[NSNumber class]] ? [(NSNumber *)val intValue] : 0; + stage = [[FIRSampleStageBridge alloc] initWithCount:v]; + } + } else { + completion( + nil, parseError([NSString stringWithFormat:@"Unknown pipeline stage: %@", stageName])); + return; + } + } + + if (stage) [stageBridges addObject:stage]; + } + + if (stageBridges.count == 0) { + completion(nil, parseError(@"No valid stages")); + return; + } + + FIRPipelineBridge *pipeline = [[FIRPipelineBridge alloc] initWithStages:stageBridges + db:firestore]; + [pipeline executeWithCompletion:^(id snapshot, NSError *execError) { + if (execError) { + completion(nil, execError); + return; + } + completion(snapshot, nil); + }]; +} + ++ (id)buildPipelineWithFirestore:(FIRFirestore *)firestore + stages:(NSArray *> *)stages + error:(NSError **)error { + FLTPipelineExpressionParser *exprParser = [[FLTPipelineExpressionParser alloc] init]; + NSMutableArray *stageBridges = [NSMutableArray array]; + + for (NSUInteger i = 0; i < stages.count; i++) { + NSDictionary *stageMap = stages[i]; + if (![stageMap isKindOfClass:[NSDictionary class]]) { + if (error) *error = parseError(@"Stage must be a map"); + return nil; + } + NSString *stageName = stageMap[@"stage"]; + id argsObj = stageMap[@"args"]; + NSDictionary *args = [argsObj isKindOfClass:[NSDictionary class]] ? argsObj : @{}; + NSArray *argsArray = [argsObj isKindOfClass:[NSArray class]] ? argsObj : nil; + + FIRStageBridge *stage = nil; + + if (i == 0) { + if ([stageName isEqualToString:@"collection"]) { + NSString *path = args[@"path"]; + FIRCollectionReference *ref = [firestore collectionWithPath:path]; + stage = [[FIRCollectionSourceStageBridge alloc] initWithRef:ref firestore:firestore]; + } else if ([stageName isEqualToString:@"collection_group"]) { + stage = [[FIRCollectionGroupSourceStageBridge alloc] initWithCollectionId:args[@"path"]]; + } else if ([stageName isEqualToString:@"database"]) { + stage = [[FIRDatabaseSourceStageBridge alloc] init]; + } else if ([stageName isEqualToString:@"documents"]) { + NSArray *docMaps = argsArray ?: @[]; + NSMutableArray *refs = [NSMutableArray array]; + for (id docMap in docMaps) { + if ([docMap isKindOfClass:[NSDictionary class]] && ((NSDictionary *)docMap)[@"path"]) + [refs addObject:[firestore documentWithPath:((NSDictionary *)docMap)[@"path"]]]; + } + stage = [[FIRDocumentsSourceStageBridge alloc] initWithDocuments:refs firestore:firestore]; + } + } else { + NSError *parseErr = nil; + if ([stageName isEqualToString:@"where"]) { + FIRExprBridge *expr = [exprParser parseBooleanExpression:args[@"expression"] + error:&parseErr]; + if (expr) stage = [[FIRWhereStageBridge alloc] initWithExpr:expr]; + } else if ([stageName isEqualToString:@"limit"]) { + stage = [[FIRLimitStageBridge alloc] initWithLimit:[args[@"limit"] intValue]]; + } else if ([stageName isEqualToString:@"offset"]) { + stage = [[FIROffsetStageBridge alloc] initWithOffset:[args[@"offset"] intValue]]; + } else if ([stageName isEqualToString:@"sort"]) { + NSArray *orderingMaps = args[@"orderings"]; + if ([orderingMaps isKindOfClass:[NSArray class]] && orderingMaps.count > 0) { + NSMutableArray *orderings = [NSMutableArray array]; + for (id om in orderingMaps) { + if (![om isKindOfClass:[NSDictionary class]]) continue; + id exprMap = ((NSDictionary *)om)[@"expression"]; + NSString *dir = ((NSDictionary *)om)[@"order_direction"]; + if (![exprMap isKindOfClass:[NSDictionary class]]) continue; + FIRExprBridge *expr = [exprParser parseExpression:exprMap error:&parseErr]; + if (!expr) break; + NSString *direction = [dir isEqualToString:@"asc"] ? @"ascending" : @"descending"; + [orderings addObject:[[FIROrderingBridge alloc] initWithExpr:expr Direction:direction]]; + } + if (orderings.count > 0) { + stage = [[FIRSorStageBridge alloc] initWithOrderings:orderings]; + } + } + } else if ([stageName isEqualToString:@"union"]) { + id other = [self buildPipelineWithFirestore:firestore + stages:args[@"pipeline"] + error:&parseErr]; + if (other) stage = [[FIRUnionStageBridge alloc] initWithOther:other]; + } + if (parseErr && error) *error = parseErr; + } + + if (stage) [stageBridges addObject:stage]; + } + + if (stageBridges.count == 0) { + if (error && !*error) *error = parseError(@"No valid stages"); + return nil; + } + + return [[FIRPipelineBridge alloc] initWithStages:stageBridges db:firestore]; +} + +@end + +#else + +@implementation FLTPipelineParser + ++ (void)executePipelineWithFirestore:(FIRFirestore *)firestore + stages:(NSArray *> *)stages + options:(nullable NSDictionary *)options + completion:(void (^)(id _Nullable snapshot, + NSError *_Nullable error))completion { + completion(nil, pipelineUnavailableError()); +} + +@end + +#endif diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m index 0672709a2921..b14d9a2393a8 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m @@ -5,6 +5,8 @@ // See also: https://pub.dev/packages/pigeon #import "FirestoreMessages.g.h" +#import "FLTFirebaseFirestoreReader.h" +#import "FLTFirebaseFirestoreWriter.h" #if TARGET_OS_OSX #import @@ -359,7 +361,7 @@ + (instancetype)makeWithType:(DocumentChangeType)type pigeonResult.type = type; pigeonResult.document = document; pigeonResult.oldIndex = oldIndex; - pigeonResult.newIndex = newIndex; + pigeonResult.index = newIndex; return pigeonResult; } + (PigeonDocumentChange *)fromList:(NSArray *)list { @@ -370,8 +372,8 @@ + (PigeonDocumentChange *)fromList:(NSArray *)list { NSAssert(pigeonResult.document != nil, @""); pigeonResult.oldIndex = GetNullableObjectAtIndex(list, 2); NSAssert(pigeonResult.oldIndex != nil, @""); - pigeonResult.newIndex = GetNullableObjectAtIndex(list, 3); - NSAssert(pigeonResult.newIndex != nil, @""); + pigeonResult.index = GetNullableObjectAtIndex(list, 3); + NSAssert(pigeonResult.index != nil, @""); return pigeonResult; } + (nullable PigeonDocumentChange *)nullableFromList:(NSArray *)list { @@ -382,7 +384,7 @@ - (NSArray *)toList { @(self.type), (self.document ? [self.document toList] : [NSNull null]), (self.oldIndex ?: [NSNull null]), - (self.newIndex ?: [NSNull null]), + (self.index ?: [NSNull null]), ]; } @end @@ -719,7 +721,7 @@ - (NSArray *)toList { } @end -@interface FirebaseFirestoreHostApiCodecReader : FlutterStandardReader +@interface FirebaseFirestoreHostApiCodecReader : FLTFirebaseFirestoreReader @end @implementation FirebaseFirestoreHostApiCodecReader - (nullable id)readValueOfType:(UInt8)type { @@ -760,7 +762,7 @@ - (nullable id)readValueOfType:(UInt8)type { } @end -@interface FirebaseFirestoreHostApiCodecWriter : FlutterStandardWriter +@interface FirebaseFirestoreHostApiCodecWriter : FLTFirebaseFirestoreWriter @end @implementation FirebaseFirestoreHostApiCodecWriter - (void)writeValue:(id)value { diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Private/FLTPipelineParser.h b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Private/FLTPipelineParser.h new file mode 100644 index 000000000000..97c77f0e2a88 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Private/FLTPipelineParser.h @@ -0,0 +1,23 @@ +/* + * Copyright 2026, the Chromium project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +#import + +@class FIRFirestore; + +NS_ASSUME_NONNULL_BEGIN + +@interface FLTPipelineParser : NSObject + ++ (void)executePipelineWithFirestore:(FIRFirestore *)firestore + stages:(NSArray *> *)stages + options:(nullable NSDictionary *)options + completion: + (void (^)(id _Nullable snapshot, NSError *_Nullable error))completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h index 28885bdc9132..a435fda97bbd 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h @@ -231,7 +231,7 @@ typedef NS_ENUM(NSUInteger, AggregateType) { @property(nonatomic, assign) DocumentChangeType type; @property(nonatomic, strong) PigeonDocumentSnapshot *document; @property(nonatomic, strong) NSNumber *oldIndex; -@property(nonatomic, strong) NSNumber *newIndex; +@property(nonatomic, strong) NSNumber *index; @end @interface PigeonQuerySnapshot : NSObject diff --git a/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp b/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp index b52f92e5c3a2..10175dffd191 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp +++ b/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp @@ -1190,7 +1190,8 @@ EncodableValue FirebaseFirestoreHostApiCodecSerializer::ReadValueOfType( return CustomEncodableValue(PigeonTransactionCommand::FromEncodableList( std::get(ReadValue(stream)))); default: - return flutter::StandardCodecSerializer::ReadValueOfType(type, stream); + return cloud_firestore_windows::FirestoreCodec::ReadValueOfType(type, + stream); } } @@ -1319,7 +1320,7 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( return; } } - flutter::StandardCodecSerializer::WriteValue(value, stream); + cloud_firestore_windows::FirestoreCodec::WriteValue(value, stream); } /// The codec used by FirebaseFirestoreHostApi. diff --git a/packages/cloud_firestore/cloud_firestore/windows/messages.g.h b/packages/cloud_firestore/cloud_firestore/windows/messages.g.h index 3589afb9db14..739c8b2486ae 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/messages.g.h +++ b/packages/cloud_firestore/cloud_firestore/windows/messages.g.h @@ -15,6 +15,8 @@ #include #include +#include "firestore_codec.h" + namespace cloud_firestore_windows { // Generated class from Pigeon. @@ -237,10 +239,11 @@ class PigeonSnapshotMetadata { bool is_from_cache() const; void set_is_from_cache(bool value_arg); - private: static PigeonSnapshotMetadata FromEncodableList( const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; + + private: friend class PigeonDocumentSnapshot; friend class PigeonQuerySnapshot; friend class FirebaseFirestoreHostApi; @@ -271,10 +274,11 @@ class PigeonDocumentSnapshot { const PigeonSnapshotMetadata& metadata() const; void set_metadata(const PigeonSnapshotMetadata& value_arg); - private: static PigeonDocumentSnapshot FromEncodableList( const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; + + private: friend class PigeonDocumentChange; friend class FirebaseFirestoreHostApi; friend class FirebaseFirestoreHostApiCodecSerializer; @@ -303,10 +307,11 @@ class PigeonDocumentChange { int64_t new_index() const; void set_new_index(int64_t value_arg); - private: static PigeonDocumentChange FromEncodableList( const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; + + private: friend class FirebaseFirestoreHostApi; friend class FirebaseFirestoreHostApiCodecSerializer; DocumentChangeType type_; @@ -672,7 +677,7 @@ class AggregateQueryResponse { }; class FirebaseFirestoreHostApiCodecSerializer - : public flutter::StandardCodecSerializer { + : public cloud_firestore_windows::FirestoreCodec { public: FirebaseFirestoreHostApiCodecSerializer(); inline static FirebaseFirestoreHostApiCodecSerializer& GetInstance() { diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/generate_pigeon.sh b/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/generate_pigeon.sh index f6f0a2d4aef5..e8950e42ef53 100755 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/generate_pigeon.sh +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/generate_pigeon.sh @@ -15,7 +15,7 @@ sed -i '' 's/private static class FirebaseFirestoreHostApiCodec extends Standard echo "Android modification complete." # Fix iOS files -FILE_NAME="../../cloud_firestore/ios/Classes/FirestoreMessages.g.m" +FILE_NAME="../../cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m" sed -i '' '/#import "FirestoreMessages.g.h"/a\ #import "FLTFirebaseFirestoreReader.h"\ #import "FLTFirebaseFirestoreWriter.h" @@ -26,7 +26,7 @@ sed -i '' 's/(self\.newIndex \?: \[NSNull null\]),/(self.index ?: [NSNull null]) sed -i '' 's/@interface FirebaseFirestoreHostApiCodecReader : FlutterStandardReader/@interface FirebaseFirestoreHostApiCodecReader : FLTFirebaseFirestoreReader/' $FILE_NAME sed -i '' 's/@interface FirebaseFirestoreHostApiCodecWriter : FlutterStandardWriter/@interface FirebaseFirestoreHostApiCodecWriter : FLTFirebaseFirestoreWriter/' $FILE_NAME -FILE_NAME="../../cloud_firestore/ios/Classes/Public/FirestoreMessages.g.h" +FILE_NAME="../../cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h" sed -i '' 's/@property(nonatomic, strong) NSNumber \*newIndex;/@property(nonatomic, strong) NSNumber \*index;/' $FILE_NAME echo "iOS modification complete." diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/test/pigeon/test_api.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/test/pigeon/test_api.dart index 67978a0a8f2c..e9ed58eafc5c 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/test/pigeon/test_api.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/test/pigeon/test_api.dart @@ -6,7 +6,7 @@ // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import // ignore_for_file: avoid_relative_lib_imports import 'dart:async'; -import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; +import 'dart:typed_data' show Uint8List; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -115,7 +115,10 @@ abstract class TestFirebaseFirestoreHostApi { Future loadBundle(FirestorePigeonFirebaseApp app, Uint8List bundle); Future namedQueryGet( - FirestorePigeonFirebaseApp app, String name, PigeonGetOptions options); + FirestorePigeonFirebaseApp app, + String name, + PigeonGetOptions options, + ); Future clearPersistence(FirestorePigeonFirebaseApp app); @@ -128,82 +131,112 @@ abstract class TestFirebaseFirestoreHostApi { Future waitForPendingWrites(FirestorePigeonFirebaseApp app); Future setIndexConfiguration( - FirestorePigeonFirebaseApp app, String indexConfiguration); + FirestorePigeonFirebaseApp app, + String indexConfiguration, + ); Future setLoggingEnabled(bool loggingEnabled); Future snapshotsInSyncSetup(FirestorePigeonFirebaseApp app); Future transactionCreate( - FirestorePigeonFirebaseApp app, int timeout, int maxAttempts); + FirestorePigeonFirebaseApp app, + int timeout, + int maxAttempts, + ); Future transactionStoreResult( - String transactionId, - PigeonTransactionResult resultType, - List? commands); + String transactionId, + PigeonTransactionResult resultType, + List? commands, + ); Future transactionGet( - FirestorePigeonFirebaseApp app, String transactionId, String path); + FirestorePigeonFirebaseApp app, + String transactionId, + String path, + ); Future documentReferenceSet( - FirestorePigeonFirebaseApp app, DocumentReferenceRequest request); + FirestorePigeonFirebaseApp app, + DocumentReferenceRequest request, + ); Future documentReferenceUpdate( - FirestorePigeonFirebaseApp app, DocumentReferenceRequest request); + FirestorePigeonFirebaseApp app, + DocumentReferenceRequest request, + ); Future documentReferenceGet( - FirestorePigeonFirebaseApp app, DocumentReferenceRequest request); + FirestorePigeonFirebaseApp app, + DocumentReferenceRequest request, + ); Future documentReferenceDelete( - FirestorePigeonFirebaseApp app, DocumentReferenceRequest request); + FirestorePigeonFirebaseApp app, + DocumentReferenceRequest request, + ); Future queryGet( - FirestorePigeonFirebaseApp app, - String path, - bool isCollectionGroup, - PigeonQueryParameters parameters, - PigeonGetOptions options); + FirestorePigeonFirebaseApp app, + String path, + bool isCollectionGroup, + PigeonQueryParameters parameters, + PigeonGetOptions options, + ); Future> aggregateQuery( - FirestorePigeonFirebaseApp app, - String path, - PigeonQueryParameters parameters, - AggregateSource source, - List queries, - bool isCollectionGroup); + FirestorePigeonFirebaseApp app, + String path, + PigeonQueryParameters parameters, + AggregateSource source, + List queries, + bool isCollectionGroup, + ); Future writeBatchCommit( - FirestorePigeonFirebaseApp app, List writes); + FirestorePigeonFirebaseApp app, + List writes, + ); Future querySnapshot( - FirestorePigeonFirebaseApp app, - String path, - bool isCollectionGroup, - PigeonQueryParameters parameters, - PigeonGetOptions options, - bool includeMetadataChanges, - ListenSource source); + FirestorePigeonFirebaseApp app, + String path, + bool isCollectionGroup, + PigeonQueryParameters parameters, + PigeonGetOptions options, + bool includeMetadataChanges, + ListenSource source, + ); Future documentReferenceSnapshot( - FirestorePigeonFirebaseApp app, - DocumentReferenceRequest parameters, - bool includeMetadataChanges, - ListenSource source); + FirestorePigeonFirebaseApp app, + DocumentReferenceRequest parameters, + bool includeMetadataChanges, + ListenSource source, + ); Future persistenceCacheIndexManagerRequest( - FirestorePigeonFirebaseApp app, - PersistenceCacheIndexManagerRequest request); + FirestorePigeonFirebaseApp app, + PersistenceCacheIndexManagerRequest request, + ); - Future executePipeline(FirestorePigeonFirebaseApp app, - List?> stages, Map? options); + Future executePipeline( + FirestorePigeonFirebaseApp app, + List?> stages, + Map? options, + ); - static void setup(TestFirebaseFirestoreHostApi? api, - {BinaryMessenger? binaryMessenger}) { + static void setup( + TestFirebaseFirestoreHostApi? api, { + BinaryMessenger? binaryMessenger, + }) { { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -211,16 +244,22 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null, expected non-null FirestorePigeonFirebaseApp.', + ); final Uint8List? arg_bundle = (args[1] as Uint8List?); - assert(arg_bundle != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null, expected non-null Uint8List.'); + assert( + arg_bundle != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null, expected non-null Uint8List.', + ); final String output = await api.loadBundle(arg_app!, arg_bundle!); return [output]; }); @@ -228,9 +267,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -238,19 +278,27 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null FirestorePigeonFirebaseApp.', + ); final String? arg_name = (args[1] as String?); - assert(arg_name != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null String.'); + assert( + arg_name != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null String.', + ); final PigeonGetOptions? arg_options = (args[2] as PigeonGetOptions?); - assert(arg_options != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null PigeonGetOptions.'); + assert( + arg_options != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null PigeonGetOptions.', + ); final PigeonQuerySnapshot output = await api.namedQueryGet(arg_app!, arg_name!, arg_options!); return [output]; @@ -259,9 +307,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -269,13 +318,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence was null, expected non-null FirestorePigeonFirebaseApp.', + ); await api.clearPersistence(arg_app!); return []; }); @@ -283,9 +336,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -293,13 +347,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork was null, expected non-null FirestorePigeonFirebaseApp.', + ); await api.disableNetwork(arg_app!); return []; }); @@ -307,9 +365,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -317,13 +376,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork was null, expected non-null FirestorePigeonFirebaseApp.', + ); await api.enableNetwork(arg_app!); return []; }); @@ -331,9 +394,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -341,13 +405,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate was null, expected non-null FirestorePigeonFirebaseApp.', + ); await api.terminate(arg_app!); return []; }); @@ -355,9 +423,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -365,13 +434,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites was null, expected non-null FirestorePigeonFirebaseApp.', + ); await api.waitForPendingWrites(arg_app!); return []; }); @@ -379,9 +452,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -389,16 +463,22 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null, expected non-null FirestorePigeonFirebaseApp.', + ); final String? arg_indexConfiguration = (args[1] as String?); - assert(arg_indexConfiguration != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null, expected non-null String.'); + assert( + arg_indexConfiguration != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null, expected non-null String.', + ); await api.setIndexConfiguration(arg_app!, arg_indexConfiguration!); return []; }); @@ -406,9 +486,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -416,12 +497,16 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled was null.', + ); final List args = (message as List?)!; final bool? arg_loggingEnabled = (args[0] as bool?); - assert(arg_loggingEnabled != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled was null, expected non-null bool.'); + assert( + arg_loggingEnabled != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled was null, expected non-null bool.', + ); await api.setLoggingEnabled(arg_loggingEnabled!); return []; }); @@ -429,9 +514,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -439,13 +525,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup was null, expected non-null FirestorePigeonFirebaseApp.', + ); final String output = await api.snapshotsInSyncSetup(arg_app!); return [output]; }); @@ -453,9 +543,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -463,30 +554,42 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null FirestorePigeonFirebaseApp.', + ); final int? arg_timeout = (args[1] as int?); - assert(arg_timeout != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null int.'); + assert( + arg_timeout != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null int.', + ); final int? arg_maxAttempts = (args[2] as int?); - assert(arg_maxAttempts != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null int.'); + assert( + arg_maxAttempts != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null int.', + ); final String output = await api.transactionCreate( - arg_app!, arg_timeout!, arg_maxAttempts!); + arg_app!, + arg_timeout!, + arg_maxAttempts!, + ); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -494,30 +597,40 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null.', + ); final List args = (message as List?)!; final String? arg_transactionId = (args[0] as String?); - assert(arg_transactionId != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null, expected non-null String.'); + assert( + arg_transactionId != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null, expected non-null String.', + ); final PigeonTransactionResult? arg_resultType = args[1] == null ? null : PigeonTransactionResult.values[args[1]! as int]; - assert(arg_resultType != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null, expected non-null PigeonTransactionResult.'); + assert( + arg_resultType != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null, expected non-null PigeonTransactionResult.', + ); final List? arg_commands = (args[2] as List?)?.cast(); await api.transactionStoreResult( - arg_transactionId!, arg_resultType!, arg_commands); + arg_transactionId!, + arg_resultType!, + arg_commands, + ); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -525,19 +638,27 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null FirestorePigeonFirebaseApp.', + ); final String? arg_transactionId = (args[1] as String?); - assert(arg_transactionId != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null String.'); + assert( + arg_transactionId != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null String.', + ); final String? arg_path = (args[2] as String?); - assert(arg_path != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null String.'); + assert( + arg_path != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null String.', + ); final PigeonDocumentSnapshot output = await api.transactionGet(arg_app!, arg_transactionId!, arg_path!); return [output]; @@ -546,9 +667,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -556,17 +678,23 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null, expected non-null FirestorePigeonFirebaseApp.', + ); final DocumentReferenceRequest? arg_request = (args[1] as DocumentReferenceRequest?); - assert(arg_request != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null, expected non-null DocumentReferenceRequest.'); + assert( + arg_request != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null, expected non-null DocumentReferenceRequest.', + ); await api.documentReferenceSet(arg_app!, arg_request!); return []; }); @@ -574,9 +702,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -584,17 +713,23 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null, expected non-null FirestorePigeonFirebaseApp.', + ); final DocumentReferenceRequest? arg_request = (args[1] as DocumentReferenceRequest?); - assert(arg_request != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null, expected non-null DocumentReferenceRequest.'); + assert( + arg_request != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null, expected non-null DocumentReferenceRequest.', + ); await api.documentReferenceUpdate(arg_app!, arg_request!); return []; }); @@ -602,9 +737,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -612,17 +748,23 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null, expected non-null FirestorePigeonFirebaseApp.', + ); final DocumentReferenceRequest? arg_request = (args[1] as DocumentReferenceRequest?); - assert(arg_request != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null, expected non-null DocumentReferenceRequest.'); + assert( + arg_request != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null, expected non-null DocumentReferenceRequest.', + ); final PigeonDocumentSnapshot output = await api.documentReferenceGet(arg_app!, arg_request!); return [output]; @@ -631,9 +773,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -641,17 +784,23 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null, expected non-null FirestorePigeonFirebaseApp.', + ); final DocumentReferenceRequest? arg_request = (args[1] as DocumentReferenceRequest?); - assert(arg_request != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null, expected non-null DocumentReferenceRequest.'); + assert( + arg_request != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null, expected non-null DocumentReferenceRequest.', + ); await api.documentReferenceDelete(arg_app!, arg_request!); return []; }); @@ -659,9 +808,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -669,37 +819,55 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null FirestorePigeonFirebaseApp.', + ); final String? arg_path = (args[1] as String?); - assert(arg_path != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null String.'); + assert( + arg_path != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null String.', + ); final bool? arg_isCollectionGroup = (args[2] as bool?); - assert(arg_isCollectionGroup != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null bool.'); + assert( + arg_isCollectionGroup != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null bool.', + ); final PigeonQueryParameters? arg_parameters = (args[3] as PigeonQueryParameters?); - assert(arg_parameters != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null PigeonQueryParameters.'); + assert( + arg_parameters != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null PigeonQueryParameters.', + ); final PigeonGetOptions? arg_options = (args[4] as PigeonGetOptions?); - assert(arg_options != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null PigeonGetOptions.'); - final PigeonQuerySnapshot output = await api.queryGet(arg_app!, - arg_path!, arg_isCollectionGroup!, arg_parameters!, arg_options!); + assert( + arg_options != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null PigeonGetOptions.', + ); + final PigeonQuerySnapshot output = await api.queryGet( + arg_app!, + arg_path!, + arg_isCollectionGroup!, + arg_parameters!, + arg_options!, + ); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -707,47 +875,63 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null FirestorePigeonFirebaseApp.', + ); final String? arg_path = (args[1] as String?); - assert(arg_path != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null String.'); + assert( + arg_path != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null String.', + ); final PigeonQueryParameters? arg_parameters = (args[2] as PigeonQueryParameters?); - assert(arg_parameters != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null PigeonQueryParameters.'); + assert( + arg_parameters != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null PigeonQueryParameters.', + ); final AggregateSource? arg_source = args[3] == null ? null : AggregateSource.values[args[3]! as int]; - assert(arg_source != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null AggregateSource.'); + assert( + arg_source != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null AggregateSource.', + ); final List? arg_queries = (args[4] as List?)?.cast(); - assert(arg_queries != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null List.'); + assert( + arg_queries != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null List.', + ); final bool? arg_isCollectionGroup = (args[5] as bool?); - assert(arg_isCollectionGroup != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null bool.'); + assert( + arg_isCollectionGroup != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null bool.', + ); final List output = await api.aggregateQuery( - arg_app!, - arg_path!, - arg_parameters!, - arg_source!, - arg_queries!, - arg_isCollectionGroup!); + arg_app!, + arg_path!, + arg_parameters!, + arg_source!, + arg_queries!, + arg_isCollectionGroup!, + ); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -755,17 +939,23 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null, expected non-null FirestorePigeonFirebaseApp.', + ); final List? arg_writes = (args[1] as List?)?.cast(); - assert(arg_writes != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null, expected non-null List.'); + assert( + arg_writes != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null, expected non-null List.', + ); await api.writeBatchCommit(arg_app!, arg_writes!); return []; }); @@ -773,9 +963,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -783,50 +974,68 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null FirestorePigeonFirebaseApp.', + ); final String? arg_path = (args[1] as String?); - assert(arg_path != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null String.'); + assert( + arg_path != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null String.', + ); final bool? arg_isCollectionGroup = (args[2] as bool?); - assert(arg_isCollectionGroup != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null bool.'); + assert( + arg_isCollectionGroup != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null bool.', + ); final PigeonQueryParameters? arg_parameters = (args[3] as PigeonQueryParameters?); - assert(arg_parameters != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null PigeonQueryParameters.'); + assert( + arg_parameters != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null PigeonQueryParameters.', + ); final PigeonGetOptions? arg_options = (args[4] as PigeonGetOptions?); - assert(arg_options != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null PigeonGetOptions.'); + assert( + arg_options != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null PigeonGetOptions.', + ); final bool? arg_includeMetadataChanges = (args[5] as bool?); - assert(arg_includeMetadataChanges != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null bool.'); + assert( + arg_includeMetadataChanges != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null bool.', + ); final ListenSource? arg_source = args[6] == null ? null : ListenSource.values[args[6]! as int]; - assert(arg_source != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null ListenSource.'); + assert( + arg_source != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null ListenSource.', + ); final String output = await api.querySnapshot( - arg_app!, - arg_path!, - arg_isCollectionGroup!, - arg_parameters!, - arg_options!, - arg_includeMetadataChanges!, - arg_source!); + arg_app!, + arg_path!, + arg_isCollectionGroup!, + arg_parameters!, + arg_options!, + arg_includeMetadataChanges!, + arg_source!, + ); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -834,35 +1043,50 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null FirestorePigeonFirebaseApp.', + ); final DocumentReferenceRequest? arg_parameters = (args[1] as DocumentReferenceRequest?); - assert(arg_parameters != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null DocumentReferenceRequest.'); + assert( + arg_parameters != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null DocumentReferenceRequest.', + ); final bool? arg_includeMetadataChanges = (args[2] as bool?); - assert(arg_includeMetadataChanges != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null bool.'); + assert( + arg_includeMetadataChanges != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null bool.', + ); final ListenSource? arg_source = args[3] == null ? null : ListenSource.values[args[3]! as int]; - assert(arg_source != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null ListenSource.'); - final String output = await api.documentReferenceSnapshot(arg_app!, - arg_parameters!, arg_includeMetadataChanges!, arg_source!); + assert( + arg_source != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null ListenSource.', + ); + final String output = await api.documentReferenceSnapshot( + arg_app!, + arg_parameters!, + arg_includeMetadataChanges!, + arg_source!, + ); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -870,19 +1094,25 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null, expected non-null FirestorePigeonFirebaseApp.', + ); final PersistenceCacheIndexManagerRequest? arg_request = args[1] == null ? null : PersistenceCacheIndexManagerRequest.values[args[1]! as int]; - assert(arg_request != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null, expected non-null PersistenceCacheIndexManagerRequest.'); + assert( + arg_request != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null, expected non-null PersistenceCacheIndexManagerRequest.', + ); await api.persistenceCacheIndexManagerRequest(arg_app!, arg_request!); return []; }); @@ -890,9 +1120,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -900,17 +1131,23 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline was null, expected non-null FirestorePigeonFirebaseApp.', + ); final List?>? arg_stages = (args[1] as List?)?.cast?>(); - assert(arg_stages != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline was null, expected non-null List?>.'); + assert( + arg_stages != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline was null, expected non-null List?>.', + ); final Map? arg_options = (args[2] as Map?)?.cast(); final PigeonPipelineSnapshot output = From d7bc27c470350031900e3a4b6f731adf53797020 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Wed, 25 Feb 2026 14:11:05 +0000 Subject: [PATCH 11/72] refactor: move aggregate classes to pipeline_aggregate.dart --- .../lib/src/pipeline_aggregate.dart | 95 ++++++++++++++++++ .../lib/src/pipeline_expression.dart | 99 ------------------- 2 files changed, 95 insertions(+), 99 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart index 403d26b7f3a4..c3ab5a96de41 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart @@ -75,6 +75,101 @@ class Count extends PipelineAggregateFunction { } } +/// Sums numeric values of the specified expression +class Sum extends PipelineAggregateFunction { + final Expression expression; + + Sum(this.expression); + + @override + String get name => 'sum'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} + +/// Calculates average of numeric values of the specified expression +class Average extends PipelineAggregateFunction { + final Expression expression; + + Average(this.expression); + + @override + String get name => 'average'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} + +/// Counts distinct values of the specified expression +class CountDistinct extends PipelineAggregateFunction { + final Expression expression; + + CountDistinct(this.expression); + + @override + String get name => 'count_distinct'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} + +/// Finds minimum value of the specified expression +class Minimum extends PipelineAggregateFunction { + final Expression expression; + + Minimum(this.expression); + + @override + String get name => 'minimum'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} + +/// Finds maximum value of the specified expression +class Maximum extends PipelineAggregateFunction { + final Expression expression; + + Maximum(this.expression); + + @override + String get name => 'maximum'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} + /// Represents an aggregate stage with functions and optional grouping class AggregateStage implements PipelineSerializable { final List accumulators; diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart index b8a9fc5d9200..6f740f548c54 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart @@ -2905,102 +2905,3 @@ class _RawFunctionExpression extends FunctionExpression { }; } } - -// ============================================================================ -// AGGREGATE FUNCTION CLASSES -// ============================================================================ - -/// Sums numeric values of the specified expression -class Sum extends PipelineAggregateFunction { - final Expression expression; - - Sum(this.expression); - - @override - String get name => 'sum'; - - @override - Map toMap() { - final map = super.toMap(); - map['args'] = { - 'expression': expression.toMap(), - }; - return map; - } -} - -/// Calculates average of numeric values of the specified expression -class Average extends PipelineAggregateFunction { - final Expression expression; - - Average(this.expression); - - @override - String get name => 'average'; - - @override - Map toMap() { - final map = super.toMap(); - map['args'] = { - 'expression': expression.toMap(), - }; - return map; - } -} - -/// Counts distinct values of the specified expression -class CountDistinct extends PipelineAggregateFunction { - final Expression expression; - - CountDistinct(this.expression); - - @override - String get name => 'count_distinct'; - - @override - Map toMap() { - final map = super.toMap(); - map['args'] = { - 'expression': expression.toMap(), - }; - return map; - } -} - -/// Finds minimum value of the specified expression -class Minimum extends PipelineAggregateFunction { - final Expression expression; - - Minimum(this.expression); - - @override - String get name => 'minimum'; - - @override - Map toMap() { - final map = super.toMap(); - map['args'] = { - 'expression': expression.toMap(), - }; - return map; - } -} - -/// Finds maximum value of the specified expression -class Maximum extends PipelineAggregateFunction { - final Expression expression; - - Maximum(this.expression); - - @override - String get name => 'maximum'; - - @override - Map toMap() { - final map = super.toMap(); - map['args'] = { - 'expression': expression.toMap(), - }; - return map; - } -} From 85d87e65451bb65adb159bf0dd779b626a84cca3 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Wed, 25 Feb 2026 14:12:44 +0000 Subject: [PATCH 12/72] feat: enhance expression parsing in FLTPipelineParser to support additional binary and unary operations --- .../cloud_firestore/FLTPipelineParser.m | 532 ++++++++++++------ 1 file changed, 362 insertions(+), 170 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m index 620d8ee7bc2d..b4f46889cf87 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m @@ -85,11 +85,22 @@ - (FIRExprBridge *)parseExpression:(NSDictionary *)map error:(NS return [self parseExpression:exprMap error:error]; } - NSArray *binaryNames = @[ - @"equal", @"not_equal", @"greater_than", @"greater_than_or_equal", @"less_than", - @"less_than_or_equal", @"add", @"subtract", @"multiply", @"divide", @"modulo" - ]; - if ([binaryNames containsObject:name]) { + // Map Dart names to iOS SDK names where they differ + NSString *sdkName = name; + if ([name isEqualToString:@"bit_xor"]) sdkName = @"xor"; + + // ------------------------------------------------------------------------- + // Binary expressions (left + right): comparisons, arithmetic, xor + // ------------------------------------------------------------------------- + static NSArray *binaryNames = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + binaryNames = @[ + @"equal", @"not_equal", @"greater_than", @"greater_than_or_equal", @"less_than", + @"less_than_or_equal", @"add", @"subtract", @"multiply", @"divide", @"modulo", @"xor" + ]; + }); + if ([binaryNames containsObject:sdkName] || [name isEqualToString:@"bit_xor"]) { id leftMap = args[@"left"]; id rightMap = args[@"right"]; if (![leftMap isKindOfClass:[NSDictionary class]] || @@ -102,9 +113,29 @@ - (FIRExprBridge *)parseExpression:(NSDictionary *)map error:(NS FIRExprBridge *left = [self parseExpression:leftMap error:error]; FIRExprBridge *right = [self parseExpression:rightMap error:error]; if (!left || !right) return nil; - return [[FIRFunctionExprBridge alloc] initWithName:name Args:@[ left, right ]]; + return [[FIRFunctionExprBridge alloc] initWithName:sdkName Args:@[ left, right ]]; } + // ------------------------------------------------------------------------- + // Unary expressions (single expression): exists, is_error, is_absent, not + // ------------------------------------------------------------------------- + NSArray *unaryNames = @[ @"exists", @"is_error", @"is_absent", @"not" ]; + if ([unaryNames containsObject:name]) { + id exprMap = args[@"expression"]; + if (![exprMap isKindOfClass:[NSDictionary class]]) { + if (error) *error = parseError([NSString stringWithFormat:@"%@ requires expression", name]); + return nil; + } + FIRExprBridge *expr = [name isEqualToString:@"not"] + ? [self parseBooleanExpression:exprMap error:error] + : [self parseExpression:exprMap error:error]; + if (!expr) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:name Args:@[ expr ]]; + } + + // ------------------------------------------------------------------------- + // N-ary logical (expressions array): and, or + // ------------------------------------------------------------------------- if ([name isEqualToString:@"and"] || [name isEqualToString:@"or"]) { NSArray *exprMaps = args[@"expressions"]; if (![exprMaps isKindOfClass:[NSArray class]] || exprMaps.count == 0) { @@ -113,26 +144,101 @@ - (FIRExprBridge *)parseExpression:(NSDictionary *)map error:(NS parseError([NSString stringWithFormat:@"%@ requires at least one expression", name]); return nil; } - FIRExprBridge *first = [self parseBooleanExpression:exprMaps[0] error:error]; - if (!first) return nil; - NSMutableArray *all = [NSMutableArray arrayWithObject:first]; - for (NSUInteger i = 1; i < exprMaps.count; i++) { - FIRExprBridge *next = [self parseBooleanExpression:exprMaps[i] error:error]; - if (!next) return nil; - [all addObject:next]; + NSMutableArray *all = [NSMutableArray array]; + for (id em in exprMaps) { + if (![em isKindOfClass:[NSDictionary class]]) continue; + FIRExprBridge *e = [self parseBooleanExpression:em error:error]; + if (!e) return nil; + [all addObject:e]; + } + if (all.count == 0) { + if (error) + *error = + parseError([NSString stringWithFormat:@"%@ requires at least one expression", name]); + return nil; } return [[FIRFunctionExprBridge alloc] initWithName:name Args:all]; } - if ([name isEqualToString:@"not"]) { - id exprMap = args[@"expression"]; - if (![exprMap isKindOfClass:[NSDictionary class]]) { - if (error) *error = parseError(@"not requires expression"); + // ------------------------------------------------------------------------- + // value + values[]: equal_any, not_equal_any + // ------------------------------------------------------------------------- + if ([name isEqualToString:@"equal_any"] || [name isEqualToString:@"not_equal_any"]) { + id valueMap = args[@"value"]; + NSArray *valuesMaps = args[@"values"]; + if (![valueMap isKindOfClass:[NSDictionary class]] || + ![valuesMaps isKindOfClass:[NSArray class]] || valuesMaps.count == 0) { + if (error) + *error = + parseError([NSString stringWithFormat:@"%@ requires value and non-empty values", name]); return nil; } - FIRExprBridge *expr = [self parseBooleanExpression:exprMap error:error]; - if (!expr) return nil; - return [[FIRFunctionExprBridge alloc] initWithName:@"not" Args:@[ expr ]]; + FIRExprBridge *valueExpr = [self parseExpression:valueMap error:error]; + if (!valueExpr) return nil; + NSMutableArray *valueExprs = [NSMutableArray array]; + for (id vm in valuesMaps) { + if (![vm isKindOfClass:[NSDictionary class]]) continue; + FIRExprBridge *ve = [self parseExpression:vm error:error]; + if (!ve) return nil; + [valueExprs addObject:ve]; + } + if (valueExprs.count == 0) { + if (error) + *error = parseError([NSString stringWithFormat:@"%@ requires at least one value", name]); + return nil; + } + NSMutableArray *argsArray = [NSMutableArray arrayWithObject:valueExpr]; + [argsArray addObjectsFromArray:valueExprs]; + return [[FIRFunctionExprBridge alloc] initWithName:name Args:argsArray]; + } + + // ------------------------------------------------------------------------- + // array + element: array_contains + // ------------------------------------------------------------------------- + if ([name isEqualToString:@"array_contains"]) { + id arrayMap = args[@"array"]; + id elementMap = args[@"element"]; + if (![arrayMap isKindOfClass:[NSDictionary class]] || + ![elementMap isKindOfClass:[NSDictionary class]]) { + if (error) *error = parseError(@"array_contains requires array and element"); + return nil; + } + FIRExprBridge *arrayExpr = [self parseExpression:arrayMap error:error]; + FIRExprBridge *elementExpr = [self parseExpression:elementMap error:error]; + if (!arrayExpr || !elementExpr) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:name Args:@[ arrayExpr, elementExpr ]]; + } + + // ------------------------------------------------------------------------- + // array + values[]: array_contains_all, array_contains_any + // ------------------------------------------------------------------------- + if ([name isEqualToString:@"array_contains_all"] || + [name isEqualToString:@"array_contains_any"]) { + id arrayMap = args[@"array"]; + NSArray *valuesMaps = args[@"values"]; + if (![valuesMaps isKindOfClass:[NSArray class]]) valuesMaps = args[@"elements"]; + if (![arrayMap isKindOfClass:[NSDictionary class]]) { + if (error) *error = parseError([NSString stringWithFormat:@"%@ requires array", name]); + return nil; + } + FIRExprBridge *arrayExpr = [self parseExpression:arrayMap error:error]; + if (!arrayExpr) return nil; + NSMutableArray *argsArray = [NSMutableArray arrayWithObject:arrayExpr]; + if ([valuesMaps isKindOfClass:[NSArray class]]) { + for (id vm in valuesMaps) { + if (![vm isKindOfClass:[NSDictionary class]]) continue; + FIRExprBridge *ve = [self parseExpression:vm error:error]; + if (!ve) return nil; + [argsArray addObject:ve]; + } + } + if (argsArray.count < 2) { + if (error) + *error = parseError( + [NSString stringWithFormat:@"%@ requires array and at least one value", name]); + return nil; + } + return [[FIRFunctionExprBridge alloc] initWithName:name Args:argsArray]; } if (error) *error = parseError([NSString stringWithFormat:@"Unsupported expression: %@", name]); @@ -148,16 +254,10 @@ - (FIRExprBridge *)parseBooleanExpression:(NSDictionary *)map @implementation FLTPipelineParser -+ (void)executePipelineWithFirestore:(FIRFirestore *)firestore - stages:(NSArray *> *)stages - options:(nullable NSDictionary *)options - completion:(void (^)(id _Nullable snapshot, - NSError *_Nullable error))completion { - if (!stages || stages.count == 0) { - completion(nil, parseError(@"Pipeline requires at least one stage")); - return; - } - ++ (NSArray *) + parseStagesWithFirestore:(FIRFirestore *)firestore + stages:(NSArray *> *)stages + error:(NSError **)error { FLTPipelineExpressionParser *exprParser = [[FLTPipelineExpressionParser alloc] init]; NSMutableArray *stageBridges = [NSMutableArray array]; NSError *parseErr = nil; @@ -165,16 +265,17 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore for (NSUInteger i = 0; i < stages.count; i++) { NSDictionary *stageMap = stages[i]; if (![stageMap isKindOfClass:[NSDictionary class]]) { - completion(nil, parseError(@"Stage must be a map")); - return; + if (error) *error = parseError(@"Stage must be a map"); + return nil; } NSString *stageName = stageMap[@"stage"]; if (![stageName isKindOfClass:[NSString class]]) { - completion(nil, parseError(@"Stage must have a 'stage' field")); - return; + if (error) *error = parseError(@"Stage must have a 'stage' field"); + return nil; } - NSDictionary *args = stageMap[@"args"]; - if (![args isKindOfClass:[NSDictionary class]]) args = @{}; + id argsObj = stageMap[@"args"]; + NSDictionary *args = [argsObj isKindOfClass:[NSDictionary class]] ? argsObj : @{}; + NSArray *argsArray = [argsObj isKindOfClass:[NSArray class]] ? argsObj : nil; FIRStageBridge *stage = nil; @@ -182,26 +283,25 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore if ([stageName isEqualToString:@"collection"]) { NSString *path = args[@"path"]; if (!path) { - completion(nil, parseError(@"collection requires 'path'")); - return; + if (error) *error = parseError(@"collection requires 'path'"); + return nil; } FIRCollectionReference *ref = [firestore collectionWithPath:path]; stage = [[FIRCollectionSourceStageBridge alloc] initWithRef:ref firestore:firestore]; } else if ([stageName isEqualToString:@"collection_group"]) { NSString *path = args[@"path"]; if (!path) { - completion(nil, parseError(@"collection_group requires 'path'")); - return; + if (error) *error = parseError(@"collection_group requires 'path'"); + return nil; } stage = [[FIRCollectionGroupSourceStageBridge alloc] initWithCollectionId:path]; } else if ([stageName isEqualToString:@"database"]) { stage = [[FIRDatabaseSourceStageBridge alloc] init]; } else if ([stageName isEqualToString:@"documents"]) { - id argsOrArray = stageMap[@"args"]; - NSArray *docMaps = [argsOrArray isKindOfClass:[NSArray class]] ? argsOrArray : nil; + NSArray *docMaps = argsArray; if (!docMaps || docMaps.count == 0) { - completion(nil, parseError(@"documents requires array of document refs")); - return; + if (error) *error = parseError(@"documents requires array of document refs"); + return nil; } NSMutableArray *refs = [NSMutableArray array]; for (id docMap in docMaps) { @@ -211,44 +311,45 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore } stage = [[FIRDocumentsSourceStageBridge alloc] initWithDocuments:refs firestore:firestore]; } else { - completion(nil, parseError([NSString - stringWithFormat:@"First stage must be collection, collection_group, " - @"documents, or database. Got: %@", - stageName])); - return; + if (error) + *error = parseError( + [NSString stringWithFormat:@"First stage must be collection, collection_group, " + @"documents, or database. Got: %@", + stageName]); + return nil; } } else { if ([stageName isEqualToString:@"where"]) { id exprMap = args[@"expression"]; if (![exprMap isKindOfClass:[NSDictionary class]]) { - completion(nil, parseError(@"where requires expression")); - return; + if (error) *error = parseError(@"where requires expression"); + return nil; } FIRExprBridge *expr = [exprParser parseBooleanExpression:exprMap error:&parseErr]; if (!expr) { - completion(nil, parseErr); - return; + if (error) *error = parseErr; + return nil; } stage = [[FIRWhereStageBridge alloc] initWithExpr:expr]; } else if ([stageName isEqualToString:@"limit"]) { NSNumber *limit = args[@"limit"]; if (![limit isKindOfClass:[NSNumber class]]) { - completion(nil, parseError(@"limit requires numeric limit")); - return; + if (error) *error = parseError(@"limit requires numeric limit"); + return nil; } stage = [[FIRLimitStageBridge alloc] initWithLimit:limit.intValue]; } else if ([stageName isEqualToString:@"offset"]) { NSNumber *offset = args[@"offset"]; if (![offset isKindOfClass:[NSNumber class]]) { - completion(nil, parseError(@"offset requires numeric offset")); - return; + if (error) *error = parseError(@"offset requires numeric offset"); + return nil; } stage = [[FIROffsetStageBridge alloc] initWithOffset:offset.intValue]; } else if ([stageName isEqualToString:@"sort"]) { NSArray *orderingMaps = args[@"orderings"]; if (![orderingMaps isKindOfClass:[NSArray class]] || orderingMaps.count == 0) { - completion(nil, parseError(@"sort requires at least one ordering")); - return; + if (error) *error = parseError(@"sort requires at least one ordering"); + return nil; } NSMutableArray *orderings = [NSMutableArray array]; for (id om in orderingMaps) { @@ -258,8 +359,8 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore if (![exprMap isKindOfClass:[NSDictionary class]]) continue; FIRExprBridge *expr = [exprParser parseExpression:exprMap error:&parseErr]; if (!expr) { - completion(nil, parseErr); - return; + if (error) *error = parseErr; + return nil; } NSString *direction = [dir isEqualToString:@"asc"] ? @"ascending" : @"descending"; FIROrderingBridge *ordering = [[FIROrderingBridge alloc] initWithExpr:expr @@ -267,23 +368,23 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore [orderings addObject:ordering]; } if (orderings.count == 0) { - completion(nil, parseError(@"sort requires at least one ordering")); - return; + if (error) *error = parseError(@"sort requires at least one ordering"); + return nil; } stage = [[FIRSorStageBridge alloc] initWithOrderings:orderings]; } else if ([stageName isEqualToString:@"select"]) { NSArray *exprMaps = args[@"expressions"]; if (![exprMaps isKindOfClass:[NSArray class]] || exprMaps.count == 0) { - completion(nil, parseError(@"select requires at least one expression")); - return; + if (error) *error = parseError(@"select requires at least one expression"); + return nil; } NSMutableDictionary *fields = [NSMutableDictionary dictionary]; for (id em in exprMaps) { if (![em isKindOfClass:[NSDictionary class]]) continue; FIRExprBridge *expr = [exprParser parseExpression:em error:&parseErr]; if (!expr) { - completion(nil, parseErr); - return; + if (error) *error = parseErr; + return nil; } NSString *alias = [em valueForKeyPath:@"args.alias"]; if (alias) { @@ -302,21 +403,21 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore } else if ([stageName isEqualToString:@"add_fields"]) { NSArray *exprMaps = args[@"expressions"]; if (![exprMaps isKindOfClass:[NSArray class]] || exprMaps.count == 0) { - completion(nil, parseError(@"add_fields requires at least one expression")); - return; + if (error) *error = parseError(@"add_fields requires at least one expression"); + return nil; } NSMutableDictionary *fields = [NSMutableDictionary dictionary]; for (id em in exprMaps) { if (![em isKindOfClass:[NSDictionary class]]) continue; FIRExprBridge *expr = [exprParser parseExpression:em error:&parseErr]; if (!expr) { - completion(nil, parseErr); - return; + if (error) *error = parseErr; + return nil; } NSString *alias = [em valueForKeyPath:@"args.alias"]; if (!alias) { - completion(nil, parseError(@"add_fields expressions must have alias")); - return; + if (error) *error = parseError(@"add_fields expressions must have alias"); + return nil; } fields[alias] = expr; } @@ -324,15 +425,15 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore } else if ([stageName isEqualToString:@"remove_fields"]) { NSArray *paths = args[@"field_paths"]; if (![paths isKindOfClass:[NSArray class]] || paths.count == 0) { - completion(nil, parseError(@"remove_fields requires field_paths")); - return; + if (error) *error = parseError(@"remove_fields requires field_paths"); + return nil; } stage = [[FIRRemoveFieldsStageBridge alloc] initWithFields:paths]; } else if ([stageName isEqualToString:@"distinct"]) { NSArray *exprMaps = args[@"expressions"]; if (![exprMaps isKindOfClass:[NSArray class]] || exprMaps.count == 0) { - completion(nil, parseError(@"distinct requires at least one expression")); - return; + if (error) *error = parseError(@"distinct requires at least one expression"); + return nil; } NSMutableDictionary *fields = [NSMutableDictionary dictionary]; for (NSUInteger j = 0; j < exprMaps.count; j++) { @@ -340,8 +441,8 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore if (![em isKindOfClass:[NSDictionary class]]) continue; FIRExprBridge *expr = [exprParser parseExpression:em error:&parseErr]; if (!expr) { - completion(nil, parseErr); - return; + if (error) *error = parseErr; + return nil; } fields[[NSString stringWithFormat:@"_%lu", (unsigned long)j]] = expr; } @@ -349,27 +450,27 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore } else if ([stageName isEqualToString:@"replace_with"]) { id exprMap = args[@"expression"]; if (![exprMap isKindOfClass:[NSDictionary class]]) { - completion(nil, parseError(@"replace_with requires expression")); - return; + if (error) *error = parseError(@"replace_with requires expression"); + return nil; } FIRExprBridge *expr = [exprParser parseExpression:exprMap error:&parseErr]; if (!expr) { - completion(nil, parseErr); - return; + if (error) *error = parseErr; + return nil; } stage = [[FIRReplaceWithStageBridge alloc] initWithExpr:expr]; } else if ([stageName isEqualToString:@"union"]) { NSArray *nestedStages = args[@"pipeline"]; if (![nestedStages isKindOfClass:[NSArray class]] || nestedStages.count == 0) { - completion(nil, parseError(@"union requires non-empty pipeline")); - return; + if (error) *error = parseError(@"union requires non-empty pipeline"); + return nil; } id otherPipeline = [self buildPipelineWithFirestore:firestore stages:nestedStages error:&parseErr]; if (!otherPipeline) { - completion(nil, parseErr); - return; + if (error) *error = parseErr; + return nil; } stage = [[FIRUnionStageBridge alloc] initWithOther:otherPipeline]; } else if ([stageName isEqualToString:@"sample"]) { @@ -382,10 +483,55 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore int v = [val isKindOfClass:[NSNumber class]] ? [(NSNumber *)val intValue] : 0; stage = [[FIRSampleStageBridge alloc] initWithCount:v]; } + } else if ([stageName isEqualToString:@"aggregate"]) { + stage = [self parseAggregateStageWithArgs:args exprParser:exprParser error:error]; + } else if ([stageName isEqualToString:@"unnest"]) { + id exprMap = args[@"expression"]; + if (![exprMap isKindOfClass:[NSDictionary class]]) { + if (error) *error = parseError(@"unnest requires expression"); + return nil; + } + FIRExprBridge *fieldExpr = nil; + FIRExprBridge *aliasExpr = nil; + NSDictionary *exprDict = (NSDictionary *)exprMap; + NSString *aliasStr = nil; + if ([exprDict[@"name"] isEqualToString:@"alias"]) { + NSDictionary *aliasArgs = exprDict[@"args"]; + if ([aliasArgs isKindOfClass:[NSDictionary class]] && aliasArgs[@"expression"]) { + fieldExpr = [exprParser parseExpression:aliasArgs[@"expression"] error:&parseErr]; + if (!fieldExpr) { + if (error) *error = parseErr; + return nil; + } + aliasStr = + [aliasArgs[@"alias"] isKindOfClass:[NSString class]] ? aliasArgs[@"alias"] : nil; + } + } + if (!fieldExpr) { + fieldExpr = [exprParser parseExpression:exprMap error:&parseErr]; + if (!fieldExpr) { + if (error) *error = parseErr; + return nil; + } + if (!aliasStr && [exprDict[@"name"] isEqualToString:@"field"]) { + NSDictionary *fieldArgs = exprDict[@"args"]; + aliasStr = + [fieldArgs[@"field"] isKindOfClass:[NSString class]] ? fieldArgs[@"field"] : @"_"; + } + } + if (!aliasStr) aliasStr = @"_"; + aliasExpr = [[FIRFieldBridge alloc] initWithName:aliasStr]; + NSString *indexFieldStr = + [args[@"index_field"] isKindOfClass:[NSString class]] ? args[@"index_field"] : nil; + FIRExprBridge *indexFieldExpr = + (indexFieldStr.length > 0) ? [[FIRFieldBridge alloc] initWithName:indexFieldStr] : nil; + stage = [[FIRUnnestStageBridge alloc] initWithField:fieldExpr + alias:aliasExpr + indexField:indexFieldExpr]; } else { - completion( - nil, parseError([NSString stringWithFormat:@"Unknown pipeline stage: %@", stageName])); - return; + if (error) + *error = parseError([NSString stringWithFormat:@"Unknown pipeline stage: %@", stageName]); + return nil; } } @@ -393,103 +539,149 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore } if (stageBridges.count == 0) { - completion(nil, parseError(@"No valid stages")); - return; + if (error && !*error) *error = parseError(@"No valid stages"); + return nil; } - FIRPipelineBridge *pipeline = [[FIRPipelineBridge alloc] initWithStages:stageBridges - db:firestore]; - [pipeline executeWithCompletion:^(id snapshot, NSError *execError) { - if (execError) { - completion(nil, execError); - return; + return stageBridges; +} + ++ (FIRAggregateFunctionBridge *)aggregateFunctionFromMap:(NSDictionary *)funcMap + exprParser:(FLTPipelineExpressionParser *)exprParser + error:(NSError **)error { + NSString *name = funcMap[@"name"]; + if (![name isKindOfClass:[NSString class]]) { + if (error) *error = parseError(@"Aggregate function must have a 'name'"); + return nil; + } + // Map Dart aggregate function names to iOS SDK names (count_all -> count with no args; minimum -> + // min; maximum -> max) + NSString *iosName = name; + if ([name isEqualToString:@"count_all"]) { + iosName = @"count"; + } else if ([name isEqualToString:@"minimum"]) { + iosName = @"min"; + } else if ([name isEqualToString:@"maximum"]) { + iosName = @"max"; + } + NSDictionary *argsDict = funcMap[@"args"]; + NSMutableArray *argsArray = [NSMutableArray array]; + if ([argsDict isKindOfClass:[NSDictionary class]]) { + id exprMap = argsDict[@"expression"]; + if ([exprMap isKindOfClass:[NSDictionary class]]) { + FIRExprBridge *expr = [exprParser parseExpression:exprMap error:error]; + if (!expr) return nil; + [argsArray addObject:expr]; } - completion(snapshot, nil); - }]; + } + return [[FIRAggregateFunctionBridge alloc] initWithName:iosName Args:argsArray]; } -+ (id)buildPipelineWithFirestore:(FIRFirestore *)firestore - stages:(NSArray *> *)stages - error:(NSError **)error { - FLTPipelineExpressionParser *exprParser = [[FLTPipelineExpressionParser alloc] init]; - NSMutableArray *stageBridges = [NSMutableArray array]; ++ (FIRStageBridge *)parseAggregateStageWithArgs:(NSDictionary *)args + exprParser:(FLTPipelineExpressionParser *)exprParser + error:(NSError **)error { + NSError *parseErr = nil; + NSArray *accumulatorMaps = nil; + NSArray *groupMaps = nil; - for (NSUInteger i = 0; i < stages.count; i++) { - NSDictionary *stageMap = stages[i]; + if (args[@"aggregate_stage"]) { + NSDictionary *stageMap = args[@"aggregate_stage"]; if (![stageMap isKindOfClass:[NSDictionary class]]) { - if (error) *error = parseError(@"Stage must be a map"); + if (error) *error = parseError(@"aggregate_stage must be a map"); return nil; } - NSString *stageName = stageMap[@"stage"]; - id argsObj = stageMap[@"args"]; - NSDictionary *args = [argsObj isKindOfClass:[NSDictionary class]] ? argsObj : @{}; - NSArray *argsArray = [argsObj isKindOfClass:[NSArray class]] ? argsObj : nil; + accumulatorMaps = stageMap[@"accumulators"]; + groupMaps = stageMap[@"groups"]; + } + if (!accumulatorMaps || ![accumulatorMaps isKindOfClass:[NSArray class]]) { + accumulatorMaps = args[@"aggregate_functions"]; + } + if (![accumulatorMaps isKindOfClass:[NSArray class]] || accumulatorMaps.count == 0) { + if (error) *error = parseError(@"aggregate requires accumulators or aggregate_functions"); + return nil; + } - FIRStageBridge *stage = nil; + NSMutableDictionary *accumulators = + [NSMutableDictionary dictionary]; + for (id accMap in accumulatorMaps) { + if (![accMap isKindOfClass:[NSDictionary class]]) continue; + NSString *alias = nil; + NSDictionary *funcMap = nil; + if ([accMap[@"name"] isEqualToString:@"alias"]) { + NSDictionary *accArgs = accMap[@"args"]; + if (![accArgs isKindOfClass:[NSDictionary class]]) continue; + alias = accArgs[@"alias"]; + funcMap = accArgs[@"aggregate_function"]; + } + if (![alias isKindOfClass:[NSString class]] || ![funcMap isKindOfClass:[NSDictionary class]]) { + if (error) *error = parseError(@"Each accumulator must have alias and aggregate_function"); + return nil; + } + FIRAggregateFunctionBridge *func = [self aggregateFunctionFromMap:funcMap + exprParser:exprParser + error:&parseErr]; + if (!func) { + if (error) *error = parseErr; + return nil; + } + accumulators[alias] = func; + } + if (accumulators.count == 0) { + if (error) *error = parseError(@"aggregate requires at least one valid accumulator"); + return nil; + } - if (i == 0) { - if ([stageName isEqualToString:@"collection"]) { - NSString *path = args[@"path"]; - FIRCollectionReference *ref = [firestore collectionWithPath:path]; - stage = [[FIRCollectionSourceStageBridge alloc] initWithRef:ref firestore:firestore]; - } else if ([stageName isEqualToString:@"collection_group"]) { - stage = [[FIRCollectionGroupSourceStageBridge alloc] initWithCollectionId:args[@"path"]]; - } else if ([stageName isEqualToString:@"database"]) { - stage = [[FIRDatabaseSourceStageBridge alloc] init]; - } else if ([stageName isEqualToString:@"documents"]) { - NSArray *docMaps = argsArray ?: @[]; - NSMutableArray *refs = [NSMutableArray array]; - for (id docMap in docMaps) { - if ([docMap isKindOfClass:[NSDictionary class]] && ((NSDictionary *)docMap)[@"path"]) - [refs addObject:[firestore documentWithPath:((NSDictionary *)docMap)[@"path"]]]; - } - stage = [[FIRDocumentsSourceStageBridge alloc] initWithDocuments:refs firestore:firestore]; - } - } else { - NSError *parseErr = nil; - if ([stageName isEqualToString:@"where"]) { - FIRExprBridge *expr = [exprParser parseBooleanExpression:args[@"expression"] - error:&parseErr]; - if (expr) stage = [[FIRWhereStageBridge alloc] initWithExpr:expr]; - } else if ([stageName isEqualToString:@"limit"]) { - stage = [[FIRLimitStageBridge alloc] initWithLimit:[args[@"limit"] intValue]]; - } else if ([stageName isEqualToString:@"offset"]) { - stage = [[FIROffsetStageBridge alloc] initWithOffset:[args[@"offset"] intValue]]; - } else if ([stageName isEqualToString:@"sort"]) { - NSArray *orderingMaps = args[@"orderings"]; - if ([orderingMaps isKindOfClass:[NSArray class]] && orderingMaps.count > 0) { - NSMutableArray *orderings = [NSMutableArray array]; - for (id om in orderingMaps) { - if (![om isKindOfClass:[NSDictionary class]]) continue; - id exprMap = ((NSDictionary *)om)[@"expression"]; - NSString *dir = ((NSDictionary *)om)[@"order_direction"]; - if (![exprMap isKindOfClass:[NSDictionary class]]) continue; - FIRExprBridge *expr = [exprParser parseExpression:exprMap error:&parseErr]; - if (!expr) break; - NSString *direction = [dir isEqualToString:@"asc"] ? @"ascending" : @"descending"; - [orderings addObject:[[FIROrderingBridge alloc] initWithExpr:expr Direction:direction]]; - } - if (orderings.count > 0) { - stage = [[FIRSorStageBridge alloc] initWithOrderings:orderings]; - } - } - } else if ([stageName isEqualToString:@"union"]) { - id other = [self buildPipelineWithFirestore:firestore - stages:args[@"pipeline"] - error:&parseErr]; - if (other) stage = [[FIRUnionStageBridge alloc] initWithOther:other]; - } - if (parseErr && error) *error = parseErr; + NSMutableDictionary *groups = [NSMutableDictionary dictionary]; + if ([groupMaps isKindOfClass:[NSArray class]] && groupMaps.count > 0) { + for (NSUInteger g = 0; g < groupMaps.count; g++) { + id gm = groupMaps[g]; + if (![gm isKindOfClass:[NSDictionary class]]) continue; + FIRExprBridge *expr = [exprParser parseExpression:gm error:&parseErr]; + if (!expr) continue; + groups[[NSString stringWithFormat:@"_%lu", (unsigned long)g]] = expr; } + } - if (stage) [stageBridges addObject:stage]; + return [[FIRAggregateStageBridge alloc] initWithAccumulators:accumulators groups:groups]; +} + ++ (void)executePipelineWithFirestore:(FIRFirestore *)firestore + stages:(NSArray *> *)stages + options:(nullable NSDictionary *)options + completion:(void (^)(id _Nullable snapshot, + NSError *_Nullable error))completion { + if (!stages || stages.count == 0) { + completion(nil, parseError(@"Pipeline requires at least one stage")); + return; } - if (stageBridges.count == 0) { - if (error && !*error) *error = parseError(@"No valid stages"); - return nil; + NSError *parseErr = nil; + NSArray *stageBridges = [self parseStagesWithFirestore:firestore + stages:stages + error:&parseErr]; + if (!stageBridges) { + completion(nil, parseErr); + return; } + FIRPipelineBridge *pipeline = [[FIRPipelineBridge alloc] initWithStages:stageBridges + db:firestore]; + [pipeline executeWithCompletion:^(id snapshot, NSError *execError) { + if (execError) { + completion(nil, execError); + return; + } + completion(snapshot, nil); + }]; +} + ++ (id)buildPipelineWithFirestore:(FIRFirestore *)firestore + stages:(NSArray *> *)stages + error:(NSError **)error { + NSArray *stageBridges = [self parseStagesWithFirestore:firestore + stages:stages + error:error]; + if (!stageBridges) return nil; return [[FIRPipelineBridge alloc] initWithStages:stageBridges db:firestore]; } From 6699324438612578ff7d7554b80f6afce6bfdfcc Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Wed, 25 Feb 2026 15:33:59 +0000 Subject: [PATCH 13/72] fix: update args type in _UnnestStage to use a more specific map type --- .../cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart index 11c4ec624da1..ab06a32dc51e 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart @@ -363,7 +363,7 @@ final class _UnnestStage extends PipelineStage { Map toMap() { final map = { 'stage': name, - 'args': { + 'args': { 'expression': expression.toMap(), }, }; From 59c0b886a7f9bbddc4be5f97c32b88a88bd8c111 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Wed, 25 Feb 2026 15:34:21 +0000 Subject: [PATCH 14/72] feat: add filter expression parsing to FLTPipelineParser for enhanced query capabilities --- .../cloud_firestore/FLTPipelineParser.m | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m index b4f46889cf87..986628021869 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m @@ -38,6 +38,7 @@ @interface FLTPipelineExpressionParser : NSObject - (FIRExprBridge *)parseExpression:(NSDictionary *)map error:(NSError **)error; - (FIRExprBridge *)parseBooleanExpression:(NSDictionary *)map error:(NSError **)error; +- (FIRExprBridge *)rightExprFromValue:(id)value error:(NSError **)error; @end @implementation FLTPipelineExpressionParser @@ -241,10 +242,160 @@ - (FIRExprBridge *)parseExpression:(NSDictionary *)map error:(NS return [[FIRFunctionExprBridge alloc] initWithName:name Args:argsArray]; } + // ------------------------------------------------------------------------- + // PipelineFilter (name "filter"): operator-based (and/or) or field-based + // ------------------------------------------------------------------------- + if ([name isEqualToString:@"filter"]) { + return [self parseFilterExpressionWithArgs:args error:error]; + } + if (error) *error = parseError([NSString stringWithFormat:@"Unsupported expression: %@", name]); return nil; } +- (FIRExprBridge *)rightExprFromValue:(id)value error:(NSError **)error { + if ([value isKindOfClass:[NSDictionary class]]) { + return [self parseExpression:(NSDictionary *)value error:error]; + } + return [[FIRConstantBridge alloc] init:value]; +} + +- (FIRExprBridge *)parseFilterExpressionWithArgs:(NSDictionary *)args error:(NSError **)error { + // Operator-based: and/or with expressions array (from PipelineFilter.and / .or) + NSString *operator= args[@"operator"]; + NSArray *exprMaps = args[@"expressions"]; + if ([operator isKindOfClass:[NSString class]] && [exprMaps isKindOfClass:[NSArray class]]) { + if (exprMaps.count == 0) { + if (error) *error = parseError(@"filter with operator requires at least one expression"); + return nil; + } + if (exprMaps.count == 1) { + id em = exprMaps[0]; + if (![em isKindOfClass:[NSDictionary class]]) { + if (error) *error = parseError(@"filter expressions must be maps"); + return nil; + } + return [self parseBooleanExpression:(NSDictionary *)em error:error]; + } + NSMutableArray *all = [NSMutableArray array]; + for (id em in exprMaps) { + if (![em isKindOfClass:[NSDictionary class]]) continue; + FIRExprBridge *e = [self parseBooleanExpression:(NSDictionary *)em error:error]; + if (!e) return nil; + [all addObject:e]; + } + if (all.count == 0) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:operator Args:all]; + } + + // Field-based: field + isEqualTo, isGreaterThan, etc. + NSString *fieldName = args[@"field"]; + if (![fieldName isKindOfClass:[NSString class]]) { + if (error) *error = parseError(@"filter requires operator+expressions or field"); + return nil; + } + FIRExprBridge *fieldExpr = [[FIRFieldBridge alloc] initWithName:fieldName]; + + static NSArray *filterComparisonKeys = nil; + static dispatch_once_t filterOnce; + dispatch_once(&filterOnce, ^{ + filterComparisonKeys = @[ + @"isEqualTo", @"isNotEqualTo", @"isGreaterThan", @"isGreaterThanOrEqualTo", @"isLessThan", + @"isLessThanOrEqualTo", @"arrayContains", @"arrayContainsAny", @"whereIn", @"whereNotIn", + @"isNull", @"isNotNull" + ]; + }); + for (NSString *key in filterComparisonKeys) { + id value = args[key]; + if (value == nil) continue; + + if ([key isEqualToString:@"isEqualTo"]) { + FIRExprBridge *right = [self rightExprFromValue:value error:error]; + if (!right) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:@"equal" Args:@[ fieldExpr, right ]]; + } + if ([key isEqualToString:@"isNotEqualTo"]) { + FIRExprBridge *right = [self rightExprFromValue:value error:error]; + if (!right) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:@"not_equal" Args:@[ fieldExpr, right ]]; + } + if ([key isEqualToString:@"isGreaterThan"]) { + FIRExprBridge *right = [self rightExprFromValue:value error:error]; + if (!right) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:@"greater_than" + Args:@[ fieldExpr, right ]]; + } + if ([key isEqualToString:@"isGreaterThanOrEqualTo"]) { + FIRExprBridge *right = [self rightExprFromValue:value error:error]; + if (!right) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:@"greater_than_or_equal" + Args:@[ fieldExpr, right ]]; + } + if ([key isEqualToString:@"isLessThan"]) { + FIRExprBridge *right = [self rightExprFromValue:value error:error]; + if (!right) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:@"less_than" Args:@[ fieldExpr, right ]]; + } + if ([key isEqualToString:@"isLessThanOrEqualTo"]) { + FIRExprBridge *right = [self rightExprFromValue:value error:error]; + if (!right) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:@"less_than_or_equal" + Args:@[ fieldExpr, right ]]; + } + if ([key isEqualToString:@"arrayContains"]) { + FIRExprBridge *right = [self rightExprFromValue:value error:error]; + if (!right) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:@"array_contains" + Args:@[ fieldExpr, right ]]; + } + if ([key isEqualToString:@"arrayContainsAny"] || [key isEqualToString:@"whereIn"]) { + NSArray *valuesList = [value isKindOfClass:[NSArray class]] ? value : @[]; + NSMutableArray *valueExprs = [NSMutableArray array]; + for (id v in valuesList) { + FIRExprBridge *ve = [self rightExprFromValue:v error:error]; + if (!ve) return nil; + [valueExprs addObject:ve]; + } + if (valueExprs.count == 0) { + if (error) *error = parseError(@"arrayContainsAny/whereIn requires non-empty list"); + return nil; + } + NSMutableArray *argsArray = [NSMutableArray arrayWithObject:fieldExpr]; + [argsArray addObjectsFromArray:valueExprs]; + return [[FIRFunctionExprBridge alloc] initWithName:@"equal_any" Args:argsArray]; + } + if ([key isEqualToString:@"whereNotIn"]) { + NSArray *valuesList = [value isKindOfClass:[NSArray class]] ? value : @[]; + NSMutableArray *valueExprs = [NSMutableArray array]; + for (id v in valuesList) { + FIRExprBridge *ve = [self rightExprFromValue:v error:error]; + if (!ve) return nil; + [valueExprs addObject:ve]; + } + if (valueExprs.count == 0) { + if (error) *error = parseError(@"whereNotIn requires non-empty list"); + return nil; + } + NSMutableArray *argsArray = [NSMutableArray arrayWithObject:fieldExpr]; + [argsArray addObjectsFromArray:valueExprs]; + return [[FIRFunctionExprBridge alloc] initWithName:@"not_equal_any" Args:argsArray]; + } + if ([key isEqualToString:@"isNull"]) { + FIRExprBridge *right = [[FIRConstantBridge alloc] init:[NSNull null]]; + return [[FIRFunctionExprBridge alloc] initWithName:@"equal" Args:@[ fieldExpr, right ]]; + } + if ([key isEqualToString:@"isNotNull"]) { + FIRExprBridge *right = [[FIRConstantBridge alloc] init:[NSNull null]]; + return [[FIRFunctionExprBridge alloc] initWithName:@"not_equal" Args:@[ fieldExpr, right ]]; + } + } + + if (error) + *error = + parseError(@"filter requires at least one comparison (isEqualTo, isGreaterThan, etc.)"); + return nil; +} + - (FIRExprBridge *)parseBooleanExpression:(NSDictionary *)map error:(NSError **)error { return [self parseExpression:map error:error]; From 642c0de56f96684759be4eaafe36697c837432f6 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Fri, 27 Feb 2026 11:31:05 +0000 Subject: [PATCH 15/72] fix: handle null values in parseConstantValue --- .../plugins/firebase/firestore/utils/ExpressionHelpers.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java index b3ef31dbb038..7e2c4020aa6d 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java @@ -77,7 +77,11 @@ static BooleanExpression parseOrExpression( * types: String, Number, Boolean, Date, Timestamp, GeoPoint, byte[], Blob, DocumentReference, * VectorValue */ - static Expression parseConstantValue(@NonNull Object value) { + static Expression parseConstantValue(Object value) { + + if (value == null) { + return Expression.nullValue(); + } if (value instanceof String) { return Expression.constant((String) value); From 0a06aea2cff655c898a8c0abf9fd42525fa1ab41 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Fri, 27 Feb 2026 14:00:38 +0000 Subject: [PATCH 16/72] feat: introduce keyForExpressionMap method in FLTPipelineParser for improved expression handling --- .../cloud_firestore/FLTPipelineParser.m | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m index 986628021869..3018ce5820fa 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m @@ -405,6 +405,25 @@ - (FIRExprBridge *)parseBooleanExpression:(NSDictionary *)map @implementation FLTPipelineParser +/// Returns the key (alias or field name) for an expression map in select/distinct stages. +/// Uses args.alias if present; otherwise for "field" expressions uses args.field. Returns nil if +/// no key can be determined (caller should error). ++ (NSString *)keyForExpressionMap:(NSDictionary *)em error:(NSError **)error { + NSString *alias = [em valueForKeyPath:@"args.alias"]; + if ([alias isKindOfClass:[NSString class]] && alias.length > 0) { + return alias; + } + if ([em[@"name"] isEqualToString:@"field"]) { + NSString *field = [em valueForKeyPath:@"args.field"]; + if ([field isKindOfClass:[NSString class]]) return field; + if (error) *error = parseError(@"field expression must have args.field"); + return nil; + } + if (error) + *error = parseError(@"select/distinct expression must have alias or be a field reference"); + return nil; +} + + (NSArray *) parseStagesWithFirestore:(FIRFirestore *)firestore stages:(NSArray *> *)stages @@ -537,18 +556,9 @@ @implementation FLTPipelineParser if (error) *error = parseErr; return nil; } - NSString *alias = [em valueForKeyPath:@"args.alias"]; - if (alias) { - fields[alias] = expr; - } else { - NSString *fn = em[@"name"]; - if ([fn isEqualToString:@"field"]) { - NSString *field = [em valueForKeyPath:@"args.field"]; - fields[field ?: @"_"] = expr; - } else { - fields[[NSString stringWithFormat:@"_%lu", (unsigned long)fields.count]] = expr; - } - } + NSString *key = [self keyForExpressionMap:em error:error]; + if (!key) return nil; + fields[key] = expr; } stage = [[FIRSelectStageBridge alloc] initWithSelections:fields]; } else if ([stageName isEqualToString:@"add_fields"]) { @@ -587,15 +597,16 @@ @implementation FLTPipelineParser return nil; } NSMutableDictionary *fields = [NSMutableDictionary dictionary]; - for (NSUInteger j = 0; j < exprMaps.count; j++) { - id em = exprMaps[j]; + for (id em in exprMaps) { if (![em isKindOfClass:[NSDictionary class]]) continue; FIRExprBridge *expr = [exprParser parseExpression:em error:&parseErr]; if (!expr) { if (error) *error = parseErr; return nil; } - fields[[NSString stringWithFormat:@"_%lu", (unsigned long)j]] = expr; + NSString *key = [self keyForExpressionMap:em error:error]; + if (!key) return nil; + fields[key] = expr; } stage = [[FIRDistinctStageBridge alloc] initWithGroups:fields]; } else if ([stageName isEqualToString:@"replace_with"]) { From 1b29c4d432597d12e08990825647f0ac9467a8f3 Mon Sep 17 00:00:00 2001 From: Jude Selase Kwashie <64037520+SelaseKay@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:31:36 +0000 Subject: [PATCH 17/72] feat: bump Firebase JS SDK to 12.9.0 (#18043) --- .../firebase_core_web/lib/src/firebase_sdk_version.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/firebase_core/firebase_core_web/lib/src/firebase_sdk_version.dart b/packages/firebase_core/firebase_core_web/lib/src/firebase_sdk_version.dart index 6a43bfd5f478..3e04c6928134 100644 --- a/packages/firebase_core/firebase_core_web/lib/src/firebase_sdk_version.dart +++ b/packages/firebase_core/firebase_core_web/lib/src/firebase_sdk_version.dart @@ -6,4 +6,4 @@ part of '../firebase_core_web.dart'; /// The currently supported Firebase JS SDK version. -const String supportedFirebaseJsSdkVersion = '12.7.0'; +const String supportedFirebaseJsSdkVersion = '12.9.0'; From fd07be0c0e3ab3457ef670e6b6069dc104938a08 Mon Sep 17 00:00:00 2001 From: Morgan Chen Date: Fri, 27 Feb 2026 09:57:33 -0800 Subject: [PATCH 18/72] fix: use builtin GITHUB_TOKEN instead of explicit secret (#18031) * use builtin GITHUB_TOKEN instead of explicit secret * add explicit permission --- .github/workflows/pr_title.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr_title.yaml b/.github/workflows/pr_title.yaml index 317338e169a3..453064704526 100644 --- a/.github/workflows/pr_title.yaml +++ b/.github/workflows/pr_title.yaml @@ -9,8 +9,10 @@ on: jobs: validate: + permissions: + pull-requests: read runs-on: ubuntu-latest steps: - uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From fed585f5a9b65d683cefdc7fa97ed2692e4ec817 Mon Sep 17 00:00:00 2001 From: Aashish <112133849+aashishpatil-g@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:35:30 -0800 Subject: [PATCH 19/72] refactor(fdc): Support for entityId path extensions and hardening (#17988) Support for reading entity ids from paths in response extension Hardening SQLite to use transactions for edits Setup schema versioning to handle future schema updates. Handle edge cases - multi-dimensional arrays, mixed type arrays, scalar arrays --- .../firebase_data_connect/generate_proto.sh | 7 +- .../cache/{cache_manager.dart => cache.dart} | 43 ++-- .../lib/src/cache/cache_data_types.dart | 121 +++++++++- .../lib/src/cache/cache_provider.dart | 9 +- .../src/cache/in_memory_cache_provider.dart | 14 +- .../lib/src/cache/result_tree_processor.dart | 121 +++++----- .../lib/src/cache/sqlite_cache_provider.dart | 127 +++++++--- .../lib/src/common/common_library.dart | 9 +- .../lib/src/common/dataconnect_error.dart | 27 +++ .../lib/src/core/ref.dart | 2 +- .../lib/src/firebase_data_connect.dart | 2 +- .../src/generated/connector_service.pb.dart | 74 ++++-- .../generated/connector_service.pbenum.dart | 13 - .../generated/connector_service.pbgrpc.dart | 13 - .../generated/connector_service.pbjson.dart | 37 +-- .../google/protobuf/duration.pb.dart | 166 +++++++++++++ .../google/protobuf/duration.pbenum.dart | 10 + .../google/protobuf/duration.pbjson.dart | 28 +++ .../generated/google/protobuf/struct.pb.dart | 13 - .../google/protobuf/struct.pbenum.dart | 13 - .../google/protobuf/struct.pbjson.dart | 13 - .../lib/src/generated/graphql_error.pb.dart | 13 - .../src/generated/graphql_error.pbenum.dart | 13 - .../src/generated/graphql_error.pbjson.dart | 13 - .../graphql_response_extensions.pb.dart | 222 ++++++++++++++++++ .../graphql_response_extensions.pbenum.dart | 10 + .../graphql_response_extensions.pbjson.dart | 65 +++++ .../lib/src/network/grpc_transport.dart | 14 +- .../lib/src/network/rest_transport.dart | 9 +- .../protos/connector_service.proto | 48 ++-- .../graphql_response_extensions.proto | 59 +++++ .../protos/google/duration.proto | 116 +++++++++ .../firebase_data_connect/pubspec.yaml | 1 + .../test/src/cache/cache_manager_test.dart | 102 ++++++-- .../src/cache/result_tree_processor_test.dart | 43 +++- 35 files changed, 1257 insertions(+), 333 deletions(-) rename packages/firebase_data_connect/firebase_data_connect/lib/src/cache/{cache_manager.dart => cache.dart} (80%) create mode 100644 packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pb.dart create mode 100644 packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pbenum.dart create mode 100644 packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pbjson.dart create mode 100644 packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pb.dart create mode 100644 packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pbenum.dart create mode 100644 packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pbjson.dart create mode 100644 packages/firebase_data_connect/firebase_data_connect/protos/firebase/graphql_response_extensions.proto create mode 100644 packages/firebase_data_connect/firebase_data_connect/protos/google/duration.proto diff --git a/packages/firebase_data_connect/firebase_data_connect/generate_proto.sh b/packages/firebase_data_connect/firebase_data_connect/generate_proto.sh index b8d3232eef71..5b3fc45dcaed 100755 --- a/packages/firebase_data_connect/firebase_data_connect/generate_proto.sh +++ b/packages/firebase_data_connect/firebase_data_connect/generate_proto.sh @@ -1,4 +1,9 @@ #!/bin/bash + +# Uses dart protoc_plugin version 21.1.2. There are compilation issues with newer plugin versions. +# https://github.com/google/protobuf.dart/releases/tag/protoc_plugin-v21.1.2 +# Run `pub global activate protoc_plugin 21.1.2` + rm -rf lib/src/generated mkdir lib/src/generated -protoc --dart_out=grpc:lib/src/generated -I./protos/firebase -I./protos/google connector_service.proto google/protobuf/struct.proto graphql_error.proto --proto_path=./protos +protoc --dart_out=grpc:lib/src/generated -I./protos/firebase -I./protos/google connector_service.proto google/protobuf/struct.proto google/protobuf/duration.proto graphql_error.proto graphql_response_extensions.proto --proto_path=./protos diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_manager.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache.dart similarity index 80% rename from packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_manager.dart rename to packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache.dart index b2cc1506161f..3983df6c5842 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_manager.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache.dart @@ -50,9 +50,13 @@ class Cache { Stream> get impactedQueries => _impactedQueryController.stream; String _constructCacheIdentifier() { - final rawIdentifier = - '${_settings.storage}-${dataConnect.app.options.projectId}-${dataConnect.app.name}-${dataConnect.connectorConfig.serviceId}-${dataConnect.connectorConfig.connector}-${dataConnect.connectorConfig.location}-${dataConnect.auth?.currentUser?.uid ?? 'anon'}-${dataConnect.transport.transportOptions.host}'; - return convertToSha256(rawIdentifier); + final rawPrefix = + '${_settings.storage}-${dataConnect.app.options.projectId}-${dataConnect.app.name}-${dataConnect.connectorConfig.serviceId}-${dataConnect.connectorConfig.connector}-${dataConnect.connectorConfig.location}-${dataConnect.transport.transportOptions.host}'; + final prefixSha = convertToSha256(rawPrefix); + final rawSuffix = dataConnect.auth?.currentUser?.uid ?? 'anon'; + final suffixSha = convertToSha256(rawSuffix); + + return '$prefixSha-$suffixSha'; } void _initializeProvider() { @@ -92,23 +96,32 @@ class Cache { return; } - final dehydrationResult = await _resultTreeProcessor.dehydrate( - queryId, serverResponse.data, _cacheProvider!); + final Map paths = + serverResponse.extensions != null + ? ExtensionResponse.fromJson(serverResponse.extensions!) + .flattenPathMetadata() + : {}; + + final dehydrationResult = await _resultTreeProcessor.dehydrateResults( + queryId, serverResponse.data, _cacheProvider!, paths); EntityNode rootNode = dehydrationResult.dehydratedTree; Map dehydratedMap = rootNode.toJson(mode: EncodingMode.dehydrated); // if we have server ttl, that overrides maxAge from cacheSettings - Duration ttl = - serverResponse.ttl != null ? serverResponse.ttl! : _settings.maxAge; + Duration ttl = serverResponse.extensions != null && + serverResponse.extensions!['ttl'] != null + ? Duration(seconds: serverResponse.extensions!['ttl'] as int) + : (serverResponse.ttl ?? _settings.maxAge); + final resultTree = ResultTree( data: dehydratedMap, ttl: ttl, cachedAt: DateTime.now(), lastAccessed: DateTime.now()); - _cacheProvider!.saveResultTree(queryId, resultTree); + _cacheProvider!.setResultTree(queryId, resultTree); Set impactedQueryIds = dehydrationResult.impactedQueryIds; impactedQueryIds.remove(queryId); // remove query being cached @@ -116,7 +129,8 @@ class Cache { } /// Fetches a cached result. - Future?> get(String queryId, bool allowStale) async { + Future?> resultTree( + String queryId, bool allowStale) async { if (_cacheProvider == null) { return null; } @@ -137,23 +151,20 @@ class Cache { } resultTree.lastAccessed = DateTime.now(); - _cacheProvider!.saveResultTree(queryId, resultTree); + _cacheProvider!.setResultTree(queryId, resultTree); EntityNode rootNode = EntityNode.fromJson(resultTree.data, _cacheProvider!); + Map hydratedJson = - rootNode.toJson(); //default mode for toJson is hydrate + await _resultTreeProcessor.hydrateResults(rootNode, _cacheProvider!); + return hydratedJson; } return null; } - /// Invalidates the cache. - Future invalidate() async { - _cacheProvider?.clear(); - } - void dispose() { _impactedQueryController.close(); } diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_data_types.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_data_types.dart index ae80c237817a..0768da15232c 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_data_types.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_data_types.dart @@ -15,41 +15,144 @@ import 'dart:convert'; import 'package:firebase_data_connect/src/cache/cache_provider.dart'; -import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:firebase_data_connect/src/common/common_library.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart' show kIsWeb, listEquals; /// Type of storage to use for the cache enum CacheStorage { persistent, memory } -const String kGlobalIDKey = 'cacheId'; +const String kGlobalIDKey = 'guid'; + +@immutable +class DataConnectPath { + final List components; + + DataConnectPath([List? components]) + : components = components ?? []; + + DataConnectPath appending(DataConnectPathSegment segment) { + return DataConnectPath([...components, segment]); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is DataConnectPath && + runtimeType == other.runtimeType && + listEquals(components, other.components); + + @override + int get hashCode => Object.hashAll(components); + + @override + String toString() => 'DataConnectPath($components)'; +} + +/// Additional information about object / field identified by a path +class PathMetadata { + final DataConnectPath path; + final String? entityId; + + PathMetadata({required this.path, this.entityId}); + + @override + String toString() { + return '$path : ${entityId ?? "null"}'; + } +} + +/// Represents the server response contained within the extension response +class PathMetadataResponse { + final List path; + final String? entityId; + final List? entityIds; + + PathMetadataResponse({required this.path, this.entityId, this.entityIds}); + + factory PathMetadataResponse.fromJson(Map json) { + return PathMetadataResponse( + path: (json['path'] as List).map(_parsePathSegment).toList(), + entityId: json['entityId'] as String?, + entityIds: (json['entityIds'] as List?)?.cast(), + ); + } +} + +DataConnectPathSegment _parsePathSegment(dynamic segment) { + if (segment is String) { + return DataConnectFieldPathSegment(segment); + } else if (segment is double || segment is int) { + int index = (segment is double) ? segment.toInt() : segment; + return DataConnectListIndexPathSegment(index); + } + throw ArgumentError('Invalid path segment type: ${segment.runtimeType}'); +} + +/// Represents the extension section within the server response +class ExtensionResponse { + final Duration? maxAge; + final List dataConnect; + + ExtensionResponse({this.maxAge, required this.dataConnect}); + + factory ExtensionResponse.fromJson(Map json) { + return ExtensionResponse( + maxAge: + json['ttl'] != null ? Duration(seconds: json['ttl'] as int) : null, + dataConnect: (json['dataConnect'] as List?) + ?.map((e) => + PathMetadataResponse.fromJson(e as Map)) + .toList() ?? + [], + ); + } + + Map flattenPathMetadata() { + final Map result = {}; + for (final pmr in dataConnect) { + if (pmr.entityId != null) { + final pm = PathMetadata( + path: DataConnectPath(pmr.path), entityId: pmr.entityId); + result[pm.path] = pm; + } + + if (pmr.entityIds != null) { + for (var i = 0; i < pmr.entityIds!.length; i++) { + final entityId = pmr.entityIds![i]; + final indexPath = DataConnectPath(pmr.path) + .appending(DataConnectListIndexPathSegment(i)); + final pm = PathMetadata(path: indexPath, entityId: entityId); + result[pm.path] = pm; + } + } + } + return result; + } +} /// Configuration for the cache class CacheSettings { /// The type of storage to use (e.g., "persistent", "memory") final CacheStorage storage; - /// The maximum size of the cache in bytes - final int maxSizeBytes; - /// Duration for which cache is used before revalidation with server final Duration maxAge; // Internal const constructor const CacheSettings._internal({ required this.storage, - required this.maxSizeBytes, required this.maxAge, }); // Factory constructor to handle the logic factory CacheSettings({ CacheStorage? storage, - int? maxSizeBytes, Duration maxAge = Duration.zero, }) { return CacheSettings._internal( storage: storage ?? (kIsWeb ? CacheStorage.memory : CacheStorage.persistent), - maxSizeBytes: maxSizeBytes ?? (kIsWeb ? 40000000 : 100000000), maxAge: maxAge, ); } @@ -203,7 +306,7 @@ class EntityNode { Map json, CacheProvider cacheProvider) { EntityDataObject? entity; if (json[kGlobalIDKey] != null) { - entity = cacheProvider.getEntityDataObject(json[kGlobalIDKey]); + entity = cacheProvider.getEntityData(json[kGlobalIDKey]); } Map? scalars; diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_provider.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_provider.dart index 9f65aa127820..484e1e390a45 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_provider.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_provider.dart @@ -25,19 +25,16 @@ abstract class CacheProvider { Future initialize(); /// Stores a `ResultTree` object. - void saveResultTree(String queryId, ResultTree resultTree); + void setResultTree(String queryId, ResultTree resultTree); /// Retrieves a `ResultTree` object. ResultTree? getResultTree(String queryId); /// Stores an `EntityDataObject` object. - void saveEntityDataObject(EntityDataObject edo); + void updateEntityData(EntityDataObject edo); /// Retrieves an `EntityDataObject` object. - EntityDataObject getEntityDataObject(String guid); - - /// Manages the cache size and eviction policies. - void manageCacheSize(); + EntityDataObject getEntityData(String guid); /// Clears all data from the cache. void clear(); diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/in_memory_cache_provider.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/in_memory_cache_provider.dart index b625921bbe05..cd44d4e25ac5 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/in_memory_cache_provider.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/in_memory_cache_provider.dart @@ -16,6 +16,7 @@ import 'cache_data_types.dart'; import 'cache_provider.dart'; /// An in-memory implementation of the `CacheProvider`. +/// This is used for the web platform class InMemoryCacheProvider implements CacheProvider { final Map _resultTrees = {}; final Map _edos = {}; @@ -31,12 +32,12 @@ class InMemoryCacheProvider implements CacheProvider { @override Future initialize() async { - // nothing to be intialized. + // nothing to be intialized return true; } @override - void saveResultTree(String queryId, ResultTree resultTree) { + void setResultTree(String queryId, ResultTree resultTree) { _resultTrees[queryId] = resultTree; } @@ -46,20 +47,15 @@ class InMemoryCacheProvider implements CacheProvider { } @override - void saveEntityDataObject(EntityDataObject edo) { + void updateEntityData(EntityDataObject edo) { _edos[edo.guid] = edo; } @override - EntityDataObject getEntityDataObject(String guid) { + EntityDataObject getEntityData(String guid) { return _edos.putIfAbsent(guid, () => EntityDataObject(guid: guid)); } - @override - void manageCacheSize() { - // In-memory cache doesn't have a size limit in this implementation. - } - @override void clear() { _resultTrees.clear(); diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/result_tree_processor.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/result_tree_processor.dart index 34b557c31d71..ce4bf1bad24a 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/result_tree_processor.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/result_tree_processor.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import 'dart:developer' as developer; + import '../common/common_library.dart'; import 'cache_data_types.dart'; import 'cache_provider.dart'; @@ -27,76 +29,109 @@ class DehydrationResult { class ResultTreeProcessor { /// Takes a server response, traverses the data, creates or updates `EntityDataObject`s, /// and builds a dehydrated `EntityNode` tree. - Future dehydrate(String queryId, - Map serverResponse, CacheProvider cacheProvider) async { + Future dehydrateResults( + String queryId, + Map serverResponse, + CacheProvider cacheProvider, + Map paths) async { final impactedQueryIds = {}; Map jsonData = serverResponse; if (serverResponse.containsKey('data')) { jsonData = serverResponse['data']; } - final rootNode = - _dehydrateNode(queryId, jsonData, cacheProvider, impactedQueryIds); + final rootNode = _dehydrateNode(queryId, jsonData, cacheProvider, + impactedQueryIds, DataConnectPath(), paths); return DehydrationResult(rootNode, impactedQueryIds); } - EntityNode _dehydrateNode(String queryId, dynamic data, - CacheProvider cacheProvider, Set impactedQueryIds) { + EntityNode _dehydrateNode( + String queryId, + dynamic data, + CacheProvider cacheProvider, + Set impactedQueryIds, + DataConnectPath path, + Map paths) { if (data is Map) { - // data contains a unique entity id. we can normalize - final guid = data[kGlobalIDKey] as String?; + // Look up entityId for current path + String? guid; + if (paths.containsKey(path)) { + guid = paths[path]?.entityId; + } - final serverValues = {}; + final scalarValues = {}; // scalars final nestedObjects = {}; final nestedObjectLists = >{}; for (final entry in data.entries) { final key = entry.key; final value = entry.value; - if (value is Map) { - EntityNode en = - _dehydrateNode(queryId, value, cacheProvider, impactedQueryIds); + //developer.log('detected Map for $key'); + EntityNode en = _dehydrateNode( + queryId, + value, + cacheProvider, + impactedQueryIds, + path.appending(DataConnectFieldPathSegment(key)), + paths); nestedObjects[key] = en; } else if (value is List) { + //developer.log('detected List for $key'); final nodeList = []; final scalarValueList = []; - for (final item in value) { + for (var i = 0; i < value.length; i++) { + final item = value[i]; if (item is Map) { nodeList.add(_dehydrateNode( - queryId, item, cacheProvider, impactedQueryIds)); + queryId, + item, + cacheProvider, + impactedQueryIds, + path + .appending(DataConnectFieldPathSegment(key)) + .appending(DataConnectListIndexPathSegment(i)), + paths)); } else { // assuming scalar - we don't handle array of arrays scalarValueList.add(item); } } - - // we either do object lists or scalar lists stored with scalars - // we don't handle mixed lists. - if (nodeList.isNotEmpty) { + // we either normalize object lists or scalar lists stored with scalars + // we don't normalize mixed lists. We store them as-is for reconstruction from cache. + if (nodeList.isNotEmpty && scalarValueList.isNotEmpty) { + // mixed type array - we directly store the json as-is + developer + .log('detected mixed type array for key $key. storing as-is'); + scalarValues[key] = value; + } else if (nodeList.isNotEmpty) { nestedObjectLists[key] = nodeList; + } else if (scalarValueList.isNotEmpty) { + scalarValues[key] = scalarValueList; } else { - serverValues[key] = scalarValueList; + // we have empty array. save key as scalar since we can't determine type + scalarValues[key] = value; } + // end list handling } else { - serverValues[key] = value; + //developer.log('detected Scalar for $key'); + scalarValues[key] = value; } } if (guid != null) { - final existingEdo = cacheProvider.getEntityDataObject(guid); - existingEdo.setServerValues(serverValues, queryId); - cacheProvider.saveEntityDataObject(existingEdo); + final existingEdo = cacheProvider.getEntityData(guid); + existingEdo.setServerValues(scalarValues, queryId); + cacheProvider.updateEntityData(existingEdo); impactedQueryIds.addAll(existingEdo.referencedFrom); - return EntityNode( entity: existingEdo, nestedObjects: nestedObjects, nestedObjectLists: nestedObjectLists); } else { return EntityNode( - scalarValues: serverValues, + scalarValues: scalarValues, nestedObjects: nestedObjects, nestedObjectLists: nestedObjectLists); } @@ -108,40 +143,8 @@ class ResultTreeProcessor { /// Takes a dehydrated `EntityNode` tree, fetches the corresponding `EntityDataObject`s /// from the `CacheProvider`, and reconstructs the original data structure. - Future> hydrate( + Future> hydrateResults( EntityNode dehydratedTree, CacheProvider cacheProvider) async { - return await _hydrateNode(dehydratedTree, cacheProvider) - as Map; - } - - Future _hydrateNode( - EntityNode node, CacheProvider cacheProvider) async { - final Map data = {}; - if (node.entity != null) { - final edo = cacheProvider.getEntityDataObject(node.entity!.guid); - data.addAll(edo.fields()); - } - - if (node.scalarValues != null) { - data.addAll(node.scalarValues!); - } - - if (node.nestedObjects != null) { - for (final entry in node.nestedObjects!.entries) { - data[entry.key] = await _hydrateNode(entry.value, cacheProvider); - } - } - - if (node.nestedObjectLists != null) { - for (final entry in node.nestedObjectLists!.entries) { - final list = []; - for (final item in entry.value) { - list.add(await _hydrateNode(item, cacheProvider)); - } - data[entry.key] = list; - } - } - - return data; + return dehydratedTree.toJson(); //default mode for toJson is hydrate } } diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/sqlite_cache_provider.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/sqlite_cache_provider.dart index 3de7c1b44c95..1493f32a05ac 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/sqlite_cache_provider.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/sqlite_cache_provider.dart @@ -39,7 +39,19 @@ class SQLite3CacheProvider implements CacheProvider { final path = join(dbPath.path, '$_identifier.db'); _db = sqlite3.open(path); } - _createTables(); + + int curVersion = _getDatabaseVersion(); + if (curVersion == 0) { + _createTables(); + } else { + int major = curVersion ~/ 1000000; + if (major != 1) { + developer.log( + 'Unsupported schema major version $major detected. Expected 1'); + return false; + } + } + return true; } catch (e) { developer.log('Error initializing SQLiteProvider $e'); @@ -47,19 +59,37 @@ class SQLite3CacheProvider implements CacheProvider { } } + int _getDatabaseVersion() { + final resultSet = _db.select('PRAGMA user_version;'); + return resultSet.first.columnAt(0) as int; + } + + void _setDatabaseVersion(int version) { + _db.execute('PRAGMA user_version = $version;'); + } + void _createTables() { - _db.execute(''' - CREATE TABLE IF NOT EXISTS $resultTreeTable ( - query_id TEXT PRIMARY KEY, - result_tree TEXT - ); - '''); - _db.execute(''' - CREATE TABLE IF NOT EXISTS $entityDataTable ( - guid TEXT PRIMARY KEY, - entity_data_object TEXT - ); - '''); + _db.execute('BEGIN TRANSACTION'); + try { + _db.execute(''' + CREATE TABLE IF NOT EXISTS $resultTreeTable ( + query_id TEXT PRIMARY KEY NOT NULL, + last_accessed REAL NOT NULL, + data TEXT NOT NULL + ); + '''); + _db.execute(''' + CREATE TABLE IF NOT EXISTS $entityDataTable ( + entity_guid TEXT PRIMARY KEY NOT NULL, + data TEXT NOT NULL + ); + '''); + _setDatabaseVersion(1000000); // 1.0.0 + _db.execute('COMMIT'); + } catch (_) { + _db.execute('ROLLBACK'); + rethrow; + } } @override @@ -69,57 +99,84 @@ class SQLite3CacheProvider implements CacheProvider { @override void clear() { - _db.execute('DELETE FROM $resultTreeTable'); - _db.execute('DELETE FROM $entityDataTable'); + _db.execute('BEGIN TRANSACTION'); + try { + _db.execute('DELETE FROM $resultTreeTable'); + _db.execute('DELETE FROM $entityDataTable'); + _db.execute('COMMIT'); + } catch (_) { + _db.execute('ROLLBACK'); + rethrow; + } } @override - EntityDataObject getEntityDataObject(String guid) { + EntityDataObject getEntityData(String guid) { final resultSet = _db.select( - 'SELECT entity_data_object FROM $entityDataTable WHERE guid = ?', + 'SELECT data FROM $entityDataTable WHERE entity_guid = ?', [guid], ); if (resultSet.isEmpty) { - // not found lets create an empty one. + // not found lets create an empty one EntityDataObject edo = EntityDataObject(guid: guid); return edo; } - return EntityDataObject.fromRawJson( - resultSet.first['entity_data_object'] as String); + return EntityDataObject.fromRawJson(resultSet.first['data'] as String); } @override ResultTree? getResultTree(String queryId) { final resultSet = _db.select( - 'SELECT result_tree FROM $resultTreeTable WHERE query_id = ?', + 'SELECT data FROM $resultTreeTable WHERE query_id = ?', [queryId], ); if (resultSet.isEmpty) { return null; } - return ResultTree.fromRawJson(resultSet.first['result_tree'] as String); + _updateLastAccessedTime(queryId); + return ResultTree.fromRawJson(resultSet.first['data'] as String); } - @override - void manageCacheSize() { - // TODO: implement manageCacheSize + void _updateLastAccessedTime(String queryId) { + _db.execute( + 'UPDATE $resultTreeTable SET last_accessed = ? WHERE query_id = ?', + [DateTime.now().millisecondsSinceEpoch / 1000.0, queryId], + ); } @override - void saveEntityDataObject(EntityDataObject edo) { + void updateEntityData(EntityDataObject edo) { String rawJson = edo.toRawJson(); - _db.execute( - 'INSERT OR REPLACE INTO $entityDataTable (guid, entity_data_object) VALUES (?, ?)', - [edo.guid, rawJson], - ); + _db.execute('BEGIN TRANSACTION'); + try { + _db.execute( + 'INSERT OR REPLACE INTO $entityDataTable (entity_guid, data) VALUES (?, ?)', + [edo.guid, rawJson], + ); + _db.execute('COMMIT'); + } catch (_) { + _db.execute('ROLLBACK'); + rethrow; + } } @override - void saveResultTree(String queryId, ResultTree resultTree) { - _db.execute( - 'INSERT OR REPLACE INTO $resultTreeTable (query_id, result_tree) VALUES (?, ?)', - [queryId, resultTree.toRawJson()], - ); + void setResultTree(String queryId, ResultTree resultTree) { + _db.execute('BEGIN TRANSACTION'); + try { + _db.execute( + 'INSERT OR REPLACE INTO $resultTreeTable (query_id, last_accessed, data) VALUES (?, ?, ?)', + [ + queryId, + DateTime.now().millisecondsSinceEpoch / 1000.0, + resultTree.toRawJson() + ], + ); + _db.execute('COMMIT'); + } catch (_) { + _db.execute('ROLLBACK'); + rethrow; + } } } diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/common/common_library.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/common/common_library.dart index e24e7a7e3b89..9247287f5adf 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/common/common_library.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/common/common_library.dart @@ -61,11 +61,18 @@ class TransportOptions { bool? isSecure; } +/// Encapsulates the response from server class ServerResponse { + /// Data returned from server final Map data; + + /// duration for which the results are considered not stale Duration? ttl; - ServerResponse(this.data); + /// Additional data provided in extensions + final Map? extensions; + + ServerResponse(this.data, {this.extensions}); } /// Interface for transports connecting to the DataConnect backend. diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/common/dataconnect_error.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/common/dataconnect_error.dart index 3928a9706536..43b7fd964418 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/common/dataconnect_error.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/common/dataconnect_error.dart @@ -58,16 +58,43 @@ class DataConnectOperationFailureResponseErrorInfo { } /// Path where error occurred. +@immutable sealed class DataConnectPathSegment {} class DataConnectFieldPathSegment extends DataConnectPathSegment { final String field; DataConnectFieldPathSegment(this.field); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is DataConnectFieldPathSegment && + runtimeType == other.runtimeType && + field == other.field; + + @override + int get hashCode => field.hashCode; + + @override + String toString() => field; } class DataConnectListIndexPathSegment extends DataConnectPathSegment { final int index; DataConnectListIndexPathSegment(this.index); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is DataConnectListIndexPathSegment && + runtimeType == other.runtimeType && + index == other.index; + + @override + int get hashCode => index.hashCode; + + @override + String toString() => index.toString(); } typedef Serializer = String Function(Variables vars); diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/core/ref.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/core/ref.dart index e23a00134b70..5b359f6a15b3 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/core/ref.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/core/ref.dart @@ -251,7 +251,7 @@ class QueryRef extends OperationRef { final cacheManager = dataConnect.cacheManager!; bool allowStale = fetchPolicy == QueryFetchPolicy.cacheOnly; //if its cache only, we always allow stale - final cachedData = await cacheManager.get(_queryId, allowStale); + final cachedData = await cacheManager.resultTree(_queryId, allowStale); if (cachedData != null) { try { diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/firebase_data_connect.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/firebase_data_connect.dart index 43cc4fb63e1a..1684e88ed8b8 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/firebase_data_connect.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/firebase_data_connect.dart @@ -25,7 +25,7 @@ import './network/transport_library.dart' if (dart.library.html) './network/rest_library.dart'; import 'cache/cache_data_types.dart'; -import 'cache/cache_manager.dart'; +import 'cache/cache.dart'; /// DataConnect class class FirebaseDataConnect extends FirebasePluginPlatform { diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pb.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pb.dart index 54aed178ad40..1f38718bd4c9 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pb.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pb.dart @@ -1,16 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. // // Generated code. Do not modify. // source: connector_service.proto @@ -27,7 +14,8 @@ import 'dart:core' as $core; import 'package:protobuf/protobuf.dart' as $pb; import 'google/protobuf/struct.pb.dart' as $1; -import 'graphql_error.pb.dart' as $2; +import 'graphql_error.pb.dart' as $3; +import 'graphql_response_extensions.pb.dart' as $4; /// The ExecuteQuery request to Firebase Data Connect. class ExecuteQueryRequest extends $pb.GeneratedMessage { @@ -257,7 +245,8 @@ class ExecuteMutationRequest extends $pb.GeneratedMessage { class ExecuteQueryResponse extends $pb.GeneratedMessage { factory ExecuteQueryResponse({ $1.Struct? data, - $core.Iterable<$2.GraphqlError>? errors, + $core.Iterable<$3.GraphqlError>? errors, + $4.GraphqlResponseExtensions? extensions, }) { final $result = create(); if (data != null) { @@ -266,6 +255,9 @@ class ExecuteQueryResponse extends $pb.GeneratedMessage { if (errors != null) { $result.errors.addAll(errors); } + if (extensions != null) { + $result.extensions = extensions; + } return $result; } ExecuteQueryResponse._() : super(); @@ -283,9 +275,11 @@ class ExecuteQueryResponse extends $pb.GeneratedMessage { createEmptyInstance: create) ..aOM<$1.Struct>(1, _omitFieldNames ? '' : 'data', subBuilder: $1.Struct.create) - ..pc<$2.GraphqlError>( + ..pc<$3.GraphqlError>( 2, _omitFieldNames ? '' : 'errors', $pb.PbFieldType.PM, - subBuilder: $2.GraphqlError.create) + subBuilder: $3.GraphqlError.create) + ..aOM<$4.GraphqlResponseExtensions>(3, _omitFieldNames ? '' : 'extensions', + subBuilder: $4.GraphqlResponseExtensions.create) ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' @@ -329,14 +323,30 @@ class ExecuteQueryResponse extends $pb.GeneratedMessage { /// Errors of this response. @$pb.TagNumber(2) - $core.List<$2.GraphqlError> get errors => $_getList(1); + $core.List<$3.GraphqlError> get errors => $_getList(1); + + /// Additional response information. + @$pb.TagNumber(3) + $4.GraphqlResponseExtensions get extensions => $_getN(2); + @$pb.TagNumber(3) + set extensions($4.GraphqlResponseExtensions v) { + setField(3, v); + } + + @$pb.TagNumber(3) + $core.bool hasExtensions() => $_has(2); + @$pb.TagNumber(3) + void clearExtensions() => clearField(3); + @$pb.TagNumber(3) + $4.GraphqlResponseExtensions ensureExtensions() => $_ensure(2); } /// The ExecuteMutation response from Firebase Data Connect. class ExecuteMutationResponse extends $pb.GeneratedMessage { factory ExecuteMutationResponse({ $1.Struct? data, - $core.Iterable<$2.GraphqlError>? errors, + $core.Iterable<$3.GraphqlError>? errors, + $4.GraphqlResponseExtensions? extensions, }) { final $result = create(); if (data != null) { @@ -345,6 +355,9 @@ class ExecuteMutationResponse extends $pb.GeneratedMessage { if (errors != null) { $result.errors.addAll(errors); } + if (extensions != null) { + $result.extensions = extensions; + } return $result; } ExecuteMutationResponse._() : super(); @@ -362,9 +375,11 @@ class ExecuteMutationResponse extends $pb.GeneratedMessage { createEmptyInstance: create) ..aOM<$1.Struct>(1, _omitFieldNames ? '' : 'data', subBuilder: $1.Struct.create) - ..pc<$2.GraphqlError>( + ..pc<$3.GraphqlError>( 2, _omitFieldNames ? '' : 'errors', $pb.PbFieldType.PM, - subBuilder: $2.GraphqlError.create) + subBuilder: $3.GraphqlError.create) + ..aOM<$4.GraphqlResponseExtensions>(3, _omitFieldNames ? '' : 'extensions', + subBuilder: $4.GraphqlResponseExtensions.create) ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' @@ -409,7 +424,22 @@ class ExecuteMutationResponse extends $pb.GeneratedMessage { /// Errors of this response. @$pb.TagNumber(2) - $core.List<$2.GraphqlError> get errors => $_getList(1); + $core.List<$3.GraphqlError> get errors => $_getList(1); + + /// Additional response information. + @$pb.TagNumber(3) + $4.GraphqlResponseExtensions get extensions => $_getN(2); + @$pb.TagNumber(3) + set extensions($4.GraphqlResponseExtensions v) { + setField(3, v); + } + + @$pb.TagNumber(3) + $core.bool hasExtensions() => $_has(2); + @$pb.TagNumber(3) + void clearExtensions() => clearField(3); + @$pb.TagNumber(3) + $4.GraphqlResponseExtensions ensureExtensions() => $_ensure(2); } const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbenum.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbenum.dart index d53ea6876082..aabd1a01d514 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbenum.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbenum.dart @@ -1,16 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. // // Generated code. Do not modify. // source: connector_service.proto diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbgrpc.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbgrpc.dart index b6704e57cca1..8b1732a117ae 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbgrpc.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbgrpc.dart @@ -1,16 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. // // Generated code. Do not modify. // source: connector_service.proto diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbjson.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbjson.dart index 834e9aad147e..03a497345922 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbjson.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbjson.dart @@ -1,16 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. // // Generated code. Do not modify. // source: connector_service.proto @@ -108,6 +95,14 @@ const ExecuteQueryResponse$json = { '6': '.google.firebase.dataconnect.v1.GraphqlError', '10': 'errors' }, + { + '1': 'extensions', + '3': 3, + '4': 1, + '5': 11, + '6': '.google.firebase.dataconnect.v1.GraphqlResponseExtensions', + '10': 'extensions' + }, ], }; @@ -115,7 +110,9 @@ const ExecuteQueryResponse$json = { final $typed_data.Uint8List executeQueryResponseDescriptor = $convert.base64Decode( 'ChRFeGVjdXRlUXVlcnlSZXNwb25zZRIrCgRkYXRhGAEgASgLMhcuZ29vZ2xlLnByb3RvYnVmLl' 'N0cnVjdFIEZGF0YRJECgZlcnJvcnMYAiADKAsyLC5nb29nbGUuZmlyZWJhc2UuZGF0YWNvbm5l' - 'Y3QudjEuR3JhcGhxbEVycm9yUgZlcnJvcnM='); + 'Y3QudjEuR3JhcGhxbEVycm9yUgZlcnJvcnMSWQoKZXh0ZW5zaW9ucxgDIAEoCzI5Lmdvb2dsZS' + '5maXJlYmFzZS5kYXRhY29ubmVjdC52MS5HcmFwaHFsUmVzcG9uc2VFeHRlbnNpb25zUgpleHRl' + 'bnNpb25z'); @$core.Deprecated('Use executeMutationResponseDescriptor instead') const ExecuteMutationResponse$json = { @@ -137,6 +134,14 @@ const ExecuteMutationResponse$json = { '6': '.google.firebase.dataconnect.v1.GraphqlError', '10': 'errors' }, + { + '1': 'extensions', + '3': 3, + '4': 1, + '5': 11, + '6': '.google.firebase.dataconnect.v1.GraphqlResponseExtensions', + '10': 'extensions' + }, ], }; @@ -144,4 +149,6 @@ const ExecuteMutationResponse$json = { final $typed_data.Uint8List executeMutationResponseDescriptor = $convert.base64Decode( 'ChdFeGVjdXRlTXV0YXRpb25SZXNwb25zZRIrCgRkYXRhGAEgASgLMhcuZ29vZ2xlLnByb3RvYn' 'VmLlN0cnVjdFIEZGF0YRJECgZlcnJvcnMYAiADKAsyLC5nb29nbGUuZmlyZWJhc2UuZGF0YWNv' - 'bm5lY3QudjEuR3JhcGhxbEVycm9yUgZlcnJvcnM='); + 'bm5lY3QudjEuR3JhcGhxbEVycm9yUgZlcnJvcnMSWQoKZXh0ZW5zaW9ucxgDIAEoCzI5Lmdvb2' + 'dsZS5maXJlYmFzZS5kYXRhY29ubmVjdC52MS5HcmFwaHFsUmVzcG9uc2VFeHRlbnNpb25zUgpl' + 'eHRlbnNpb25z'); diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pb.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pb.dart new file mode 100644 index 000000000000..4bcbcd32a4c2 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pb.dart @@ -0,0 +1,166 @@ +// +// Generated code. Do not modify. +// source: google/protobuf/duration.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import + +import 'dart:core' as $core; + +import 'package:fixnum/fixnum.dart' as $fixnum; +import 'package:protobuf/protobuf.dart' as $pb; +import 'package:protobuf/src/protobuf/mixins/well_known.dart' as $mixin; + +/// A Duration represents a signed, fixed-length span of time represented +/// as a count of seconds and fractions of seconds at nanosecond +/// resolution. It is independent of any calendar and concepts like "day" +/// or "month". It is related to Timestamp in that the difference between +/// two Timestamp values is a Duration and it can be added or subtracted +/// from a Timestamp. Range is approximately +-10,000 years. +/// +/// # Examples +/// +/// Example 1: Compute Duration from two Timestamps in pseudo code. +/// +/// Timestamp start = ...; +/// Timestamp end = ...; +/// Duration duration = ...; +/// +/// duration.seconds = end.seconds - start.seconds; +/// duration.nanos = end.nanos - start.nanos; +/// +/// if (duration.seconds < 0 && duration.nanos > 0) { +/// duration.seconds += 1; +/// duration.nanos -= 1000000000; +/// } else if (duration.seconds > 0 && duration.nanos < 0) { +/// duration.seconds -= 1; +/// duration.nanos += 1000000000; +/// } +/// +/// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. +/// +/// Timestamp start = ...; +/// Duration duration = ...; +/// Timestamp end = ...; +/// +/// end.seconds = start.seconds + duration.seconds; +/// end.nanos = start.nanos + duration.nanos; +/// +/// if (end.nanos < 0) { +/// end.seconds -= 1; +/// end.nanos += 1000000000; +/// } else if (end.nanos >= 1000000000) { +/// end.seconds += 1; +/// end.nanos -= 1000000000; +/// } +/// +/// Example 3: Compute Duration from datetime.timedelta in Python. +/// +/// td = datetime.timedelta(days=3, minutes=10) +/// duration = Duration() +/// duration.FromTimedelta(td) +/// +/// # JSON Mapping +/// +/// In JSON format, the Duration type is encoded as a string rather than an +/// object, where the string ends in the suffix "s" (indicating seconds) and +/// is preceded by the number of seconds, with nanoseconds expressed as +/// fractional seconds. For example, 3 seconds with 0 nanoseconds should be +/// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should +/// be expressed in JSON format as "3.000000001s", and 3 seconds and 1 +/// microsecond should be expressed in JSON format as "3.000001s". +class Duration extends $pb.GeneratedMessage with $mixin.DurationMixin { + factory Duration({ + $fixnum.Int64? seconds, + $core.int? nanos, + }) { + final $result = create(); + if (seconds != null) { + $result.seconds = seconds; + } + if (nanos != null) { + $result.nanos = nanos; + } + return $result; + } + Duration._() : super(); + factory Duration.fromBuffer($core.List<$core.int> i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(i, r); + factory Duration.fromJson($core.String i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'Duration', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'google.protobuf'), + createEmptyInstance: create, + toProto3Json: $mixin.DurationMixin.toProto3JsonHelper, + fromProto3Json: $mixin.DurationMixin.fromProto3JsonHelper) + ..aInt64(1, _omitFieldNames ? '' : 'seconds') + ..a<$core.int>(2, _omitFieldNames ? '' : 'nanos', $pb.PbFieldType.O3) + ..hasRequiredFields = false; + + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + Duration clone() => Duration()..mergeFromMessage(this); + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + Duration copyWith(void Function(Duration) updates) => + super.copyWith((message) => updates(message as Duration)) as Duration; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static Duration create() => Duration._(); + Duration createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static Duration getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static Duration? _defaultInstance; + + /// Signed seconds of the span of time. Must be from -315,576,000,000 + /// to +315,576,000,000 inclusive. Note: these bounds are computed from: + /// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years + @$pb.TagNumber(1) + $fixnum.Int64 get seconds => $_getI64(0); + @$pb.TagNumber(1) + set seconds($fixnum.Int64 v) { + $_setInt64(0, v); + } + + @$pb.TagNumber(1) + $core.bool hasSeconds() => $_has(0); + @$pb.TagNumber(1) + void clearSeconds() => clearField(1); + + /// Signed fractions of a second at nanosecond resolution of the span + /// of time. Durations less than one second are represented with a 0 + /// `seconds` field and a positive or negative `nanos` field. For durations + /// of one second or more, a non-zero value for the `nanos` field must be + /// of the same sign as the `seconds` field. Must be from -999,999,999 + /// to +999,999,999 inclusive. + @$pb.TagNumber(2) + $core.int get nanos => $_getIZ(1); + @$pb.TagNumber(2) + set nanos($core.int v) { + $_setSignedInt32(1, v); + } + + @$pb.TagNumber(2) + $core.bool hasNanos() => $_has(1); + @$pb.TagNumber(2) + void clearNanos() => clearField(2); +} + +const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); +const _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pbenum.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pbenum.dart new file mode 100644 index 000000000000..1a2c58d81056 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pbenum.dart @@ -0,0 +1,10 @@ +// +// Generated code. Do not modify. +// source: google/protobuf/duration.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pbjson.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pbjson.dart new file mode 100644 index 000000000000..5847acb2d458 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pbjson.dart @@ -0,0 +1,28 @@ +// +// Generated code. Do not modify. +// source: google/protobuf/duration.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use durationDescriptor instead') +const Duration$json = { + '1': 'Duration', + '2': [ + {'1': 'seconds', '3': 1, '4': 1, '5': 3, '10': 'seconds'}, + {'1': 'nanos', '3': 2, '4': 1, '5': 5, '10': 'nanos'}, + ], +}; + +/// Descriptor for `Duration`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List durationDescriptor = $convert.base64Decode( + 'CghEdXJhdGlvbhIYCgdzZWNvbmRzGAEgASgDUgdzZWNvbmRzEhQKBW5hbm9zGAIgASgFUgVuYW' + '5vcw=='); diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pb.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pb.dart index 7b9093d681ff..42d55e426602 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pb.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pb.dart @@ -1,16 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. // // Generated code. Do not modify. // source: google/protobuf/struct.proto diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pbenum.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pbenum.dart index b5acd2512df2..7f9bf0cbf322 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pbenum.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pbenum.dart @@ -1,16 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. // // Generated code. Do not modify. // source: google/protobuf/struct.proto diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pbjson.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pbjson.dart index 3f53dbf0a988..c0693f570058 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pbjson.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pbjson.dart @@ -1,16 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. // // Generated code. Do not modify. // source: google/protobuf/struct.proto diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pb.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pb.dart index a50398e29488..2def4cc62994 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pb.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pb.dart @@ -1,16 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. // // Generated code. Do not modify. // source: graphql_error.proto diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pbenum.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pbenum.dart index 9f28e16d3c23..53454c94a217 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pbenum.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pbenum.dart @@ -1,16 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. // // Generated code. Do not modify. // source: graphql_error.proto diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pbjson.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pbjson.dart index ae48a28388dc..9a90ffc79685 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pbjson.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pbjson.dart @@ -1,16 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. // // Generated code. Do not modify. // source: graphql_error.proto diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pb.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pb.dart new file mode 100644 index 000000000000..c86ba89dbd75 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pb.dart @@ -0,0 +1,222 @@ +// +// Generated code. Do not modify. +// source: graphql_response_extensions.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +import 'google/protobuf/duration.pb.dart' as $2; +import 'google/protobuf/struct.pb.dart' as $1; + +/// Data Connect specific properties for a path under response.data. +/// (-- Design doc: http://go/fdc-caching-wire-protocol --) +class GraphqlResponseExtensions_DataConnectProperties + extends $pb.GeneratedMessage { + factory GraphqlResponseExtensions_DataConnectProperties({ + $1.ListValue? path, + $core.String? entityId, + $core.Iterable<$core.String>? entityIds, + $2.Duration? maxAge, + }) { + final $result = create(); + if (path != null) { + $result.path = path; + } + if (entityId != null) { + $result.entityId = entityId; + } + if (entityIds != null) { + $result.entityIds.addAll(entityIds); + } + if (maxAge != null) { + $result.maxAge = maxAge; + } + return $result; + } + GraphqlResponseExtensions_DataConnectProperties._() : super(); + factory GraphqlResponseExtensions_DataConnectProperties.fromBuffer( + $core.List<$core.int> i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(i, r); + factory GraphqlResponseExtensions_DataConnectProperties.fromJson( + $core.String i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames + ? '' + : 'GraphqlResponseExtensions.DataConnectProperties', + package: const $pb.PackageName( + _omitMessageNames ? '' : 'google.firebase.dataconnect.v1'), + createEmptyInstance: create) + ..aOM<$1.ListValue>(1, _omitFieldNames ? '' : 'path', + subBuilder: $1.ListValue.create) + ..aOS(2, _omitFieldNames ? '' : 'entityId') + ..pPS(3, _omitFieldNames ? '' : 'entityIds') + ..aOM<$2.Duration>(4, _omitFieldNames ? '' : 'maxAge', + subBuilder: $2.Duration.create) + ..hasRequiredFields = false; + + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + GraphqlResponseExtensions_DataConnectProperties clone() => + GraphqlResponseExtensions_DataConnectProperties()..mergeFromMessage(this); + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + GraphqlResponseExtensions_DataConnectProperties copyWith( + void Function(GraphqlResponseExtensions_DataConnectProperties) + updates) => + super.copyWith((message) => updates( + message as GraphqlResponseExtensions_DataConnectProperties)) + as GraphqlResponseExtensions_DataConnectProperties; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GraphqlResponseExtensions_DataConnectProperties create() => + GraphqlResponseExtensions_DataConnectProperties._(); + GraphqlResponseExtensions_DataConnectProperties createEmptyInstance() => + create(); + static $pb.PbList + createRepeated() => + $pb.PbList(); + @$core.pragma('dart2js:noInline') + static GraphqlResponseExtensions_DataConnectProperties getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor< + GraphqlResponseExtensions_DataConnectProperties>(create); + static GraphqlResponseExtensions_DataConnectProperties? _defaultInstance; + + /// The path under response.data where the rest of the fields apply. + /// Each element may be a string (field name) or number (array index). + /// The root of response.data is denoted by the empty list `[]`. + /// (-- To simplify client logic, the server should never set this to null. + /// i.e. Use `[]` if the properties below apply to everything in data. --) + @$pb.TagNumber(1) + $1.ListValue get path => $_getN(0); + @$pb.TagNumber(1) + set path($1.ListValue v) { + setField(1, v); + } + + @$pb.TagNumber(1) + $core.bool hasPath() => $_has(0); + @$pb.TagNumber(1) + void clearPath() => clearField(1); + @$pb.TagNumber(1) + $1.ListValue ensurePath() => $_ensure(0); + + /// A single Entity ID. Set if the path points to a single entity. + @$pb.TagNumber(2) + $core.String get entityId => $_getSZ(1); + @$pb.TagNumber(2) + set entityId($core.String v) { + $_setString(1, v); + } + + @$pb.TagNumber(2) + $core.bool hasEntityId() => $_has(1); + @$pb.TagNumber(2) + void clearEntityId() => clearField(2); + + /// A list of Entity IDs. Set if the path points to an array of entities. An + /// ID is present for each element of the array at the corresponding index. + @$pb.TagNumber(3) + $core.List<$core.String> get entityIds => $_getList(2); + + /// The server-suggested duration before data under path is considered stale. + /// (-- Right now, this field is never set. For future plans, see + /// http://go/fdc-sdk-caching-config#heading=h.rmvncy2rao3g --) + @$pb.TagNumber(4) + $2.Duration get maxAge => $_getN(3); + @$pb.TagNumber(4) + set maxAge($2.Duration v) { + setField(4, v); + } + + @$pb.TagNumber(4) + $core.bool hasMaxAge() => $_has(3); + @$pb.TagNumber(4) + void clearMaxAge() => clearField(4); + @$pb.TagNumber(4) + $2.Duration ensureMaxAge() => $_ensure(3); +} + +/// GraphqlResponseExtensions contains additional information of +/// `GraphqlResponse` or `ExecuteQueryResponse`. +class GraphqlResponseExtensions extends $pb.GeneratedMessage { + factory GraphqlResponseExtensions({ + $core.Iterable? + dataConnect, + }) { + final $result = create(); + if (dataConnect != null) { + $result.dataConnect.addAll(dataConnect); + } + return $result; + } + GraphqlResponseExtensions._() : super(); + factory GraphqlResponseExtensions.fromBuffer($core.List<$core.int> i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(i, r); + factory GraphqlResponseExtensions.fromJson($core.String i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GraphqlResponseExtensions', + package: const $pb.PackageName( + _omitMessageNames ? '' : 'google.firebase.dataconnect.v1'), + createEmptyInstance: create) + ..pc( + 1, _omitFieldNames ? '' : 'dataConnect', $pb.PbFieldType.PM, + subBuilder: GraphqlResponseExtensions_DataConnectProperties.create) + ..hasRequiredFields = false; + + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + GraphqlResponseExtensions clone() => + GraphqlResponseExtensions()..mergeFromMessage(this); + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + GraphqlResponseExtensions copyWith( + void Function(GraphqlResponseExtensions) updates) => + super.copyWith((message) => updates(message as GraphqlResponseExtensions)) + as GraphqlResponseExtensions; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GraphqlResponseExtensions create() => GraphqlResponseExtensions._(); + GraphqlResponseExtensions createEmptyInstance() => create(); + static $pb.PbList createRepeated() => + $pb.PbList(); + @$core.pragma('dart2js:noInline') + static GraphqlResponseExtensions getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GraphqlResponseExtensions? _defaultInstance; + + /// Data Connect specific GraphQL extension, a list of paths and properties. + /// (-- Future fields should go inside to avoid name conflicts with other GQL + /// extensions in the wild unless we're implementing a common 3P pattern in + /// extensions such as versioning and telemetry. --) + @$pb.TagNumber(1) + $core.List get dataConnect => + $_getList(0); +} + +const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); +const _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pbenum.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pbenum.dart new file mode 100644 index 000000000000..924da2c849bb --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pbenum.dart @@ -0,0 +1,10 @@ +// +// Generated code. Do not modify. +// source: graphql_response_extensions.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pbjson.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pbjson.dart new file mode 100644 index 000000000000..a1022d1267e2 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pbjson.dart @@ -0,0 +1,65 @@ +// +// Generated code. Do not modify. +// source: graphql_response_extensions.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use graphqlResponseExtensionsDescriptor instead') +const GraphqlResponseExtensions$json = { + '1': 'GraphqlResponseExtensions', + '2': [ + { + '1': 'data_connect', + '3': 1, + '4': 3, + '5': 11, + '6': + '.google.firebase.dataconnect.v1.GraphqlResponseExtensions.DataConnectProperties', + '10': 'dataConnect' + }, + ], + '3': [GraphqlResponseExtensions_DataConnectProperties$json], +}; + +@$core.Deprecated('Use graphqlResponseExtensionsDescriptor instead') +const GraphqlResponseExtensions_DataConnectProperties$json = { + '1': 'DataConnectProperties', + '2': [ + { + '1': 'path', + '3': 1, + '4': 1, + '5': 11, + '6': '.google.protobuf.ListValue', + '10': 'path' + }, + {'1': 'entity_id', '3': 2, '4': 1, '5': 9, '10': 'entityId'}, + {'1': 'entity_ids', '3': 3, '4': 3, '5': 9, '10': 'entityIds'}, + { + '1': 'max_age', + '3': 4, + '4': 1, + '5': 11, + '6': '.google.protobuf.Duration', + '10': 'maxAge' + }, + ], +}; + +/// Descriptor for `GraphqlResponseExtensions`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List graphqlResponseExtensionsDescriptor = $convert.base64Decode( + 'ChlHcmFwaHFsUmVzcG9uc2VFeHRlbnNpb25zEnIKDGRhdGFfY29ubmVjdBgBIAMoCzJPLmdvb2' + 'dsZS5maXJlYmFzZS5kYXRhY29ubmVjdC52MS5HcmFwaHFsUmVzcG9uc2VFeHRlbnNpb25zLkRh' + 'dGFDb25uZWN0UHJvcGVydGllc1ILZGF0YUNvbm5lY3QatwEKFURhdGFDb25uZWN0UHJvcGVydG' + 'llcxIuCgRwYXRoGAEgASgLMhouZ29vZ2xlLnByb3RvYnVmLkxpc3RWYWx1ZVIEcGF0aBIbCgll' + 'bnRpdHlfaWQYAiABKAlSCGVudGl0eUlkEh0KCmVudGl0eV9pZHMYAyADKAlSCWVudGl0eUlkcx' + 'IyCgdtYXhfYWdlGAQgASgLMhkuZ29vZ2xlLnByb3RvYnVmLkR1cmF0aW9uUgZtYXhBZ2U='); diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/grpc_transport.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/grpc_transport.dart index e0c3e660333b..180bb209168b 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/grpc_transport.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/grpc_transport.dart @@ -173,6 +173,9 @@ ServerResponse handleResponse(CommonResponse commonResponse) { Map? jsond = commonResponse.data as Map?; String jsonEncoded = jsonEncode(commonResponse.data); + Map? jsonExt = + commonResponse.extensions as Map?; + if (commonResponse.errors.isNotEmpty) { Map? data = jsonDecode(jsonEncoded) as Map?; @@ -202,7 +205,7 @@ ServerResponse handleResponse(CommonResponse commonResponse) { // no errors - return a standard response if (jsond != null) { - return ServerResponse(jsond); + return ServerResponse(jsond, extensions: jsonExt); } else { return ServerResponse({}); } @@ -219,20 +222,21 @@ DataConnectTransport getTransport( GRPCTransport(transportOptions, options, appId, sdkType, appCheck); class CommonResponse { - CommonResponse(this.deserializer, this.data, this.errors); + CommonResponse(this.deserializer, this.data, this.errors, this.extensions); static CommonResponse fromExecuteMutation( Deserializer deserializer, ExecuteMutationResponse response) { return CommonResponse( - deserializer, response.data.toProto3Json(), response.errors); + deserializer, response.data.toProto3Json(), response.errors, null); } static CommonResponse fromExecuteQuery( Deserializer deserializer, ExecuteQueryResponse response) { - return CommonResponse( - deserializer, response.data.toProto3Json(), response.errors); + return CommonResponse(deserializer, response.data.toProto3Json(), + response.errors, response.extensions.toProto3Json()); } final Deserializer deserializer; final Object? data; final List errors; + final Object? extensions; } diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/rest_transport.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/rest_transport.dart index 0ee037744ffd..708d6fa02010 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/rest_transport.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/rest_transport.dart @@ -117,6 +117,7 @@ class RestTransport implements DataConnectTransport { ); Map bodyJson = jsonDecode(r.body) as Map; + if (r.statusCode != 200) { String message = bodyJson.containsKey('message') ? bodyJson['message']! : r.body; @@ -127,7 +128,13 @@ class RestTransport implements DataConnectTransport { "Received a status code of ${r.statusCode} with a message '$message'", ); } - return ServerResponse(bodyJson); + final Map? extensions = + bodyJson['extensions'] as Map?; + final serverResponse = ServerResponse(bodyJson, extensions: extensions); + if (extensions != null && extensions.containsKey('ttl')) { + serverResponse.ttl = Duration(seconds: extensions['ttl'] as int); + } + return serverResponse; } on Exception catch (e) { if (e is DataConnectError) { rethrow; diff --git a/packages/firebase_data_connect/firebase_data_connect/protos/connector_service.proto b/packages/firebase_data_connect/firebase_data_connect/protos/connector_service.proto index a93b79234362..0d8e28c5221a 100644 --- a/packages/firebase_data_connect/firebase_data_connect/protos/connector_service.proto +++ b/packages/firebase_data_connect/firebase_data_connect/protos/connector_service.proto @@ -1,30 +1,29 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -// Adapted from http://google3/google/firebase/dataconnect/v1main/connector_service.proto;rcl=596717236 +// Adapted from third_party/firebase/dataconnect/emulator/server/api/connector_service.proto syntax = "proto3"; package google.firebase.dataconnect.v1; import "google/api/field_behavior.proto"; -import "graphql_error.proto"; import "google/protobuf/struct.proto"; +import "graphql_error.proto"; +import "graphql_response_extensions.proto"; -option java_package = "google.firebase.dataconnect.proto"; +option java_package = "com.google.firebase.dataconnect.api"; option java_multiple_files = true; // Firebase Data Connect provides means to deploy a set of predefined GraphQL @@ -40,12 +39,11 @@ option java_multiple_files = true; // token. service ConnectorService { // Execute a predefined query in a Connector. - rpc ExecuteQuery(ExecuteQueryRequest) returns (ExecuteQueryResponse) { - } + rpc ExecuteQuery(ExecuteQueryRequest) returns (ExecuteQueryResponse) {} // Execute a predefined mutation in a Connector. - rpc ExecuteMutation(ExecuteMutationRequest) returns (ExecuteMutationResponse) { - } + rpc ExecuteMutation(ExecuteMutationRequest) + returns (ExecuteMutationResponse) {} } // The ExecuteQuery request to Firebase Data Connect. @@ -94,6 +92,8 @@ message ExecuteQueryResponse { google.protobuf.Struct data = 1; // Errors of this response. repeated GraphqlError errors = 2; + // Additional response information. + GraphqlResponseExtensions extensions = 3; } // The ExecuteMutation response from Firebase Data Connect. @@ -102,4 +102,6 @@ message ExecuteMutationResponse { google.protobuf.Struct data = 1; // Errors of this response. repeated GraphqlError errors = 2; -} \ No newline at end of file + // Additional response information. + GraphqlResponseExtensions extensions = 3; +} diff --git a/packages/firebase_data_connect/firebase_data_connect/protos/firebase/graphql_response_extensions.proto b/packages/firebase_data_connect/firebase_data_connect/protos/firebase/graphql_response_extensions.proto new file mode 100644 index 000000000000..e04e1927ada4 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/protos/firebase/graphql_response_extensions.proto @@ -0,0 +1,59 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Adapted from third_party/firebase/dataconnect/emulator/server/api/graphql_response_extensions.proto + +syntax = "proto3"; + +package google.firebase.dataconnect.v1; + +import "google/protobuf/duration.proto"; +import "google/protobuf/struct.proto"; + +option java_multiple_files = true; +option java_outer_classname = "GraphqlResponseExtensionsProto"; +option java_package = "com.google.firebase.dataconnect.api"; + +// GraphqlResponseExtensions contains additional information of +// `GraphqlResponse` or `ExecuteQueryResponse`. +message GraphqlResponseExtensions { + // Data Connect specific properties for a path under response.data. + // (-- Design doc: http://go/fdc-caching-wire-protocol --) + message DataConnectProperties { + // The path under response.data where the rest of the fields apply. + // Each element may be a string (field name) or number (array index). + // The root of response.data is denoted by the empty list `[]`. + // (-- To simplify client logic, the server should never set this to null. + // i.e. Use `[]` if the properties below apply to everything in data. --) + google.protobuf.ListValue path = 1; + + // A single Entity ID. Set if the path points to a single entity. + string entity_id = 2; + + // A list of Entity IDs. Set if the path points to an array of entities. An + // ID is present for each element of the array at the corresponding index. + repeated string entity_ids = 3; + + // The server-suggested duration before data under path is considered stale. + // (-- Right now, this field is never set. For future plans, see + // http://go/fdc-sdk-caching-config#heading=h.rmvncy2rao3g --) + google.protobuf.Duration max_age = 4; + } + // Data Connect specific GraphQL extension, a list of paths and properties. + // (-- Future fields should go inside to avoid name conflicts with other GQL + // extensions in the wild unless we're implementing a common 3P pattern in + // extensions such as versioning and telemetry. --) + repeated DataConnectProperties data_connect = 1; +} + diff --git a/packages/firebase_data_connect/firebase_data_connect/protos/google/duration.proto b/packages/firebase_data_connect/firebase_data_connect/protos/google/duration.proto new file mode 100644 index 000000000000..cb7cf0e926cc --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/protos/google/duration.proto @@ -0,0 +1,116 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/durationpb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "DurationProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// A Duration represents a signed, fixed-length span of time represented +// as a count of seconds and fractions of seconds at nanosecond +// resolution. It is independent of any calendar and concepts like "day" +// or "month". It is related to Timestamp in that the difference between +// two Timestamp values is a Duration and it can be added or subtracted +// from a Timestamp. Range is approximately +-10,000 years. +// +// # Examples +// +// Example 1: Compute Duration from two Timestamps in pseudo code. +// +// Timestamp start = ...; +// Timestamp end = ...; +// Duration duration = ...; +// +// duration.seconds = end.seconds - start.seconds; +// duration.nanos = end.nanos - start.nanos; +// +// if (duration.seconds < 0 && duration.nanos > 0) { +// duration.seconds += 1; +// duration.nanos -= 1000000000; +// } else if (duration.seconds > 0 && duration.nanos < 0) { +// duration.seconds -= 1; +// duration.nanos += 1000000000; +// } +// +// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. +// +// Timestamp start = ...; +// Duration duration = ...; +// Timestamp end = ...; +// +// end.seconds = start.seconds + duration.seconds; +// end.nanos = start.nanos + duration.nanos; +// +// if (end.nanos < 0) { +// end.seconds -= 1; +// end.nanos += 1000000000; +// } else if (end.nanos >= 1000000000) { +// end.seconds += 1; +// end.nanos -= 1000000000; +// } +// +// Example 3: Compute Duration from datetime.timedelta in Python. +// +// td = datetime.timedelta(days=3, minutes=10) +// duration = Duration() +// duration.FromTimedelta(td) +// +// # JSON Mapping +// +// In JSON format, the Duration type is encoded as a string rather than an +// object, where the string ends in the suffix "s" (indicating seconds) and +// is preceded by the number of seconds, with nanoseconds expressed as +// fractional seconds. For example, 3 seconds with 0 nanoseconds should be +// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should +// be expressed in JSON format as "3.000000001s", and 3 seconds and 1 +// microsecond should be expressed in JSON format as "3.000001s". +// +// +message Duration { + // Signed seconds of the span of time. Must be from -315,576,000,000 + // to +315,576,000,000 inclusive. Note: these bounds are computed from: + // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years + int64 seconds = 1; + + // Signed fractions of a second at nanosecond resolution of the span + // of time. Durations less than one second are represented with a 0 + // `seconds` field and a positive or negative `nanos` field. For durations + // of one second or more, a non-zero value for the `nanos` field must be + // of the same sign as the `seconds` field. Must be from -999,999,999 + // to +999,999,999 inclusive. + int32 nanos = 2; +} \ No newline at end of file diff --git a/packages/firebase_data_connect/firebase_data_connect/pubspec.yaml b/packages/firebase_data_connect/firebase_data_connect/pubspec.yaml index e71288430a1e..971425f165dc 100644 --- a/packages/firebase_data_connect/firebase_data_connect/pubspec.yaml +++ b/packages/firebase_data_connect/firebase_data_connect/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: firebase_auth: ^6.1.3 firebase_core: ^4.3.0 firebase_core_platform_interface: ^6.0.2 + fixnum: ^1.1.1 flutter: sdk: flutter grpc: ^3.2.4 diff --git a/packages/firebase_data_connect/firebase_data_connect/test/src/cache/cache_manager_test.dart b/packages/firebase_data_connect/firebase_data_connect/test/src/cache/cache_manager_test.dart index db40791ad2a0..dec53744b7e3 100644 --- a/packages/firebase_data_connect/firebase_data_connect/test/src/cache/cache_manager_test.dart +++ b/packages/firebase_data_connect/firebase_data_connect/test/src/cache/cache_manager_test.dart @@ -18,7 +18,7 @@ import 'package:firebase_data_connect/src/core/ref.dart'; import 'package:firebase_data_connect/src/network/rest_library.dart'; import 'package:firebase_data_connect/src/common/common_library.dart'; import 'package:firebase_data_connect/src/cache/cache_data_types.dart'; -import 'package:firebase_data_connect/src/cache/cache_manager.dart'; +import 'package:firebase_data_connect/src/cache/cache.dart'; import 'package:firebase_data_connect/src/cache/cache_provider.dart'; import 'package:firebase_data_connect/src/cache/in_memory_cache_provider.dart'; import 'package:firebase_data_connect/src/cache/sqlite_cache_provider.dart'; @@ -51,18 +51,27 @@ void main() { const String simpleQueryResponse = ''' {"data": {"items":[ - {"desc":"itemDesc1","name":"itemOne", "cacheId":"123","price":4}, - {"desc":"itemDesc2","name":"itemTwo", "cacheId":"345","price":7} + {"desc":"itemDesc1","name":"itemOne","price":4}, + {"desc":"itemDesc2","name":"itemTwo","price":7} ]}} '''; + final Map simpleQueryExtensions = { + 'dataConnect': [ + { + 'path': ['items'], + 'entityIds': ['123', '345'] + } + ] + }; + // query that updates the price for cacheId 123 to 11 const String simpleQueryResponseUpdate = ''' {"data": {"items":[ - {"desc":"itemDesc1","name":"itemOne", "cacheId":"123","price":11}, - {"desc":"itemDesc2","name":"itemTwo", "cacheId":"345","price":7} + {"desc":"itemDesc1","name":"itemOne","price":11}, + {"desc":"itemDesc2","name":"itemTwo","price":7} ]}} '''; @@ -70,10 +79,19 @@ void main() { // query two has same object as query one so should refer to same Entity. const String simpleQueryTwoResponse = ''' {"data": { - "item": { "desc":"itemDesc1","name":"itemOne", "cacheId":"123","price":4 } + "item": { "desc":"itemDesc1","name":"itemOne","price":4 } }} '''; + final Map simpleQueryTwoExtensions = { + 'dataConnect': [ + { + 'path': ['item'], + 'entityId': '123' + } + ] + }; + group('Cache Provider Tests', () { setUp(() async { mockApp = MockFirebaseApp(); @@ -126,9 +144,11 @@ void main() { Map jsonData = jsonDecode(simpleQueryResponse) as Map; - await cache.update('itemsSimple', ServerResponse(jsonData)); + await cache.update('itemsSimple', + ServerResponse(jsonData, extensions: simpleQueryExtensions)); - Map? cachedData = await cache.get('itemsSimple', true); + Map? cachedData = + await cache.resultTree('itemsSimple', true); expect(jsonData['data'], cachedData); }); // test set get @@ -144,8 +164,8 @@ void main() { edo.updateServerValue('name', 'test', null); edo.updateServerValue('desc', 'testDesc', null); - cp.saveEntityDataObject(edo); - EntityDataObject edo2 = cp.getEntityDataObject('1234'); + cp.updateEntityData(edo); + EntityDataObject edo2 = cp.getEntityData('1234'); expect(edo.fields().length, edo2.fields().length); expect(edo.fields()['name'], edo2.fields()['name']); @@ -162,21 +182,24 @@ void main() { Map jsonDataOne = jsonDecode(simpleQueryResponse) as Map; - await cache.update(queryOneId, ServerResponse(jsonDataOne)); + await cache.update(queryOneId, + ServerResponse(jsonDataOne, extensions: simpleQueryExtensions)); Map jsonDataTwo = jsonDecode(simpleQueryTwoResponse) as Map; - await cache.update(queryTwoId, ServerResponse(jsonDataTwo)); + await cache.update(queryTwoId, + ServerResponse(jsonDataTwo, extensions: simpleQueryTwoExtensions)); Map jsonDataOneUpdate = jsonDecode(simpleQueryResponseUpdate) as Map; - await cache.update(queryOneId, ServerResponse(jsonDataOneUpdate)); + await cache.update(queryOneId, + ServerResponse(jsonDataOneUpdate, extensions: simpleQueryExtensions)); // shared object should be updated. // now reload query two from cache and check object value. // it should be updated Map? jsonDataTwoUpdated = - await cache.get(queryTwoId, true); + await cache.resultTree(queryTwoId, true); if (jsonDataTwoUpdated == null) { fail('No query two found in cache'); } @@ -194,16 +217,16 @@ void main() { await cp.initialize(); String oid = '1234'; - EntityDataObject edo = cp.getEntityDataObject(oid); + EntityDataObject edo = cp.getEntityData(oid); String testValue = 'testValue'; String testProp = 'testProp'; edo.updateServerValue(testProp, testValue, null); - cp.saveEntityDataObject(edo); + cp.updateEntityData(edo); - EntityDataObject edo2 = cp.getEntityDataObject(oid); + EntityDataObject edo2 = cp.getEntityData(oid); String value = edo2.fields()[testProp]; expect(testValue, value); @@ -221,7 +244,8 @@ void main() { Map jsonData = jsonDecode(simpleQueryResponse) as Map; - await cache.update('itemsSimple', ServerResponse(jsonData)); + await cache.update('itemsSimple', + ServerResponse(jsonData, extensions: simpleQueryExtensions)); QueryRef ref = QueryRef( dataConnect, @@ -254,5 +278,47 @@ void main() { expect(resultDelayed.source, DataSource.server); }); }); + + test('Test AnyValue Caching', () async { + if (dataConnect.cacheManager == null) { + fail('No cache available'); + } + + Cache cache = dataConnect.cacheManager!; + + const String anyValueSingleData = ''' + {"data": {"anyValueItem": + { "name": "AnyItem B", + "blob": {"values":["A", 45, {"embedKey": "embedVal"}, ["A", "AA"]]} + } + }} + '''; + + final Map anyValueSingleExt = { + 'dataConnect': [ + { + 'path': ['anyValueItem'], + 'entityId': 'AnyValueItemSingle_ID' + } + ] + }; + + Map jsonData = + jsonDecode(anyValueSingleData) as Map; + + await cache.update('queryAnyValue', + ServerResponse(jsonData, extensions: anyValueSingleExt)); + + Map? cachedData = + await cache.resultTree('queryAnyValue', true); + + expect(cachedData?['anyValueItem']?['name'], 'AnyItem B'); + List values = cachedData?['anyValueItem']?['blob']?['values']; + expect(values.length, 4); + expect(values[0], 'A'); + expect(values[1], 45); + expect(values[2], {'embedKey': 'embedVal'}); + expect(values[3], ['A', 'AA']); + }); }); // test group } //main diff --git a/packages/firebase_data_connect/firebase_data_connect/test/src/cache/result_tree_processor_test.dart b/packages/firebase_data_connect/firebase_data_connect/test/src/cache/result_tree_processor_test.dart index 6d3c71d09a17..9b347425c7dc 100644 --- a/packages/firebase_data_connect/firebase_data_connect/test/src/cache/result_tree_processor_test.dart +++ b/packages/firebase_data_connect/firebase_data_connect/test/src/cache/result_tree_processor_test.dart @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +import 'package:firebase_data_connect/src/cache/cache_data_types.dart'; import 'package:firebase_data_connect/src/cache/result_tree_processor.dart'; +import 'package:firebase_data_connect/src/common/common_library.dart'; import 'package:flutter_test/flutter_test.dart'; import 'dart:convert'; @@ -23,19 +25,46 @@ void main() { const String simpleQueryResponse = ''' {"data": {"items":[ - {"desc":"itemDesc1","name":"itemOne", "cacheId":"123","price":4}, - {"desc":"itemDesc2","name":"itemTwo", "cacheId":"345","price":7} + {"desc":"itemDesc1","name":"itemOne","price":4}, + {"desc":"itemDesc2","name":"itemTwo","price":7} ]}} '''; + final Map simpleQueryPaths = { + DataConnectPath([ + DataConnectFieldPathSegment('items'), + DataConnectListIndexPathSegment(0) + ]): PathMetadata( + path: DataConnectPath([ + DataConnectFieldPathSegment('items'), + DataConnectListIndexPathSegment(0) + ]), + entityId: '123'), + DataConnectPath([ + DataConnectFieldPathSegment('items'), + DataConnectListIndexPathSegment(1) + ]): PathMetadata( + path: DataConnectPath([ + DataConnectFieldPathSegment('items'), + DataConnectListIndexPathSegment(1) + ]), + entityId: '345'), + }; + // query two has same object as query one so should refer to same Entity. const String simpleQueryResponseTwo = ''' {"data": { - "item": { "desc":"itemDesc1","name":"itemOne", "cacheId":"123","price":4 } + "item": { "desc":"itemDesc1","name":"itemOne","price":4 } }} '''; + final Map simpleQueryTwoPaths = { + DataConnectPath([DataConnectFieldPathSegment('item')]): PathMetadata( + path: DataConnectPath([DataConnectFieldPathSegment('item')]), + entityId: '123'), + }; + group('CacheProviderTests', () { // Dehydrate two queries sharing a single object. // Confirm that same EntityDataObject is present in both the dehydrated queries @@ -45,8 +74,8 @@ void main() { Map jsonData = jsonDecode(simpleQueryResponse) as Map; - DehydrationResult result = - await rp.dehydrate('itemsSimple', jsonData['data'], cp); + DehydrationResult result = await rp.dehydrateResults( + 'itemsSimple', jsonData['data'], cp, simpleQueryPaths); expect(result.dehydratedTree.nestedObjectLists?.length, 1); expect(result.dehydratedTree.nestedObjectLists?['items']?.length, 2); expect(result.dehydratedTree.nestedObjectLists?['items']?.first.entity, @@ -54,8 +83,8 @@ void main() { Map jsonDataTwo = jsonDecode(simpleQueryResponseTwo) as Map; - DehydrationResult resultTwo = - await rp.dehydrate('itemsSimpleTwo', jsonDataTwo, cp); + DehydrationResult resultTwo = await rp.dehydrateResults( + 'itemsSimpleTwo', jsonDataTwo['data'], cp, simpleQueryTwoPaths); List? guids = result.dehydratedTree.nestedObjectLists?['items'] ?.map((item) => item.entity?.guid) From 2334cf01fa30696f006cd52cb13756905269b487 Mon Sep 17 00:00:00 2001 From: Jude Selase Kwashie <64037520+SelaseKay@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:09:26 +0000 Subject: [PATCH 20/72] chore: fix formatting (#18044) * feat: bump Firebase JS SDK to 12.9.0 * chore: fix formatting * chore: revert JS SDK bump --- .../cloud_firestore/FirestoreMessages.g.m | 75 +++++---- .../firebase_auth/firebase_auth_messages.g.m | 142 +++++++++--------- .../Sources/firebase_core/messages.g.m | 26 ++-- .../FLTFirebaseMessagingPlugin.m | 8 +- 4 files changed, 129 insertions(+), 122 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m index 34e717b694a7..16dce2073a10 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m @@ -934,11 +934,12 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, binaryMessenger:binaryMessenger codec:FirebaseFirestoreHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(setIndexConfigurationApp: - indexConfiguration:completion:)], - @"FirebaseFirestoreHostApi api (%@) doesn't respond to " - @"@selector(setIndexConfigurationApp:indexConfiguration:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector( + setIndexConfigurationApp:indexConfiguration:completion:)], + @"FirebaseFirestoreHostApi api (%@) doesn't respond to " + @"@selector(setIndexConfigurationApp:indexConfiguration:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FirestorePigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1006,11 +1007,11 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, binaryMessenger:binaryMessenger codec:FirebaseFirestoreHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(transactionCreateApp: - timeout:maxAttempts:completion:)], - @"FirebaseFirestoreHostApi api (%@) doesn't respond to " - @"@selector(transactionCreateApp:timeout:maxAttempts:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector(transactionCreateApp:timeout:maxAttempts:completion:)], + @"FirebaseFirestoreHostApi api (%@) doesn't respond to " + @"@selector(transactionCreateApp:timeout:maxAttempts:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FirestorePigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1034,8 +1035,8 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, binaryMessenger:binaryMessenger codec:FirebaseFirestoreHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (transactionStoreResultTransactionId:resultType:commands:completion:)], + NSCAssert([api respondsToSelector:@selector(transactionStoreResultTransactionId:resultType: + commands:completion:)], @"FirebaseFirestoreHostApi api (%@) doesn't respond to " @"@selector(transactionStoreResultTransactionId:resultType:commands:completion:)", api); @@ -1062,11 +1063,11 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, binaryMessenger:binaryMessenger codec:FirebaseFirestoreHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(transactionGetApp: - transactionId:path:completion:)], - @"FirebaseFirestoreHostApi api (%@) doesn't respond to " - @"@selector(transactionGetApp:transactionId:path:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector(transactionGetApp:transactionId:path:completion:)], + @"FirebaseFirestoreHostApi api (%@) doesn't respond to " + @"@selector(transactionGetApp:transactionId:path:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FirestorePigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1192,8 +1193,8 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, binaryMessenger:binaryMessenger codec:FirebaseFirestoreHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (queryGetApp:path:isCollectionGroup:parameters:options:completion:)], + NSCAssert([api respondsToSelector:@selector(queryGetApp:path:isCollectionGroup:parameters: + options:completion:)], @"FirebaseFirestoreHostApi api (%@) doesn't respond to " @"@selector(queryGetApp:path:isCollectionGroup:parameters:options:completion:)", api); @@ -1225,9 +1226,8 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, binaryMessenger:binaryMessenger codec:FirebaseFirestoreHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (aggregateQueryApp: - path:parameters:source:queries:isCollectionGroup:completion:)], + NSCAssert([api respondsToSelector:@selector(aggregateQueryApp:path:parameters:source:queries: + isCollectionGroup:completion:)], @"FirebaseFirestoreHostApi api (%@) doesn't respond to " @"@selector(aggregateQueryApp:path:parameters:source:queries:isCollectionGroup:" @"completion:)", @@ -1287,14 +1287,13 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, binaryMessenger:binaryMessenger codec:FirebaseFirestoreHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (querySnapshotApp: - path:isCollectionGroup:parameters:options:includeMetadataChanges - :source:completion:)], - @"FirebaseFirestoreHostApi api (%@) doesn't respond to " - @"@selector(querySnapshotApp:path:isCollectionGroup:parameters:options:" - @"includeMetadataChanges:source:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector(querySnapshotApp:path:isCollectionGroup:parameters: + options:includeMetadataChanges:source:completion:)], + @"FirebaseFirestoreHostApi api (%@) doesn't respond to " + @"@selector(querySnapshotApp:path:isCollectionGroup:parameters:options:" + @"includeMetadataChanges:source:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FirestorePigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1326,9 +1325,8 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, binaryMessenger:binaryMessenger codec:FirebaseFirestoreHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (documentReferenceSnapshotApp: - parameters:includeMetadataChanges:source:completion:)], + NSCAssert([api respondsToSelector:@selector(documentReferenceSnapshotApp:parameters: + includeMetadataChanges:source:completion:)], @"FirebaseFirestoreHostApi api (%@) doesn't respond to " @"@selector(documentReferenceSnapshotApp:parameters:includeMetadataChanges:source:" @"completion:)", @@ -1359,11 +1357,12 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, binaryMessenger:binaryMessenger codec:FirebaseFirestoreHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (persistenceCacheIndexManagerRequestApp:request:completion:)], - @"FirebaseFirestoreHostApi api (%@) doesn't respond to " - @"@selector(persistenceCacheIndexManagerRequestApp:request:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector( + persistenceCacheIndexManagerRequestApp:request:completion:)], + @"FirebaseFirestoreHostApi api (%@) doesn't respond to " + @"@selector(persistenceCacheIndexManagerRequestApp:request:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FirestorePigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); diff --git a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/firebase_auth_messages.g.m b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/firebase_auth_messages.g.m index 8a32f1224ae6..365ff70d690c 100644 --- a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/firebase_auth_messages.g.m +++ b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/firebase_auth_messages.g.m @@ -1065,11 +1065,11 @@ void SetUpFirebaseAuthHostApiWithSuffix(id binaryMesseng binaryMessenger:binaryMessenger codec:FirebaseAuthHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(confirmPasswordResetApp: - code:newPassword:completion:)], - @"FirebaseAuthHostApi api (%@) doesn't respond to " - @"@selector(confirmPasswordResetApp:code:newPassword:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector(confirmPasswordResetApp:code:newPassword:completion:)], + @"FirebaseAuthHostApi api (%@) doesn't respond to " + @"@selector(confirmPasswordResetApp:code:newPassword:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1096,11 +1096,13 @@ void SetUpFirebaseAuthHostApiWithSuffix(id binaryMesseng binaryMessenger:binaryMessenger codec:FirebaseAuthHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (createUserWithEmailAndPasswordApp:email:password:completion:)], - @"FirebaseAuthHostApi api (%@) doesn't respond to " - @"@selector(createUserWithEmailAndPasswordApp:email:password:completion:)", - api); + NSCAssert( + [api + respondsToSelector:@selector( + createUserWithEmailAndPasswordApp:email:password:completion:)], + @"FirebaseAuthHostApi api (%@) doesn't respond to " + @"@selector(createUserWithEmailAndPasswordApp:email:password:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1213,11 +1215,12 @@ void SetUpFirebaseAuthHostApiWithSuffix(id binaryMesseng binaryMessenger:binaryMessenger codec:FirebaseAuthHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (signInWithEmailAndPasswordApp:email:password:completion:)], - @"FirebaseAuthHostApi api (%@) doesn't respond to " - @"@selector(signInWithEmailAndPasswordApp:email:password:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector( + signInWithEmailAndPasswordApp:email:password:completion:)], + @"FirebaseAuthHostApi api (%@) doesn't respond to " + @"@selector(signInWithEmailAndPasswordApp:email:password:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1245,11 +1248,11 @@ void SetUpFirebaseAuthHostApiWithSuffix(id binaryMesseng binaryMessenger:binaryMessenger codec:FirebaseAuthHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(signInWithEmailLinkApp: - email:emailLink:completion:)], - @"FirebaseAuthHostApi api (%@) doesn't respond to " - @"@selector(signInWithEmailLinkApp:email:emailLink:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector(signInWithEmailLinkApp:email:emailLink:completion:)], + @"FirebaseAuthHostApi api (%@) doesn't respond to " + @"@selector(signInWithEmailLinkApp:email:emailLink:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1277,11 +1280,11 @@ void SetUpFirebaseAuthHostApiWithSuffix(id binaryMesseng binaryMessenger:binaryMessenger codec:FirebaseAuthHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(signInWithProviderApp: - signInProvider:completion:)], - @"FirebaseAuthHostApi api (%@) doesn't respond to " - @"@selector(signInWithProviderApp:signInProvider:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector(signInWithProviderApp:signInProvider:completion:)], + @"FirebaseAuthHostApi api (%@) doesn't respond to " + @"@selector(signInWithProviderApp:signInProvider:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1361,8 +1364,8 @@ void SetUpFirebaseAuthHostApiWithSuffix(id binaryMesseng binaryMessenger:binaryMessenger codec:FirebaseAuthHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (sendPasswordResetEmailApp:email:actionCodeSettings:completion:)], + NSCAssert([api respondsToSelector: + @selector(sendPasswordResetEmailApp:email:actionCodeSettings:completion:)], @"FirebaseAuthHostApi api (%@) doesn't respond to " @"@selector(sendPasswordResetEmailApp:email:actionCodeSettings:completion:)", api); @@ -1392,8 +1395,8 @@ void SetUpFirebaseAuthHostApiWithSuffix(id binaryMesseng binaryMessenger:binaryMessenger codec:FirebaseAuthHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (sendSignInLinkToEmailApp:email:actionCodeSettings:completion:)], + NSCAssert([api respondsToSelector: + @selector(sendSignInLinkToEmailApp:email:actionCodeSettings:completion:)], @"FirebaseAuthHostApi api (%@) doesn't respond to " @"@selector(sendSignInLinkToEmailApp:email:actionCodeSettings:completion:)", api); @@ -1535,7 +1538,7 @@ void SetUpFirebaseAuthHostApiWithSuffix(id binaryMesseng codec:FirebaseAuthHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(revokeTokenWithAuthorizationCodeApp: - authorizationCode:completion:)], + authorizationCode:completion:)], @"FirebaseAuthHostApi api (%@) doesn't respond to " @"@selector(revokeTokenWithAuthorizationCodeApp:authorizationCode:completion:)", api); @@ -1844,11 +1847,11 @@ void SetUpFirebaseAuthUserHostApiWithSuffix(id binaryMes binaryMessenger:binaryMessenger codec:FirebaseAuthUserHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(reauthenticateWithCredentialApp: - input:completion:)], - @"FirebaseAuthUserHostApi api (%@) doesn't respond to " - @"@selector(reauthenticateWithCredentialApp:input:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector(reauthenticateWithCredentialApp:input:completion:)], + @"FirebaseAuthUserHostApi api (%@) doesn't respond to " + @"@selector(reauthenticateWithCredentialApp:input:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1874,11 +1877,12 @@ void SetUpFirebaseAuthUserHostApiWithSuffix(id binaryMes binaryMessenger:binaryMessenger codec:FirebaseAuthUserHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(reauthenticateWithProviderApp: - signInProvider:completion:)], - @"FirebaseAuthUserHostApi api (%@) doesn't respond to " - @"@selector(reauthenticateWithProviderApp:signInProvider:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector( + reauthenticateWithProviderApp:signInProvider:completion:)], + @"FirebaseAuthUserHostApi api (%@) doesn't respond to " + @"@selector(reauthenticateWithProviderApp:signInProvider:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1929,11 +1933,12 @@ void SetUpFirebaseAuthUserHostApiWithSuffix(id binaryMes binaryMessenger:binaryMessenger codec:FirebaseAuthUserHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(sendEmailVerificationApp: - actionCodeSettings:completion:)], - @"FirebaseAuthUserHostApi api (%@) doesn't respond to " - @"@selector(sendEmailVerificationApp:actionCodeSettings:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector( + sendEmailVerificationApp:actionCodeSettings:completion:)], + @"FirebaseAuthUserHostApi api (%@) doesn't respond to " + @"@selector(sendEmailVerificationApp:actionCodeSettings:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -2099,8 +2104,8 @@ void SetUpFirebaseAuthUserHostApiWithSuffix(id binaryMes binaryMessenger:binaryMessenger codec:FirebaseAuthUserHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (verifyBeforeUpdateEmailApp:newEmail:actionCodeSettings:completion:)], + NSCAssert([api respondsToSelector:@selector(verifyBeforeUpdateEmailApp:newEmail: + actionCodeSettings:completion:)], @"FirebaseAuthUserHostApi api (%@) doesn't respond to " @"@selector(verifyBeforeUpdateEmailApp:newEmail:actionCodeSettings:completion:)", api); @@ -2204,11 +2209,11 @@ void SetUpMultiFactorUserHostApiWithSuffix(id binaryMess binaryMessenger:binaryMessenger codec:MultiFactorUserHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(enrollPhoneApp: - assertion:displayName:completion:)], - @"MultiFactorUserHostApi api (%@) doesn't respond to " - @"@selector(enrollPhoneApp:assertion:displayName:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector(enrollPhoneApp:assertion:displayName:completion:)], + @"MultiFactorUserHostApi api (%@) doesn't respond to " + @"@selector(enrollPhoneApp:assertion:displayName:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -2234,11 +2239,11 @@ void SetUpMultiFactorUserHostApiWithSuffix(id binaryMess binaryMessenger:binaryMessenger codec:MultiFactorUserHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(enrollTotpApp: - assertionId:displayName:completion:)], - @"MultiFactorUserHostApi api (%@) doesn't respond to " - @"@selector(enrollTotpApp:assertionId:displayName:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector(enrollTotpApp:assertionId:displayName:completion:)], + @"MultiFactorUserHostApi api (%@) doesn't respond to " + @"@selector(enrollTotpApp:assertionId:displayName:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -2430,8 +2435,8 @@ void SetUpMultiFactoResolverHostApiWithSuffix(id binaryM binaryMessenger:binaryMessenger codec:MultiFactoResolverHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (resolveSignInResolverId:assertion:totpAssertionId:completion:)], + NSCAssert([api respondsToSelector: + @selector(resolveSignInResolverId:assertion:totpAssertionId:completion:)], @"MultiFactoResolverHostApi api (%@) doesn't respond to " @"@selector(resolveSignInResolverId:assertion:totpAssertionId:completion:)", api); @@ -2549,8 +2554,8 @@ void SetUpMultiFactorTotpHostApiWithSuffix(id binaryMess binaryMessenger:binaryMessenger codec:MultiFactorTotpHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(getAssertionForEnrollmentSecretKey: - oneTimePassword:completion:)], + NSCAssert([api respondsToSelector: + @selector(getAssertionForEnrollmentSecretKey:oneTimePassword:completion:)], @"MultiFactorTotpHostApi api (%@) doesn't respond to " @"@selector(getAssertionForEnrollmentSecretKey:oneTimePassword:completion:)", api); @@ -2579,8 +2584,8 @@ void SetUpMultiFactorTotpHostApiWithSuffix(id binaryMess binaryMessenger:binaryMessenger codec:MultiFactorTotpHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(getAssertionForSignInEnrollmentId: - oneTimePassword:completion:)], + NSCAssert([api respondsToSelector: + @selector(getAssertionForSignInEnrollmentId:oneTimePassword:completion:)], @"MultiFactorTotpHostApi api (%@) doesn't respond to " @"@selector(getAssertionForSignInEnrollmentId:oneTimePassword:completion:)", api); @@ -2627,11 +2632,12 @@ void SetUpMultiFactorTotpSecretHostApiWithSuffix(id bina binaryMessenger:binaryMessenger codec:MultiFactorTotpSecretHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(generateQrCodeUrlSecretKey: - accountName:issuer:completion:)], - @"MultiFactorTotpSecretHostApi api (%@) doesn't respond to " - @"@selector(generateQrCodeUrlSecretKey:accountName:issuer:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector( + generateQrCodeUrlSecretKey:accountName:issuer:completion:)], + @"MultiFactorTotpSecretHostApi api (%@) doesn't respond to " + @"@selector(generateQrCodeUrlSecretKey:accountName:issuer:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSString *arg_secretKey = GetNullableObjectAtIndex(args, 0); diff --git a/packages/firebase_core/firebase_core/ios/firebase_core/Sources/firebase_core/messages.g.m b/packages/firebase_core/firebase_core/ios/firebase_core/Sources/firebase_core/messages.g.m index ff9ac9933bc0..cf3e439b92ac 100644 --- a/packages/firebase_core/firebase_core/ios/firebase_core/Sources/firebase_core/messages.g.m +++ b/packages/firebase_core/firebase_core/ios/firebase_core/Sources/firebase_core/messages.g.m @@ -224,11 +224,11 @@ void SetUpFirebaseCoreHostApiWithSuffix(id binaryMesseng binaryMessenger:binaryMessenger codec:nullGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(initializeAppAppName: - initializeAppRequest:completion:)], - @"FirebaseCoreHostApi api (%@) doesn't respond to " - @"@selector(initializeAppAppName:initializeAppRequest:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector(initializeAppAppName:initializeAppRequest:completion:)], + @"FirebaseCoreHostApi api (%@) doesn't respond to " + @"@selector(initializeAppAppName:initializeAppRequest:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSString *arg_appName = GetNullableObjectAtIndex(args, 0); @@ -313,11 +313,13 @@ void SetUpFirebaseAppHostApiWithSuffix(id binaryMessenge binaryMessenger:binaryMessenger codec:nullGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (setAutomaticDataCollectionEnabledAppName:enabled:completion:)], - @"FirebaseAppHostApi api (%@) doesn't respond to " - @"@selector(setAutomaticDataCollectionEnabledAppName:enabled:completion:)", - api); + NSCAssert( + [api + respondsToSelector:@selector( + setAutomaticDataCollectionEnabledAppName:enabled:completion:)], + @"FirebaseAppHostApi api (%@) doesn't respond to " + @"@selector(setAutomaticDataCollectionEnabledAppName:enabled:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSString *arg_appName = GetNullableObjectAtIndex(args, 0); @@ -342,8 +344,8 @@ void SetUpFirebaseAppHostApiWithSuffix(id binaryMessenge binaryMessenger:binaryMessenger codec:nullGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (setAutomaticResourceManagementEnabledAppName:enabled:completion:)], + NSCAssert([api respondsToSelector:@selector(setAutomaticResourceManagementEnabledAppName: + enabled:completion:)], @"FirebaseAppHostApi api (%@) doesn't respond to " @"@selector(setAutomaticResourceManagementEnabledAppName:enabled:completion:)", api); diff --git a/packages/firebase_messaging/firebase_messaging/ios/firebase_messaging/Sources/firebase_messaging/FLTFirebaseMessagingPlugin.m b/packages/firebase_messaging/firebase_messaging/ios/firebase_messaging/Sources/firebase_messaging/FLTFirebaseMessagingPlugin.m index 3d8c9298a4e9..76879a85c526 100644 --- a/packages/firebase_messaging/firebase_messaging/ios/firebase_messaging/Sources/firebase_messaging/FLTFirebaseMessagingPlugin.m +++ b/packages/firebase_messaging/firebase_messaging/ios/firebase_messaging/Sources/firebase_messaging/FLTFirebaseMessagingPlugin.m @@ -299,12 +299,12 @@ - (void)setupNotificationHandlingWithRemoteNotification: respondsToSelector:@selector(userNotificationCenter:openSettingsForNotification:)]; _originalNotificationCenterDelegateRespondsTo.willPresentNotification = (unsigned int)[_originalNotificationCenterDelegate - respondsToSelector:@selector(userNotificationCenter: - willPresentNotification:withCompletionHandler:)]; + respondsToSelector:@selector(userNotificationCenter:willPresentNotification: + withCompletionHandler:)]; _originalNotificationCenterDelegateRespondsTo.didReceiveNotificationResponse = (unsigned int)[_originalNotificationCenterDelegate - respondsToSelector:@selector(userNotificationCenter: - didReceiveNotificationResponse:withCompletionHandler:)]; + respondsToSelector:@selector(userNotificationCenter:didReceiveNotificationResponse: + withCompletionHandler:)]; } } From f1bf4b8473237aa73ca5d767c0179702d0ed0307 Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Mon, 2 Mar 2026 14:40:57 +0100 Subject: [PATCH 21/72] test: fix flaky CI (#18046) * test: fix flaky CI * fixing * clean macos * clean * format * fixing CI * flaky fdc * fix fdc * update * fix * improve * up to date * fix * clean * cleaning --- .github/workflows/android.yaml | 2 +- .github/workflows/e2e_tests_fdc.yaml | 28 ++-- .github/workflows/ios.yaml | 13 +- .github/workflows/macos.yaml | 19 ++- .../integration_test/document_change_e2e.dart | 147 ++++++++--------- .../example/integration_test/query_e2e.dart | 73 ++++----- .../firebase_app_installations_e2e_test.dart | 29 +++- .../firebase_auth/firebase_auth_e2e_test.dart | 19 ++- .../firebase_database/data_snapshot_e2e.dart | 6 +- .../database_reference_e2e.dart | 24 +-- .../firebase_database/query_e2e.dart | 150 +++++++++--------- .../firebase_storage/reference_e2e.dart | 36 +++-- .../firebase_storage/task_e2e.dart | 27 ++-- 13 files changed, 315 insertions(+), 258 deletions(-) diff --git a/.github/workflows/android.yaml b/.github/workflows/android.yaml index 02dca1cc1de2..54bb34693732 100644 --- a/.github/workflows/android.yaml +++ b/.github/workflows/android.yaml @@ -110,7 +110,7 @@ jobs: emulator-build: 14214601 working-directory: ${{ matrix.working_directory }} script: | - flutter test integration_test/e2e_test.dart --ignore-timeouts --dart-define=CI=true -d emulator-5554 + flutter test integration_test/e2e_test.dart --timeout 10x --dart-define=CI=true -d emulator-5554 - name: Ensure Appium is shut down # Required because of below issue where emulator failing to shut down properly causes tests to fail # https://github.com/ReactiveCircus/android-emulator-runner/issues/385 diff --git a/.github/workflows/e2e_tests_fdc.yaml b/.github/workflows/e2e_tests_fdc.yaml index 2d29c3512e3a..66bd1c53f8f7 100644 --- a/.github/workflows/e2e_tests_fdc.yaml +++ b/.github/workflows/e2e_tests_fdc.yaml @@ -102,7 +102,7 @@ jobs: ~/.android/adb* key: avd-${{ runner.os }}-${{ env.AVD_API_LEVEL }}-${{ env.AVD_TARGET }}-${{ env.AVD_ARCH }} - name: Start AVD then run E2E tests - uses: reactivecircus/android-emulator-runner@v2 + uses: reactivecircus/android-emulator-runner@b530d96654c385303d652368551fb075bc2f0b6b with: api-level: ${{ env.AVD_API_LEVEL }} target: ${{ env.AVD_TARGET }} @@ -110,7 +110,7 @@ jobs: emulator-build: 14214601 working-directory: 'packages/firebase_data_connect/firebase_data_connect/example' script: | - flutter test integration_test/e2e_test.dart --dart-define=CI=true -d emulator-5554 + flutter test integration_test/e2e_test.dart --dart-define=CI=true --timeout 10x -d emulator-5554 - name: Save Android Emulator Cache # Branches can read main cache but main cannot read branch cache. Avoid LRU eviction with main-only cache. if: github.ref == 'refs/heads/main' @@ -213,19 +213,27 @@ jobs: unset PGSERVICEFILE firebase experiments:enable dataconnect ./start-firebase-emulator.sh + - uses: futureware-tech/simulator-action@e89aa8f93d3aec35083ff49d2854d07f7186f7f5 + id: simulator + with: + model: "iPhone 16" - name: 'E2E Tests' working-directory: 'packages/firebase_data_connect/firebase_data_connect/example' + env: + SIMULATOR: ${{ steps.simulator.outputs.udid }} run: | - # Boot simulator and wait for System app to be ready. - # List of available simulators: https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md#installed-simulators - SIMULATOR="iPhone 16" - xcrun simctl bootstatus "$SIMULATOR" -b - xcrun simctl logverbose "$SIMULATOR" enable - # Sleep to allow simulator to settle. - sleep 15 # Uncomment following line to have simulator logs printed out for debugging purposes. # xcrun simctl spawn booted log stream --predicate 'eventMessage contains "flutter"' & - flutter test integration_test/e2e_test.dart -d "$SIMULATOR" --dart-define=CI=true + # The iOS simulator sometimes fails to connect the VM Service, hanging for + # 12 minutes before timing out. Use a 6-minute limit and retry once with + # a simulator reboot. Normal connection takes 30s-5min. + perl -e 'alarm 360; exec @ARGV' -- flutter test integration_test/e2e_test.dart -d "$SIMULATOR" --dart-define=CI=true --timeout 10x || { + echo "First attempt failed or timed out. Rebooting simulator and retrying..." + xcrun simctl shutdown "$SIMULATOR" || true + xcrun simctl boot "$SIMULATOR" + xcrun simctl bootstatus "$SIMULATOR" -b + flutter test integration_test/e2e_test.dart -d "$SIMULATOR" --dart-define=CI=true --timeout 10x + } - name: Save Firestore Emulator Cache # Branches can read main cache but main cannot read branch cache. Avoid LRU eviction with main-only cache. if: github.ref == 'refs/heads/main' diff --git a/.github/workflows/ios.yaml b/.github/workflows/ios.yaml index ee58e942eaf8..c58afe204a5e 100644 --- a/.github/workflows/ios.yaml +++ b/.github/workflows/ios.yaml @@ -114,7 +114,7 @@ jobs: ccache -s - name: Start Firebase Emulator run: sudo chown -R 501:20 "/Users/runner/.npm" && cd ./.github/workflows/scripts && ./start-firebase-emulator.sh - - uses: futureware-tech/simulator-action@9150831bad21ed25e472017a746f49ccbd0e674a + - uses: futureware-tech/simulator-action@e89aa8f93d3aec35083ff49d2854d07f7186f7f5 id: simulator with: # List of available simulators: https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md#installed-simulators @@ -126,7 +126,16 @@ jobs: run: | # Uncomment following line to have simulator logs printed out for debugging purposes. # xcrun simctl spawn booted log stream --predicate 'eventMessage contains "flutter"' & - flutter test integration_test/e2e_test.dart -d "$SIMULATOR" --ignore-timeouts --dart-define=CI=true + # The iOS simulator sometimes fails to connect the VM Service, hanging for + # 12 minutes before timing out. Use a 6-minute limit and retry once with + # a simulator reboot. Normal connection takes 30s-5min. + perl -e 'alarm 360; exec @ARGV' -- flutter test integration_test/e2e_test.dart -d "$SIMULATOR" --timeout 10x --dart-define=CI=true || { + echo "First attempt failed or timed out. Rebooting simulator and retrying..." + xcrun simctl shutdown "$SIMULATOR" || true + xcrun simctl boot "$SIMULATOR" + xcrun simctl bootstatus "$SIMULATOR" -b + flutter test integration_test/e2e_test.dart -d "$SIMULATOR" --timeout 10x --dart-define=CI=true + } - name: Save Firestore Emulator Cache # Branches can read main cache but main cannot read branch cache. Avoid LRU eviction with main-only cache. if: github.ref == 'refs/heads/main' diff --git a/.github/workflows/macos.yaml b/.github/workflows/macos.yaml index cdcb9f44c027..0012b593f2db 100644 --- a/.github/workflows/macos.yaml +++ b/.github/workflows/macos.yaml @@ -102,11 +102,26 @@ jobs: - name: 'E2E Tests' working-directory: ${{ matrix.working_directory }} run: | - flutter test \ + # flutter test on macOS CI may exit 1 due to "Failed to foreground app" + # even when all tests pass. Check actual test results to determine success. + set +e + OUTPUT=$(flutter test \ integration_test/e2e_test.dart \ -d macos \ --dart-define=CI=true \ - --ignore-timeouts + --timeout 10x 2>&1) + EXIT_CODE=$? + echo "$OUTPUT" + if [ $EXIT_CODE -ne 0 ]; then + if echo "$OUTPUT" | grep -q "Some tests failed" || echo "$OUTPUT" | grep -q "test failed"; then + exit 1 + fi + if echo "$OUTPUT" | grep -q "tests passed"; then + echo "All tests passed but flutter test exited with $EXIT_CODE (likely 'Failed to foreground app'). Treating as success." + exit 0 + fi + exit $EXIT_CODE + fi - name: Save Firestore Emulator Cache continue-on-error: true # Branches can read main cache but main cannot read branch cache. Avoid LRU eviction with main-only cache. diff --git a/packages/cloud_firestore/cloud_firestore/example/integration_test/document_change_e2e.dart b/packages/cloud_firestore/cloud_firestore/example/integration_test/document_change_e2e.dart index bc4eb570638d..74e5f9f37ab5 100644 --- a/packages/cloud_firestore/cloud_firestore/example/integration_test/document_change_e2e.dart +++ b/packages/cloud_firestore/cloud_firestore/example/integration_test/document_change_e2e.dart @@ -94,49 +94,43 @@ void runDocumentChangeTests() { // Set something in the database await doc1.set({'name': 'doc1'}); - Stream>> stream = - collection.snapshots(); - int call = 0; - - StreamSubscription subscription = stream.listen( - expectAsync1( - (QuerySnapshot> snapshot) { - call++; - if (call == 1) { - expect(snapshot.docs.length, equals(1)); - expect(snapshot.docChanges.length, equals(1)); - expect(snapshot.docChanges[0], isA()); - DocumentChange> change = - snapshot.docChanges[0]; - expect(change.newIndex, equals(0)); - expect(change.oldIndex, equals(-1)); - expect(change.type, equals(DocumentChangeType.added)); - expect(change.doc.data()!['name'], equals('doc1')); - } else if (call == 2) { - expect(snapshot.docs.length, equals(0)); - expect(snapshot.docChanges.length, equals(1)); - expect(snapshot.docChanges[0], isA()); - DocumentChange> change = - snapshot.docChanges[0]; - expect(change.newIndex, equals(-1)); - expect(change.oldIndex, equals(0)); - expect(change.type, equals(DocumentChangeType.removed)); - expect(change.doc.data()!['name'], equals('doc1')); - } else { - fail('Should not have been called'); - } - }, - count: 2, - reason: 'Stream should only have been called twice.', - ), - ); + final snapshots = >>[]; + final receivedAll = Completer(); + + StreamSubscription subscription = + collection.snapshots().listen((snapshot) { + snapshots.add(snapshot); + if (snapshots.length >= 2 && !receivedAll.isCompleted) { + receivedAll.complete(); + } + }); - await Future.delayed( - const Duration(seconds: 1), - ); // Ensure listener fires + // Wait for the initial snapshot before modifying + await Future.delayed(const Duration(milliseconds: 500)); await doc1.delete(); + await receivedAll.future.timeout(const Duration(seconds: 30)); await subscription.cancel(); + + // Verify first snapshot (added) + expect(snapshots[0].docs.length, equals(1)); + expect(snapshots[0].docChanges.length, equals(1)); + DocumentChange> addChange = + snapshots[0].docChanges[0]; + expect(addChange.newIndex, equals(0)); + expect(addChange.oldIndex, equals(-1)); + expect(addChange.type, equals(DocumentChangeType.added)); + expect(addChange.doc.data()!['name'], equals('doc1')); + + // Verify second snapshot (removed) + expect(snapshots[1].docs.length, equals(0)); + expect(snapshots[1].docChanges.length, equals(1)); + DocumentChange> removeChange = + snapshots[1].docChanges[0]; + expect(removeChange.newIndex, equals(-1)); + expect(removeChange.oldIndex, equals(0)); + expect(removeChange.type, equals(DocumentChangeType.removed)); + expect(removeChange.doc.data()!['name'], equals('doc1')); }, skip: defaultTargetPlatform == TargetPlatform.windows || defaultTargetPlatform == TargetPlatform.android, @@ -154,48 +148,47 @@ void runDocumentChangeTests() { await doc1.set({'value': 1}); await doc2.set({'value': 2}); await doc3.set({'value': 3}); - Stream>> stream = - collection.orderBy('value').snapshots(); - - int call = 0; - StreamSubscription subscription = stream.listen( - expectAsync1( - (QuerySnapshot> snapshot) { - call++; - if (call == 1) { - expect(snapshot.docs.length, equals(3)); - expect(snapshot.docChanges.length, equals(3)); - snapshot.docChanges.asMap().forEach( - (int index, DocumentChange> change) { - expect(change.oldIndex, equals(-1)); - expect(change.newIndex, equals(index)); - expect(change.type, equals(DocumentChangeType.added)); - expect(change.doc.data()!['value'], equals(index + 1)); - }); - } else if (call == 2) { - expect(snapshot.docs.length, equals(3)); - expect(snapshot.docChanges.length, equals(1)); - DocumentChange> change = - snapshot.docChanges[0]; - expect(change.oldIndex, equals(0)); - expect(change.newIndex, equals(2)); - expect(change.type, equals(DocumentChangeType.modified)); - expect(change.doc.id, equals('doc1')); - } else { - fail('Should not have been called'); - } - }, - count: 2, - reason: 'Stream should only have been called twice.', - ), - ); - await Future.delayed( - const Duration(seconds: 1), - ); // Ensure listener fires + final snapshots = >>[]; + final receivedAll = Completer(); + + StreamSubscription subscription = + collection.orderBy('value').snapshots().listen((snapshot) { + snapshots.add(snapshot); + if (snapshots.length >= 2 && !receivedAll.isCompleted) { + receivedAll.complete(); + } + }); + + // Wait for the initial snapshot before modifying + await Future.delayed(const Duration(milliseconds: 500)); await doc1.update({'value': 4}); + await receivedAll.future.timeout(const Duration(seconds: 30)); await subscription.cancel(); + + // Verify first snapshot (all 3 docs added) + expect(snapshots[0].docs.length, equals(3)); + expect(snapshots[0].docChanges.length, equals(3)); + snapshots[0] + .docChanges + .asMap() + .forEach((int index, DocumentChange> change) { + expect(change.oldIndex, equals(-1)); + expect(change.newIndex, equals(index)); + expect(change.type, equals(DocumentChangeType.added)); + expect(change.doc.data()!['value'], equals(index + 1)); + }); + + // Verify second snapshot (doc1 modified, moved to end) + expect(snapshots[1].docs.length, equals(3)); + expect(snapshots[1].docChanges.length, equals(1)); + DocumentChange> change = + snapshots[1].docChanges[0]; + expect(change.oldIndex, equals(0)); + expect(change.newIndex, equals(2)); + expect(change.type, equals(DocumentChangeType.modified)); + expect(change.doc.id, equals('doc1')); }, skip: defaultTargetPlatform == TargetPlatform.windows, ); diff --git a/packages/cloud_firestore/cloud_firestore/example/integration_test/query_e2e.dart b/packages/cloud_firestore/cloud_firestore/example/integration_test/query_e2e.dart index d3e663b01ab2..49b7bb0c932a 100644 --- a/packages/cloud_firestore/cloud_firestore/example/integration_test/query_e2e.dart +++ b/packages/cloud_firestore/cloud_firestore/example/integration_test/query_e2e.dart @@ -328,53 +328,48 @@ void runQueryTests() { Stream>> stream = collection.snapshots(); - int call = 0; + final snapshots = >>[]; + final receivedAll = Completer(); - StreamSubscription subscription = stream.listen( - expectAsync1( - (QuerySnapshot> snapshot) { - call++; - if (call == 1) { - expect(snapshot.docs.length, equals(1)); - QueryDocumentSnapshot> documentSnapshot = - snapshot.docs[0]; - expect(documentSnapshot.data()['foo'], equals('bar')); - } else if (call == 2) { - expect(snapshot.docs.length, equals(2)); - QueryDocumentSnapshot> documentSnapshot = - snapshot.docs.firstWhere((doc) => doc.id == 'doc1'); - expect(documentSnapshot.data()['bar'], equals('baz')); - } else if (call == 3) { - expect(snapshot.docs.length, equals(1)); - expect( - snapshot.docs.where((doc) => doc.id == 'doc1').isEmpty, - isTrue, - ); - } else if (call == 4) { - expect(snapshot.docs.length, equals(2)); - QueryDocumentSnapshot> documentSnapshot = - snapshot.docs.firstWhere((doc) => doc.id == 'doc2'); - expect(documentSnapshot.data()['foo'], equals('bar')); - } else if (call == 5) { - expect(snapshot.docs.length, equals(2)); - QueryDocumentSnapshot> documentSnapshot = - snapshot.docs.firstWhere((doc) => doc.id == 'doc2'); - expect(documentSnapshot.data()['foo'], equals('baz')); - } else { - fail('Should not have been called'); - } - }, - count: 5, - reason: 'Stream should only have been called five times.', - ), - ); + StreamSubscription subscription = stream.listen((snapshot) { + snapshots.add(snapshot); + if (snapshots.length >= 5 && !receivedAll.isCompleted) { + receivedAll.complete(); + } + }); + // Wait for initial snapshot before making changes await Future.delayed(const Duration(milliseconds: 500)); await collection.doc('doc1').set({'bar': 'baz'}); await collection.doc('doc1').delete(); await collection.doc('doc2').set({'foo': 'bar'}); await collection.doc('doc2').update({'foo': 'baz'}); + // Wait for all 5 snapshots with a timeout instead of hanging forever + await receivedAll.future.timeout(const Duration(seconds: 30)); + + expect(snapshots[0].docs.length, equals(1)); + expect(snapshots[0].docs[0].data()['foo'], equals('bar')); + + expect(snapshots[1].docs.length, equals(2)); + final doc1 = snapshots[1].docs.firstWhere((doc) => doc.id == 'doc1'); + expect(doc1.data()['bar'], equals('baz')); + + expect(snapshots[2].docs.length, equals(1)); + expect( + snapshots[2].docs.where((doc) => doc.id == 'doc1').isEmpty, + isTrue, + ); + + expect(snapshots[3].docs.length, equals(2)); + final doc2set = snapshots[3].docs.firstWhere((doc) => doc.id == 'doc2'); + expect(doc2set.data()['foo'], equals('bar')); + + expect(snapshots[4].docs.length, equals(2)); + final doc2update = + snapshots[4].docs.firstWhere((doc) => doc.id == 'doc2'); + expect(doc2update.data()['foo'], equals('baz')); + await subscription.cancel(); }); diff --git a/tests/integration_test/firebase_app_installations/firebase_app_installations_e2e_test.dart b/tests/integration_test/firebase_app_installations/firebase_app_installations_e2e_test.dart index 994905e9fb91..50562a38aa53 100644 --- a/tests/integration_test/firebase_app_installations/firebase_app_installations_e2e_test.dart +++ b/tests/integration_test/firebase_app_installations/firebase_app_installations_e2e_test.dart @@ -52,15 +52,28 @@ void main() { () async { final id = await FirebaseInstallations.instance.getId(); - // Wait a little so we don't get a delete-pending exception - await Future.delayed(const Duration(seconds: 8)); + // Retry delete in case of delete-pending state + for (var attempt = 0; attempt < 5; attempt++) { + try { + await FirebaseInstallations.instance.delete(); + break; + } catch (e) { + if (attempt == 4) rethrow; + await Future.delayed(const Duration(seconds: 2)); + } + } - await FirebaseInstallations.instance.delete(); - - // Wait a little so we don't get a delete-pending exception - await Future.delayed(const Duration(seconds: 8)); - - final newId = await FirebaseInstallations.instance.getId(); + // Retry getId in case of delete-pending state + String? newId; + for (var attempt = 0; attempt < 5; attempt++) { + try { + newId = await FirebaseInstallations.instance.getId(); + break; + } catch (e) { + if (attempt == 4) rethrow; + await Future.delayed(const Duration(seconds: 2)); + } + } expect(newId, isNot(equals(id))); // macOS skipped because it needs keychain sharing entitlement. See: https://github.com/firebase/flutterfire/issues/9538 }, diff --git a/tests/integration_test/firebase_auth/firebase_auth_e2e_test.dart b/tests/integration_test/firebase_auth/firebase_auth_e2e_test.dart index f60a7a5b9eb3..7693e8ce69d4 100644 --- a/tests/integration_test/firebase_auth/firebase_auth_e2e_test.dart +++ b/tests/integration_test/firebase_auth/firebase_auth_e2e_test.dart @@ -34,29 +34,32 @@ void main() { setUp(() async { // Reset users on emulator. await emulatorClearAllUsers(); + await ensureSignedOut(); try { - // Create a generic testing user account. Wrapped around try/catch because web still seems to have knowledge of user. await FirebaseAuth.instance.createUserWithEmailAndPassword( email: testEmail, password: testPassword, ); - } catch (e) { - // ignore: avoid_print - print('Already existing user: $e'); + } on FirebaseAuthException catch (e) { + // 'email-already-in-use': web may retain user state after emulator clear + // 'keychain-error': known macOS issue needing keychain sharing entitlement + if (e.code != 'email-already-in-use' && e.code != 'keychain-error') { + rethrow; + } } try { - // Create a disabled user account. Wrapped around try/catch because web still seems to have knowledge of user. final disabledUserCredential = await FirebaseAuth.instance.createUserWithEmailAndPassword( email: testDisabledEmail, password: testPassword, ); await emulatorDisableUser(disabledUserCredential.user!.uid); - } catch (e) { - // ignore: avoid_print - print('Already existing disabled user: $e'); + } on FirebaseAuthException catch (e) { + if (e.code != 'email-already-in-use' && e.code != 'keychain-error') { + rethrow; + } } await ensureSignedOut(); }); diff --git a/tests/integration_test/firebase_database/data_snapshot_e2e.dart b/tests/integration_test/firebase_database/data_snapshot_e2e.dart index b5bd61879705..29ff36f4cd7d 100644 --- a/tests/integration_test/firebase_database/data_snapshot_e2e.dart +++ b/tests/integration_test/firebase_database/data_snapshot_e2e.dart @@ -164,9 +164,11 @@ void setupDataSnapshotTests() { 'b': 2, 'c': 1, }); - final s = await ref.orderByValue().get(); + // Use .once() instead of .get() because the REST API used by .get() + // does not guarantee ordered results from the emulator. + final event = await ref.orderByValue().once(); - List children = s.children.toList(); + List children = event.snapshot.children.toList(); expect(children[0].value, 1); expect(children[1].value, 2); expect(children[2].value, 3); diff --git a/tests/integration_test/firebase_database/database_reference_e2e.dart b/tests/integration_test/firebase_database/database_reference_e2e.dart index c778c2bf66b3..78fc9616ffe9 100644 --- a/tests/integration_test/firebase_database/database_reference_e2e.dart +++ b/tests/integration_test/firebase_database/database_reference_e2e.dart @@ -13,15 +13,10 @@ import 'firebase_database_e2e_test.dart'; void setupDatabaseReferenceTests() { group('DatabaseReference', () { - late DatabaseReference ref; - - setUp(() async { - ref = database.ref('tests'); - await ref.remove(); - }); - group('set()', () { test('sets value', () async { + final ref = database.ref('tests/set-value'); + await ref.remove(); final v = Random.secure().nextInt(1024); await ref.set(v); final actual = await ref.get(); @@ -55,6 +50,8 @@ void setupDatabaseReferenceTests() { ); test('removes a value if set to null', () async { + final ref = database.ref('tests/set-null'); + await ref.remove(); final v = Random.secure().nextInt(1024); await ref.set(v); final before = await ref.get(); @@ -69,6 +66,8 @@ void setupDatabaseReferenceTests() { group('setPriority()', () { test('sets a priority', () async { + final ref = database.ref('tests/set-priority'); + await ref.remove(); await ref.set('foo'); await ref.setPriority(2); final snapshot = await ref.get(); @@ -78,6 +77,8 @@ void setupDatabaseReferenceTests() { group('setWithPriority()', () { test('sets a non-null value with a non-null priority', () async { + final ref = database.ref('tests/set-with-priority'); + await ref.remove(); await Future.wait([ ref.child('first').setWithPriority(1, 10), ref.child('second').setWithPriority(2, 1), @@ -92,6 +93,8 @@ void setupDatabaseReferenceTests() { group('update()', () { test('updates value at given location', () async { + final ref = database.ref('tests/update'); + await ref.remove(); await ref.set({'foo': 'bar'}); final newValue = Random.secure().nextInt(255) + 1; await ref.update({'bar': newValue}); @@ -105,11 +108,8 @@ void setupDatabaseReferenceTests() { }); group('runTransaction()', () { - setUp(() async { - await ref.set(0); - }); - test('aborts a transaction', () async { + final ref = database.ref('tests/transaction-abort'); await ref.set(5); final snapshot = await ref.get(); expect(snapshot.value, 5); @@ -127,6 +127,8 @@ void setupDatabaseReferenceTests() { }); test('executes transaction', () async { + final ref = database.ref('tests/transaction-exec'); + await ref.set(0); final snapshot = await ref.get(); final value = (snapshot.value ?? 0) as int; final result = await ref.runTransaction((value) { diff --git a/tests/integration_test/firebase_database/query_e2e.dart b/tests/integration_test/firebase_database/query_e2e.dart index 4f4d5f713777..1196c13b80e9 100644 --- a/tests/integration_test/firebase_database/query_e2e.dart +++ b/tests/integration_test/firebase_database/query_e2e.dart @@ -441,22 +441,21 @@ void setupQueryTests() { test( 'emits an event when a child is added', () async { - expect( - ref.onChildAdded, - emitsInOrder([ - isA() - .having((s) => s.snapshot.value, 'value', 'foo') - .having((e) => e.type, 'type', DatabaseEventType.childAdded), - isA() - .having((s) => s.snapshot.value, 'value', 'bar') - .having((e) => e.type, 'type', DatabaseEventType.childAdded), - ]), - ); - - await ref.child('foo').set('foo'); - await ref.child('bar').set('bar'); + // Set data first, then subscribe. onChildAdded fires for + // existing children on initial listen, avoiding race conditions + // with native listener registration. + // Use keys that sort alphabetically in the expected order, + // since onChildAdded returns children in key order. + await ref.child('a_first').set('foo'); + await ref.child('b_second').set('bar'); + + final events = await ref.onChildAdded.take(2).toList(); + + expect(events[0].snapshot.value, 'foo'); + expect(events[0].type, DatabaseEventType.childAdded); + expect(events[1].snapshot.value, 'bar'); + expect(events[1].type, DatabaseEventType.childAdded); }, - retry: 2, ); }); @@ -467,24 +466,26 @@ void setupQueryTests() { await ref.child('foo').set('foo'); await ref.child('bar').set('bar'); - expect( - ref.onChildRemoved, - emitsInOrder([ - isA() - .having((s) => s.snapshot.value, 'value', 'bar') - .having( - (e) => e.type, - 'type', - DatabaseEventType.childRemoved, - ), - ]), - ); - // Give time for listen to be registered on native. - // TODO is there a better way to do this? - await Future.delayed(const Duration(seconds: 1)); + final completer = Completer(); + final subscription = ref.onChildRemoved.listen((event) { + // Skip probe events used for listener registration + if (event.snapshot.key == '__probe__') return; + if (!completer.isCompleted) completer.complete(event); + }); + + // Wait for native listener registration by doing a round-trip + await ref.child('__probe__').set(true); + await ref.child('__probe__').remove(); + await ref.child('bar').remove(); + + final event = + await completer.future.timeout(const Duration(seconds: 10)); + expect(event.snapshot.value, 'bar'); + expect(event.type, DatabaseEventType.childRemoved); + + await subscription.cancel(); }, - retry: 2, ); }); @@ -503,34 +504,33 @@ void setupQueryTests() { await childRef.child('foo').set('foo'); await childRef.child('bar').set('bar'); - expect( - childRef.onChildChanged, - emitsInOrder([ - isA() - .having((s) => s.snapshot.key, 'key', 'bar') - .having((s) => s.snapshot.value, 'value', 'baz') - .having( - (e) => e.type, - 'type', - DatabaseEventType.childChanged, - ), - isA() - .having((s) => s.snapshot.key, 'key', 'foo') - .having((s) => s.snapshot.value, 'value', 'bar') - .having( - (e) => e.type, - 'type', - DatabaseEventType.childChanged, - ), - ]), - ); - // Give time for listen to be registered on native. - // TODO is there a better way to do this? - await Future.delayed(const Duration(seconds: 1)); + final events = []; + final receivedTwo = Completer(); + final subscription = childRef.onChildChanged.listen((event) { + events.add(event); + if (events.length >= 2 && !receivedTwo.isCompleted) { + receivedTwo.complete(); + } + }); + + // Wait for native listener registration by doing a round-trip + await childRef.child('__probe__').set(true); + await childRef.child('__probe__').remove(); + await childRef.child('bar').set('baz'); await childRef.child('foo').set('bar'); + + await receivedTwo.future.timeout(const Duration(seconds: 10)); + + expect(events[0].snapshot.key, 'bar'); + expect(events[0].snapshot.value, 'baz'); + expect(events[0].type, DatabaseEventType.childChanged); + expect(events[1].snapshot.key, 'foo'); + expect(events[1].snapshot.value, 'bar'); + expect(events[1].type, DatabaseEventType.childChanged); + + await subscription.cancel(); }, - retry: 2, ); }); @@ -546,24 +546,32 @@ void setupQueryTests() { 'greg': {'nuggets': 52}, }); - expect( - ref.orderByChild('nuggets').onChildMoved, - emitsInOrder([ - isA().having((s) => s.snapshot.value, 'value', { - 'nuggets': 57, - }).having((e) => e.type, 'type', DatabaseEventType.childMoved), - isA().having((s) => s.snapshot.value, 'value', { - 'nuggets': 61, - }).having((e) => e.type, 'type', DatabaseEventType.childMoved), - ]), - ); - // Give time for listen to be registered on native. - // TODO is there a better way to do this? - await Future.delayed(const Duration(seconds: 1)); + final events = []; + final receivedTwo = Completer(); + final subscription = + ref.orderByChild('nuggets').onChildMoved.listen((event) { + events.add(event); + if (events.length >= 2 && !receivedTwo.isCompleted) { + receivedTwo.complete(); + } + }); + + // Wait for native listener registration by doing a round-trip + await ref.child('__probe__').set(true); + await ref.child('__probe__').remove(); + await ref.child('greg/nuggets').set(57); await ref.child('rob/nuggets').set(61); + + await receivedTwo.future.timeout(const Duration(seconds: 10)); + + expect(events[0].snapshot.value, {'nuggets': 57}); + expect(events[0].type, DatabaseEventType.childMoved); + expect(events[1].snapshot.value, {'nuggets': 61}); + expect(events[1].type, DatabaseEventType.childMoved); + + await subscription.cancel(); }, - retry: 2, ); }); diff --git a/tests/integration_test/firebase_storage/reference_e2e.dart b/tests/integration_test/firebase_storage/reference_e2e.dart index ee09eac47ae8..7f81f834ded8 100644 --- a/tests/integration_test/firebase_storage/reference_e2e.dart +++ b/tests/integration_test/firebase_storage/reference_e2e.dart @@ -79,6 +79,8 @@ void setupReferenceTests() { test('should delete a file', () async { Reference ref = storage.ref('flutter-tests/deleteMe.jpeg'); await ref.putString('To Be Deleted :)'); + // Verify the file exists before attempting delete + await ref.getMetadata(); await ref.delete(); await expectLater( @@ -250,7 +252,7 @@ void setupReferenceTests() { Uint8List data = Uint8List.fromList(list); final Reference ref = - storage.ref('flutter-tests').child('flt-ok.txt'); + storage.ref('flutter-tests').child('flt-put-data.txt'); final TaskSnapshot complete = await ref.putData( data, @@ -312,9 +314,9 @@ void setupReferenceTests() { test( 'throws [UnimplementedError] for native platforms', () async { - final File file = await createFile('flt-ok.txt'); + final File file = await createFile('flt-put-blob.txt'); final Reference ref = - storage.ref('flutter-tests').child('flt-ok.txt'); + storage.ref('flutter-tests').child('flt-put-blob.txt'); await expectLater( () => ref.putBlob( @@ -345,12 +347,12 @@ void setupReferenceTests() { 'uploads a file', () async { final File file = await createFile( - 'flt-ok.txt', + 'flt-put-file.txt', string: kTestString, ); final Reference ref = - storage.ref('flutter-tests').child('flt-ok.txt'); + storage.ref('flutter-tests').child('flt-put-file.txt'); final TaskSnapshot complete = await ref.putFile( file, @@ -429,7 +431,8 @@ void setupReferenceTests() { test('uploads a string and downloads to check its content', () async { const text = 'put string some text to compare with uploaded and downloaded'; - final Reference ref = storage.ref('flutter-tests').child('flt-ok.txt'); + final Reference ref = + storage.ref('flutter-tests').child('flt-put-string.txt'); final TaskSnapshot complete = await ref.putString(text); expect(complete.totalBytes, greaterThan(0)); expect(complete.state, TaskState.success); @@ -462,12 +465,21 @@ void setupReferenceTests() { }); group('updateMetadata', () { - test('updates metadata', () async { - Reference ref = storage.ref('flutter-tests').child('flt-ok.txt'); - FullMetadata fullMetadata = await ref - .updateMetadata(SettableMetadata(customMetadata: {'foo': 'bar'})); - expect(fullMetadata.customMetadata!['foo'], 'bar'); - }); + test( + 'updates metadata', + () async { + Reference ref = + storage.ref('flutter-tests').child('flt-update-metadata.txt'); + // Ensure the file exists before updating metadata + await ref.putString('metadata test content'); + // Verify the file is visible before updating metadata + await ref.getMetadata(); + FullMetadata fullMetadata = await ref + .updateMetadata(SettableMetadata(customMetadata: {'foo': 'bar'})); + expect(fullMetadata.customMetadata!['foo'], 'bar'); + }, + timeout: const Timeout(Duration(minutes: 2)), + ); test( 'errors if property does not exist', diff --git a/tests/integration_test/firebase_storage/task_e2e.dart b/tests/integration_test/firebase_storage/task_e2e.dart index d75d3dc566ff..fd00288c3398 100644 --- a/tests/integration_test/firebase_storage/task_e2e.dart +++ b/tests/integration_test/firebase_storage/task_e2e.dart @@ -49,8 +49,9 @@ void setupTaskTests() { // TODO(Salakar): Known issue with iOS SDK where pausing immediately will cause an 'unknown' error. if (defaultTargetPlatform == TargetPlatform.iOS) { + // Wait for the first snapshot event to confirm the task is running + // before attempting to pause. await task!.snapshotEvents.first; - await Future.delayed(const Duration(milliseconds: 750)); } // TODO(Salakar): Known issue with iOS where pausing/resuming doesn't immediately return as paused/resumed 'true'. @@ -59,8 +60,6 @@ void setupTaskTests() { expect(paused, isTrue); expect(task!.snapshot.state, TaskState.paused); - await Future.delayed(const Duration(milliseconds: 500)); - bool? resumed = await task!.resume(); expect(resumed, isTrue); expect(task!.snapshot.state, TaskState.running); @@ -106,14 +105,13 @@ void setupTaskTests() { } await _testPauseTask('Download'); }, - retry: 3, + retry: 2, // TODO(russellwheatley): Windows works on example app, but fails on tests. // Clue is in bytesTransferred + totalBytes which both equal: -3617008641903833651 - skip: !kIsWeb && ( - defaultTargetPlatform == TargetPlatform.windows || - defaultTargetPlatform == TargetPlatform.android || - defaultTargetPlatform == TargetPlatform.macOS - ), + skip: !kIsWeb && + (defaultTargetPlatform == TargetPlatform.windows || + defaultTargetPlatform == TargetPlatform.android || + defaultTargetPlatform == TargetPlatform.macOS), ); // TODO(Salakar): Test is flaky on CI - needs investigating ('[firebase_storage/unknown] An unknown error occurred, please check the server response.') @@ -123,15 +121,14 @@ void setupTaskTests() { task = uploadRef.putString('This is an upload task!'); await _testPauseTask('Upload'); }, - retry: 3, + retry: 2, // This task is flaky on mac, skip for now. // TODO(russellwheatley): Windows works on example app, but fails on tests. // Clue is in bytesTransferred + totalBytes which both equal: -3617008641903833651 - skip: !kIsWeb && ( - defaultTargetPlatform == TargetPlatform.macOS || - defaultTargetPlatform == TargetPlatform.windows || - defaultTargetPlatform == TargetPlatform.android - ), + skip: !kIsWeb && + (defaultTargetPlatform == TargetPlatform.macOS || + defaultTargetPlatform == TargetPlatform.windows || + defaultTargetPlatform == TargetPlatform.android), ); test( From 6d9becb5dbdf93016a4d4aee49df095ad05d6c02 Mon Sep 17 00:00:00 2001 From: gurusai-voleti Date: Mon, 2 Mar 2026 19:15:52 +0530 Subject: [PATCH 22/72] chore: Migrate gsutil usage to gcloud storage (#18035) * chore: Migrate gsutil usage to gcloud storage * Update start.md --- docs/storage/start.md | 4 +++- packages/firebase_storage/firebase_storage_web/README.md | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/storage/start.md b/docs/storage/start.md index 4e7f846f8f77..4b7e503f931a 100644 --- a/docs/storage/start.md +++ b/docs/storage/start.md @@ -148,7 +148,9 @@ have to grant Firebase the ability to access these files using the [Google Cloud SDK](//cloud.google.com/sdk/docs/): ```bash -gsutil -m acl ch -r -u service-PROJECT_NUMBER@gcp-sa-firebasestorage.iam.gserviceaccount.com gs://YOUR-CLOUD-STORAGE-BUCKET +gcloud storage objects add-iam-policy-binding gs://YOUR-CLOUD-STORAGE-BUCKET \ + --member="serviceAccount:service-PROJECT_NUMBER@gcp-sa-firebasestorage.iam.gserviceaccount.com" \ + --role="roles/storage.objectViewer" ``` You can find your project number as described in the [introduction to diff --git a/packages/firebase_storage/firebase_storage_web/README.md b/packages/firebase_storage/firebase_storage_web/README.md index 04207f058085..b47289d6b86c 100644 --- a/packages/firebase_storage/firebase_storage_web/README.md +++ b/packages/firebase_storage/firebase_storage_web/README.md @@ -48,7 +48,7 @@ firebase_storage/example$ cat cors.json And then, with `gsutil`: ``` -firebase_storage/example$ gsutil cors set cors.json gs://my-example-bucket.appspot.com +firebase_storage/example$ gcloud storage buckets update gs://my-example-bucket.appspot.com --cors-file=cors.json Setting CORS on gs://my-example-bucket.appspot.com/... ``` From 407c2490602484499d1ab5b2ce6860af00a218c8 Mon Sep 17 00:00:00 2001 From: Jude Selase Kwashie <64037520+SelaseKay@users.noreply.github.com> Date: Mon, 2 Mar 2026 13:46:48 +0000 Subject: [PATCH 23/72] fix(analytics, iOS): Update hashedPhoneNumber handling to use hex string conversion (#17807) * fix(analytics, iOS): Update hashedPhoneNumber handling to use hex string conversion and add Data extension for hex string initialization * refactor: Replace Data extension with a dedicated hex string conversion function for hashedPhoneNumber handling --- .../FirebaseAnalyticsPlugin.swift | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/Sources/firebase_analytics/FirebaseAnalyticsPlugin.swift b/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/Sources/firebase_analytics/FirebaseAnalyticsPlugin.swift index fcd6bda21819..80c7b5c43668 100644 --- a/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/Sources/firebase_analytics/FirebaseAnalyticsPlugin.swift +++ b/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/Sources/firebase_analytics/FirebaseAnalyticsPlugin.swift @@ -136,12 +136,31 @@ public class FirebaseAnalyticsPlugin: NSObject, FLTFirebasePluginProtocol, Flutt Analytics.initiateOnDeviceConversionMeasurement(hashedEmailAddress: data) } if let hashedPhoneNumber = arguments["hashedPhoneNumber"] as? String, - let data = hashedPhoneNumber.data(using: .utf8) { + let data = hexStringToData(hashedPhoneNumber) { Analytics.initiateOnDeviceConversionMeasurement(hashedPhoneNumber: data) } completion(.success(())) } + private func hexStringToData(_ hexString: String) -> Data? { + let length = hexString.count + guard length % 2 == 0 else { return nil } + + var data = Data(capacity: length / 2) + var index = hexString.startIndex + + for _ in 0 ..< (length / 2) { + let nextIndex = hexString.index(index, offsetBy: 2) + guard let byte = UInt8(hexString[index ..< nextIndex], radix: 16) else { + return nil + } + data.append(byte) + index = nextIndex + } + + return data + } + public func didReinitializeFirebaseCore(_ completion: @escaping () -> Void) { completion() } From 840dc5b7aff02f5f749d0cc6f9ebcc8f780964be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 14:57:53 +0100 Subject: [PATCH 24/72] chore(deps): bump node-forge in /.github/workflows/scripts/functions (#17914) Bumps [node-forge](https://github.com/digitalbazaar/forge) from 1.3.1 to 1.3.3. - [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md) - [Commits](https://github.com/digitalbazaar/forge/compare/v1.3.1...v1.3.3) --- updated-dependencies: - dependency-name: node-forge dependency-version: 1.3.3 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../workflows/scripts/functions/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/scripts/functions/package-lock.json b/.github/workflows/scripts/functions/package-lock.json index e48eda2f586d..e3d38204881a 100644 --- a/.github/workflows/scripts/functions/package-lock.json +++ b/.github/workflows/scripts/functions/package-lock.json @@ -1726,9 +1726,9 @@ } }, "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", + "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", "engines": { "node": ">= 6.13.0" } @@ -3727,9 +3727,9 @@ } }, "node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", + "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==" }, "object-assign": { "version": "4.1.1", From 28081dbdad359cbd7258783967eeb17fe6548067 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 14:58:01 +0100 Subject: [PATCH 25/72] chore(deps): bump fast-xml-parser and @google-cloud/storage (#18024) Bumps [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) and [@google-cloud/storage](https://github.com/googleapis/nodejs-storage). These dependencies needed to be updated together. Updates `fast-xml-parser` from 4.5.3 to 5.3.6 - [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases) - [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md) - [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/commits/v5.3.6) Updates `@google-cloud/storage` from 7.16.0 to 7.19.0 - [Release notes](https://github.com/googleapis/nodejs-storage/releases) - [Changelog](https://github.com/googleapis/nodejs-storage/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/nodejs-storage/compare/v7.16.0...v7.19.0) --- updated-dependencies: - dependency-name: fast-xml-parser dependency-version: 5.3.6 dependency-type: indirect - dependency-name: "@google-cloud/storage" dependency-version: 7.19.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../scripts/functions/package-lock.json | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/workflows/scripts/functions/package-lock.json b/.github/workflows/scripts/functions/package-lock.json index e3d38204881a..ecf8136b4adb 100644 --- a/.github/workflows/scripts/functions/package-lock.json +++ b/.github/workflows/scripts/functions/package-lock.json @@ -163,9 +163,9 @@ } }, "node_modules/@google-cloud/storage": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.16.0.tgz", - "integrity": "sha512-7/5LRgykyOfQENcm6hDKP8SX/u9XxE5YOiWOkgkwcoO+cG8xT/cyOvp9wwN3IxfdYgpHs8CE7Nq2PKX2lNaEXw==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.19.0.tgz", + "integrity": "sha512-n2FjE7NAOYyshogdc7KQOl/VZb4sneqPjWouSyia9CMDdMhRX5+RIbqalNmC7LOLzuLAN89VlF2HvG8na9G+zQ==", "optional": true, "dependencies": { "@google-cloud/paginator": "^5.0.0", @@ -174,7 +174,7 @@ "abort-controller": "^3.0.0", "async-retry": "^1.3.3", "duplexify": "^4.1.3", - "fast-xml-parser": "^4.4.1", + "fast-xml-parser": "^5.3.4", "gaxios": "^6.0.2", "google-auth-library": "^9.6.3", "html-entities": "^2.5.2", @@ -992,9 +992,9 @@ "optional": true }, "node_modules/fast-xml-parser": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", - "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.6.tgz", + "integrity": "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==", "funding": [ { "type": "github", @@ -1003,7 +1003,7 @@ ], "optional": true, "dependencies": { - "strnum": "^1.1.1" + "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" @@ -2128,9 +2128,9 @@ } }, "node_modules/strnum": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", - "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", "funding": [ { "type": "github", @@ -2519,9 +2519,9 @@ "optional": true }, "@google-cloud/storage": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.16.0.tgz", - "integrity": "sha512-7/5LRgykyOfQENcm6hDKP8SX/u9XxE5YOiWOkgkwcoO+cG8xT/cyOvp9wwN3IxfdYgpHs8CE7Nq2PKX2lNaEXw==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.19.0.tgz", + "integrity": "sha512-n2FjE7NAOYyshogdc7KQOl/VZb4sneqPjWouSyia9CMDdMhRX5+RIbqalNmC7LOLzuLAN89VlF2HvG8na9G+zQ==", "optional": true, "requires": { "@google-cloud/paginator": "^5.0.0", @@ -2530,7 +2530,7 @@ "abort-controller": "^3.0.0", "async-retry": "^1.3.3", "duplexify": "^4.1.3", - "fast-xml-parser": "^4.4.1", + "fast-xml-parser": "^5.3.4", "gaxios": "^6.0.2", "google-auth-library": "^9.6.3", "html-entities": "^2.5.2", @@ -3190,12 +3190,12 @@ "optional": true }, "fast-xml-parser": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", - "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.6.tgz", + "integrity": "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==", "optional": true, "requires": { - "strnum": "^1.1.1" + "strnum": "^2.1.2" } }, "faye-websocket": { @@ -4035,9 +4035,9 @@ } }, "strnum": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", - "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", "optional": true }, "stubs": { From b73fa3232b6a612b4e73a1458f695db1aa16fae9 Mon Sep 17 00:00:00 2001 From: OrlandriaC-G <112568492+OrlandriaH-G@users.noreply.github.com> Date: Mon, 2 Mar 2026 08:58:16 -0500 Subject: [PATCH 26/72] Update client.md (#17689) Updating this page as part of the FCM doc revamp project --- docs/cloud-messaging/client.md | 116 ++++++++++++++++++--------------- 1 file changed, 63 insertions(+), 53 deletions(-) diff --git a/docs/cloud-messaging/client.md b/docs/cloud-messaging/client.md index d5c8861d127b..7988b30cca16 100644 --- a/docs/cloud-messaging/client.md +++ b/docs/cloud-messaging/client.md @@ -1,25 +1,12 @@ -Project: /docs/cloud-messaging/_project.yaml -Book: /docs/_book.yaml -page_type: guide - -{% include "_shared/apis/console/_local_variables.html" %} -{% include "_local_variables.html" %} -{% include "docs/cloud-messaging/_local_variables.html" %} -{% include "docs/android/_local_variables.html" %} - # Set up a Firebase Cloud Messaging client app on Flutter -Follow these steps to set up an FCM client on Flutter. - -## Platform-specific setup and requirements +Depending on the platform you're targeting, there are some additional required setup steps that you'll need to take. -Some of the required steps depend on the platform you're targeting. +## iOS+ -### iOS+ - -#### Enable app capabilities in Xcode +### Enable app capabilities in Xcode Before your application can start to receive messages, you must enable push notifications and background modes in your Xcode project. @@ -31,27 +18,25 @@ notifications and background modes in your Xcode project. #### Upload your APNs authentication key -Before you use FCM, upload your APNs certificate to Firebase. If you don't -already have an APNs certificate, create one in the +Before you use FCM, upload your APNs authentication key to Firebase. If you don't +already have an APNs authentication key, create one in the [Apple Developer Member Center](https://developer.apple.com/membercenter/index.action). 1. Inside your project in the Firebase console, select the gear icon, select **Project Settings**, and then select the **Cloud Messaging** tab. -1. Select the **Upload Certificate** button for your development certificate, - your production certificate, or both. At least one is required. -1. For each certificate, select the .p12 file, and provide the password, if - any. Make sure the bundle ID for this certificate matches the bundle ID of - your app. Select **Save**. +1. Select the **Upload** button for your development authentication key, + your production authentication key, or both. At least one is required. +1. For each authentication key, select the .p8 file, and provide the key ID and your Apple team ID. Select **Save**. #### Method swizzling -To use the FCM Flutter plugin on Apple devices, you must not disable method -swizzling. Swizzling is required, and without it, key Firebase features such as -FCM token handling do not function properly. +To use the FCM Flutter plugin on Apple devices, method +swizzling is required. Without it, key Firebase features such as +FCM token handling won't function properly. -### Android +## Android -#### Google Play services +### Google Play services FCM clients require devices running Android 4.4 or higher that also have Google Play services installed, or an emulator running Android 4.4 with Google APIs. @@ -69,12 +54,12 @@ other means, such as through the back button, the check is still performed. If the device doesn't have a compatible version of Google Play services, your app can call [`GoogleApiAvailability.makeGooglePlayServicesAvailable()`](//developers.google.com/android/reference/com/google/android/gms/common/GoogleApiAvailability.html#public-methods) to allow users to download Google Play services from the Play Store. -### Web +## Web -#### Configure Web Credentials with FCM +### Configure Web Credentials with FCM -The FCM Web interface uses Web credentials called "Voluntary Application Server -Identification," or "VAPID" keys, to authorize send requests to supported web +The FCM Web interface uses Web credentials called Voluntary Application Server +Identification, or "VAPID" keys, to authorize send requests to supported web push services. To subscribe your app to push notifications, you need to associate a pair of keys with your Firebase project. You can either generate a new key pair or import your existing key pair through the Firebase console. @@ -131,20 +116,15 @@ see [Application server keys](https://developers.google.com/web/fundamentals/pus flutter run ``` - ## Access the registration token -To send a message to a specific device, you need to know that device's -registration token. Because you'll need to enter the token in a field in the -Notifications console to complete this tutorial, make sure to copy the token -or securely store it after you retrieve it. - -To retrieve the current registration token for an app instance, call +To send a message to a specific device, you need to know the device +registration token. To retrieve the current registration token for an app instance, call `getToken()`. If notification permission has not been granted, this method will ask the user for notification permissions. Otherwise, it returns a token or rejects the future due to an error. -Warning: In iOS SDK 10.4.0 and higher, it is a requirement that the APNs token +Warning: In iOS SDK 10.4.0 and higher, it is required that the APNs token is available before making API requests. The APNs token is not guaranteed to have been received before making FCM plugin API requests. @@ -182,14 +162,13 @@ FirebaseMessaging.instance.onTokenRefresh }); ``` - ## Prevent auto initialization {:#prevent-auto-init} When an FCM registration token is generated, the library uploads the identifier and configuration data to Firebase. If you prefer to prevent token autogeneration, disable auto-initialization at build time. -#### iOS +### iOS On iOS, add a metadata value to your `Info.plist`: @@ -197,8 +176,7 @@ On iOS, add a metadata value to your `Info.plist`: FirebaseMessagingAutoInitEnabled = NO ``` - -#### Android +### Android On Android, disable Analytics collection and FCM auto initialization (you must disable both) by adding these metadata values to your `AndroidManifest.xml`: @@ -222,17 +200,49 @@ await FirebaseMessaging.instance.setAutoInitEnabled(true); This value persists across app restarts once set. -## Next steps +## Send a test notification message + +1. Install and run the app on the target device. On Apple devices, you'll need to accept the request for permission to receive remote notifications. +2. Make sure the app is in the background on the device. +3. In the Firebase console, open the Messaging page. +4. If this is your first message, select **Create your first campaign**. Select **Firebase Notification messages** and select **Create**. +5. Otherwise, on the **Campaign** tab, select **New campaign** and then **Notifications**. +6. Enter the message text. +7. Select **Send test message** from the right pane. +8. In the field labeled **Add an FCM registration token**, enter your registration token. +9. Select **Test**. + +After you select **Test**, the targeted client device, with the app in the background, should receive the notification. + +For insight into message delivery to your app, see the FCM reporting dashboard, which records the number of messages sent and opened on Apple and Android devices, along with impression data for Android apps. + +## Handling interaction -After the client app is set up, you are ready to start sending downstream -messages with the -[Notifications composer](//console.firebase.google.com/project/_/notification). -See [Send a test message to a backgrounded app](first-message). +When users tap a notification, the default behavior on both Android and iOS is +to open the application. If the application is terminated, it will be started, +and if it is in the background, it will be brought to the foreground. -To add other, more advanced behavior to your app, you'll need a -[server implementation](/docs/cloud-messaging/server). +Depending on the content of a notification, you may want to handle the user's +interaction when the application opens. For example, if a new chat message is +sent using a notification and the user selects it, you may want to open the +specific conversation when the application opens. -Then, in your app client: +The `firebase-messaging` package provides two ways to handle this interaction: + 1. `getInitialMessage():` If the application is opened from a terminated + state, this method returns a `Future` containing a `RemoteMessage`. Once + consumed, the `RemoteMessage` will be removed. + 1. `onMessageOpenedApp`: A`Stream` which posts a `RemoteMessage` when the + application is opened from a background state. + +To make sure your users have a smooth experience, you should handle both +scenarios. The following code example outlines how this can be achieved: + +How you handle interactions depends on your application setup. The previously +shown example is a basic example of using a `StatefulWidget`. + +## Next steps +After the client app is set up, you can start receiving messages or sending them to your users: +- [Send a test message to a backgrounded app](first-message) - [Receive messages](/docs/cloud-messaging/flutter/receive) -- [Subscribe to message topics](/docs/cloud-messaging/flutter/topic-messaging) +- [Notification composer](///console.firebase.google.com/project/_/notification) From ba3c436ffae1d020f2d92cf6573051dd0ca95a65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 14:58:29 +0100 Subject: [PATCH 27/72] chore(deps): bump qs and express in /.github/workflows/scripts/functions (#18019) Bumps [qs](https://github.com/ljharb/qs) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together. Updates `qs` from 6.13.0 to 6.14.2 - [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md) - [Commits](https://github.com/ljharb/qs/compare/v6.13.0...v6.14.2) Updates `express` from 4.21.2 to 4.22.1 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/v4.22.1/History.md) - [Commits](https://github.com/expressjs/express/compare/4.21.2...v4.22.1) --- updated-dependencies: - dependency-name: qs dependency-version: 6.14.2 dependency-type: indirect - dependency-name: express dependency-version: 4.22.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../scripts/functions/package-lock.json | 328 ++++++++++-------- 1 file changed, 177 insertions(+), 151 deletions(-) diff --git a/.github/workflows/scripts/functions/package-lock.json b/.github/workflows/scripts/functions/package-lock.json index ecf8136b4adb..e3fc32958c79 100644 --- a/.github/workflows/scripts/functions/package-lock.json +++ b/.github/workflows/scripts/functions/package-lock.json @@ -596,34 +596,31 @@ "node": ">= 0.8" } }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dependencies": { - "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "function-bind": "^1.1.2" }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/cliui": { @@ -730,22 +727,6 @@ } } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -907,38 +888,38 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -972,6 +953,20 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "node_modules/express/node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -1333,17 +1328,6 @@ "node": ">=14.0.0" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -1751,9 +1735,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "engines": { "node": ">= 0.4" }, @@ -2031,36 +2015,71 @@ "node": ">= 0.8" } }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dependencies": { - "define-data-property": "^1.1.4", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -2886,18 +2905,6 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, - "call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - } - }, "call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -2907,6 +2914,15 @@ "function-bind": "^1.1.2" } }, + "call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + } + }, "cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -2982,16 +2998,6 @@ "ms": "^2.1.3" } }, - "define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - } - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -3116,38 +3122,38 @@ "optional": true }, "express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -3170,6 +3176,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "requires": { + "side-channel": "^1.1.0" + } } } }, @@ -3436,14 +3450,6 @@ "jws": "^4.0.0" } }, - "has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "requires": { - "es-define-property": "^1.0.0" - } - }, "has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -3743,9 +3749,9 @@ "optional": true }, "object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==" + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" }, "on-finished": { "version": "2.4.1", @@ -3956,33 +3962,53 @@ } } }, - "set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "requires": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - } - }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + } + }, + "side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + } + }, + "side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + } + }, + "side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "requires": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" } }, "statuses": { From 46cbaed5efeb90c233a3de295b152e9a65a1e640 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 14:58:37 +0100 Subject: [PATCH 28/72] chore(deps): bump lodash in /.github/workflows/scripts/functions (#17965) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23) --- updated-dependencies: - dependency-name: lodash dependency-version: 4.17.23 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../workflows/scripts/functions/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/scripts/functions/package-lock.json b/.github/workflows/scripts/functions/package-lock.json index e3fc32958c79..6ba199f25fa6 100644 --- a/.github/workflows/scripts/functions/package-lock.json +++ b/.github/workflows/scripts/functions/package-lock.json @@ -1574,9 +1574,9 @@ "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==" }, "node_modules/lodash.camelcase": { "version": "4.3.0", @@ -3638,9 +3638,9 @@ "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" }, "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==" }, "lodash.camelcase": { "version": "4.3.0", From d024d0726caa511f20e5a550d6e9985660ea13ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 14:58:47 +0100 Subject: [PATCH 29/72] chore(deps): bump jws in /.github/workflows/scripts/functions (#17894) Bumps and [jws](https://github.com/brianloveswords/node-jws). These dependencies needed to be updated together. Updates `jws` from 4.0.0 to 4.0.1 - [Release notes](https://github.com/brianloveswords/node-jws/releases) - [Changelog](https://github.com/auth0/node-jws/blob/master/CHANGELOG.md) - [Commits](https://github.com/brianloveswords/node-jws/compare/v4.0.0...v4.0.1) Updates `jws` from 3.2.2 to 3.2.3 - [Release notes](https://github.com/brianloveswords/node-jws/releases) - [Changelog](https://github.com/auth0/node-jws/blob/master/CHANGELOG.md) - [Commits](https://github.com/brianloveswords/node-jws/compare/v4.0.0...v4.0.1) --- updated-dependencies: - dependency-name: jws dependency-version: 4.0.1 dependency-type: indirect - dependency-name: jws dependency-version: 3.2.3 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../scripts/functions/package-lock.json | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/.github/workflows/scripts/functions/package-lock.json b/.github/workflows/scripts/functions/package-lock.json index 6ba199f25fa6..6352cd207e5c 100644 --- a/.github/workflows/scripts/functions/package-lock.json +++ b/.github/workflows/scripts/functions/package-lock.json @@ -1515,30 +1515,30 @@ } }, "node_modules/jsonwebtoken/node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", "dependencies": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "node_modules/jsonwebtoken/node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", "dependencies": { - "jwa": "^1.4.1", + "jwa": "^1.4.2", "safe-buffer": "^5.0.1" } }, "node_modules/jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "dependencies": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } @@ -1560,11 +1560,11 @@ } }, "node_modules/jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "dependencies": { - "jwa": "^2.0.0", + "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, @@ -3580,32 +3580,32 @@ }, "dependencies": { "jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", "requires": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", "requires": { - "jwa": "^1.4.1", + "jwa": "^1.4.2", "safe-buffer": "^5.0.1" } } } }, "jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "requires": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } @@ -3624,11 +3624,11 @@ } }, "jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "requires": { - "jwa": "^2.0.0", + "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, From 247cd2a9fe36a8745268ce1db5e3e1f9a89c7625 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 14:59:02 +0100 Subject: [PATCH 30/72] chore(deps): bump github/codeql-action from 3.28.16 to 3.30.5 (#17754) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.16 to 3.30.5. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/28deaeda66b76a05916b6923827895f2b14ab387...3599b3baa15b485a2e49ef411a7a4bb2452e7f93) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.30.5 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ossf-scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 6b12485276b7..e828c3cc7a28 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 with: sarif_file: results.sarif From 9361105aa7ed3e0b84fd58b371b0801c50720ade Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 14:59:04 +0100 Subject: [PATCH 31/72] chore(deps): bump form-data in /.github/workflows/scripts/functions (#17544) Bumps [form-data](https://github.com/form-data/form-data) from 2.5.3 to 2.5.5. - [Release notes](https://github.com/form-data/form-data/releases) - [Changelog](https://github.com/form-data/form-data/blob/v2.5.5/CHANGELOG.md) - [Commits](https://github.com/form-data/form-data/compare/v2.5.3...v2.5.5) --- updated-dependencies: - dependency-name: form-data dependency-version: 2.5.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../workflows/scripts/functions/package-lock.json | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/scripts/functions/package-lock.json b/.github/workflows/scripts/functions/package-lock.json index 6352cd207e5c..d0e6b9f3c645 100644 --- a/.github/workflows/scripts/functions/package-lock.json +++ b/.github/workflows/scripts/functions/package-lock.json @@ -1116,14 +1116,15 @@ } }, "node_modules/form-data": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.3.tgz", - "integrity": "sha512-XHIrMD0NpDrNM/Ckf7XJiBbLl57KEhT3+i3yY+eWm+cqYZJQTZrKo8Y8AWKnuV5GT4scfuUGt9LzNoIx3dU1nQ==", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", + "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", "optional": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.35", "safe-buffer": "^5.2.1" }, @@ -3296,14 +3297,15 @@ } }, "form-data": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.3.tgz", - "integrity": "sha512-XHIrMD0NpDrNM/Ckf7XJiBbLl57KEhT3+i3yY+eWm+cqYZJQTZrKo8Y8AWKnuV5GT4scfuUGt9LzNoIx3dU1nQ==", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", + "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", "optional": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.35", "safe-buffer": "^5.2.1" } From 8b53700139a8f9efe84f4e7edbb468714e68f625 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 14:59:12 +0100 Subject: [PATCH 32/72] chore(deps): bump actions/upload-artifact from 4.4.3 to 4.6.2 (#17251) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.4.3 to 4.6.2. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882...ea165f8d65b6e75b540449e92b4886f43607fa02) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ossf-scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index e828c3cc7a28..95e86678e12e 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -59,7 +59,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: SARIF file path: results.sarif From 870b7dd9b78c5ff72761dd58ca72a93c3327d3ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 14:59:18 +0100 Subject: [PATCH 33/72] chore(deps): bump dart-lang/setup-dart from 1.7.0 to 1.7.1 (#17158) Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.7.0 to 1.7.1. - [Release notes](https://github.com/dart-lang/setup-dart/releases) - [Changelog](https://github.com/dart-lang/setup-dart/blob/main/CHANGELOG.md) - [Commits](https://github.com/dart-lang/setup-dart/compare/e630b99d28a3b71860378cafdc2a067c71107f94...e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c) --- updated-dependencies: - dependency-name: dart-lang/setup-dart dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/all_plugins.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/all_plugins.yaml b/.github/workflows/all_plugins.yaml index 3dc43e4eb790..ac5acce22e61 100644 --- a/.github/workflows/all_plugins.yaml +++ b/.github/workflows/all_plugins.yaml @@ -193,7 +193,7 @@ jobs: # check-license-header) - run: go install github.com/google/addlicense@latest - name: Install Dart - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 + uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c - name: Install Melos uses: bluefireteam/melos-action@c7dcb921b23cc520cace360b95d02b37bf09cdaa with: From c2282fa2baf1308e44e03c36b16c25be2e99b526 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 15:12:15 +0100 Subject: [PATCH 34/72] chore(deps): bump hendrikmuhs/ccache-action from 1.2.13 to 1.2.19 (#17756) Bumps [hendrikmuhs/ccache-action](https://github.com/hendrikmuhs/ccache-action) from 1.2.13 to 1.2.19. - [Release notes](https://github.com/hendrikmuhs/ccache-action/releases) - [Commits](https://github.com/hendrikmuhs/ccache-action/compare/c92f40bee50034e84c763e33b317c77adaa81c92...bfa03e1de4d7f7c3e80ad9109feedd05c4f5a716) --- updated-dependencies: - dependency-name: hendrikmuhs/ccache-action dependency-version: 1.2.19 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/e2e_tests_fdc.yaml | 2 +- .github/workflows/ios.yaml | 2 +- .github/workflows/macos.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e_tests_fdc.yaml b/.github/workflows/e2e_tests_fdc.yaml index 66bd1c53f8f7..f746c59e87d3 100644 --- a/.github/workflows/e2e_tests_fdc.yaml +++ b/.github/workflows/e2e_tests_fdc.yaml @@ -153,7 +153,7 @@ jobs: java-version: '21' - name: Setup PostgreSQL for Linux/macOS/Windows uses: ikalnytskyi/action-setup-postgres@v7 - - uses: hendrikmuhs/ccache-action@c92f40bee50034e84c763e33b317c77adaa81c92 + - uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639 name: Xcode Compile Cache with: key: xcode-cache-${{ runner.os }} diff --git a/.github/workflows/ios.yaml b/.github/workflows/ios.yaml index c58afe204a5e..494ca9adc250 100644 --- a/.github/workflows/ios.yaml +++ b/.github/workflows/ios.yaml @@ -45,7 +45,7 @@ jobs: with: distribution: 'temurin' java-version: '21' - - uses: hendrikmuhs/ccache-action@c92f40bee50034e84c763e33b317c77adaa81c92 + - uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639 name: Xcode Compile Cache with: key: xcode-cache-${{ runner.os }} diff --git a/.github/workflows/macos.yaml b/.github/workflows/macos.yaml index 0012b593f2db..b92acb0687ea 100644 --- a/.github/workflows/macos.yaml +++ b/.github/workflows/macos.yaml @@ -43,7 +43,7 @@ jobs: with: distribution: 'temurin' java-version: '21' - - uses: hendrikmuhs/ccache-action@c92f40bee50034e84c763e33b317c77adaa81c92 + - uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639 name: Xcode Compile Cache with: key: xcode-cache-${{ runner.os }} From 548cf5100d1512f11662892319fb2ad87db4b678 Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Mon, 2 Mar 2026 16:06:32 +0100 Subject: [PATCH 35/72] chore(release): publish packages (#18050) * chore(release): publish packages - _flutterfire_internals@1.3.67 - cloud_firestore@6.1.3 - firebase_ai@3.9.0 - firebase_analytics@12.1.3 - firebase_auth@6.2.0 - firebase_core@4.5.0 - firebase_core_web@3.5.0 - firebase_data_connect@0.2.3 - firebase_remote_config@6.2.0 - firebase_remote_config_platform_interface@2.1.0 - firebase_storage@13.1.0 - firebase_in_app_messaging_platform_interface@0.2.5+18 - firebase_crashlytics_platform_interface@3.8.18 - firebase_remote_config_web@1.10.4 - firebase_database_platform_interface@0.3.0+3 - cloud_firestore_web@5.1.3 - firebase_app_installations_platform_interface@0.1.4+66 - firebase_messaging_web@4.1.3 - firebase_app_installations_web@0.1.7+3 - firebase_auth_platform_interface@8.1.7 - firebase_messaging_platform_interface@4.7.7 - cloud_firestore_platform_interface@7.0.7 - firebase_analytics_web@0.6.1+3 - firebase_app_check_platform_interface@0.2.1+5 - firebase_app_check_web@0.2.2+3 - firebase_analytics_platform_interface@5.0.7 - firebase_storage_web@3.11.3 - firebase_performance_platform_interface@0.1.6+5 - firebase_storage_platform_interface@5.2.18 - firebase_performance_web@0.1.8+3 - firebase_in_app_messaging@0.9.0+7 - firebase_crashlytics@5.0.8 - firebase_database_web@0.2.7+4 - firebase_database@12.1.4 - firebase_app_installations@0.4.0+7 - firebase_messaging@16.1.2 - firebase_auth_web@6.1.3 - firebase_app_check@0.4.1+5 - firebase_performance@0.11.1+5 - firebase_ml_model_downloader@0.4.0+7 - firebase_ml_model_downloader_platform_interface@0.1.5+18 - cloud_functions_web@5.1.3 - cloud_functions@6.0.7 - cloud_functions_platform_interface@5.8.10 * chore: BoM Version 4.10.0 * update * update changelog --- CHANGELOG.md | 150 ++++++++++++++++++ Package.swift | 4 +- VERSIONS.md | 38 +++++ packages/_flutterfire_internals/CHANGELOG.md | 4 + packages/_flutterfire_internals/pubspec.yaml | 4 +- .../cloud_firestore/CHANGELOG.md | 4 + .../cloud_firestore/example/pubspec.yaml | 4 +- .../ios/generated_firebase_sdk_version.txt | 2 +- .../cloud_firestore/pubspec.yaml | 8 +- .../CHANGELOG.md | 4 + .../pubspec.yaml | 6 +- .../cloud_firestore_web/CHANGELOG.md | 4 + .../lib/src/cloud_firestore_version.dart | 2 +- .../cloud_firestore_web/pubspec.yaml | 10 +- .../cloud_functions/CHANGELOG.md | 4 + .../cloud_functions/example/pubspec.yaml | 4 +- .../Sources/cloud_functions/Constants.swift | 2 +- .../ios/generated_firebase_sdk_version.txt | 2 +- .../cloud_functions/pubspec.yaml | 8 +- .../CHANGELOG.md | 4 + .../pubspec.yaml | 4 +- .../cloud_functions_web/CHANGELOG.md | 4 + .../lib/src/cloud_functions_version.dart | 2 +- .../cloud_functions_web/pubspec.yaml | 8 +- packages/firebase_ai/firebase_ai/CHANGELOG.md | 5 + .../firebase_ai/example/pubspec.yaml | 6 +- .../lib/src/firebaseai_version.dart | 2 +- packages/firebase_ai/firebase_ai/pubspec.yaml | 8 +- .../firebase_analytics/CHANGELOG.md | 4 + .../firebase_analytics/example/pubspec.yaml | 4 +- .../ios/generated_firebase_sdk_version.txt | 2 +- .../firebase_analytics/pubspec.yaml | 8 +- .../CHANGELOG.md | 4 + .../pubspec.yaml | 6 +- .../firebase_analytics_web/CHANGELOG.md | 4 + .../lib/src/firebase_analytics_version.dart | 2 +- .../firebase_analytics_web/pubspec.yaml | 10 +- .../firebase_app_check/CHANGELOG.md | 4 + .../firebase_app_check/example/pubspec.yaml | 6 +- .../ios/generated_firebase_sdk_version.txt | 2 +- .../firebase_app_check/pubspec.yaml | 8 +- .../CHANGELOG.md | 4 + .../pubspec.yaml | 6 +- .../firebase_app_check_web/CHANGELOG.md | 4 + .../lib/src/firebase_app_check_version.dart | 2 +- .../firebase_app_check_web/pubspec.yaml | 10 +- .../firebase_app_installations/CHANGELOG.md | 4 + .../example/pubspec.yaml | 4 +- .../Constants.swift | 2 +- .../ios/generated_firebase_sdk_version.txt | 2 +- .../firebase_app_installations/pubspec.yaml | 8 +- .../CHANGELOG.md | 4 + .../pubspec.yaml | 6 +- .../CHANGELOG.md | 4 + .../firebase_app_installations_version.dart | 2 +- .../pubspec.yaml | 10 +- .../firebase_auth/firebase_auth/CHANGELOG.md | 4 + .../firebase_auth/example/pubspec.yaml | 6 +- .../ios/generated_firebase_sdk_version.txt | 2 +- .../firebase_auth/firebase_auth/pubspec.yaml | 8 +- .../CHANGELOG.md | 4 + .../pubspec.yaml | 6 +- .../firebase_auth_web/CHANGELOG.md | 4 + .../lib/src/firebase_auth_version.dart | 2 +- .../firebase_auth_web/pubspec.yaml | 8 +- .../firebase_core/firebase_core/CHANGELOG.md | 8 + .../firebase_core/example/pubspec.yaml | 2 +- .../firebase_core/firebase_core/pubspec.yaml | 4 +- .../firebase_core_web/CHANGELOG.md | 4 + .../lib/src/firebase_core_version.dart | 2 +- .../firebase_core_web/pubspec.yaml | 2 +- .../firebase_crashlytics/CHANGELOG.md | 4 + .../firebase_crashlytics/example/pubspec.yaml | 6 +- .../ios/generated_firebase_sdk_version.txt | 2 +- .../firebase_crashlytics/pubspec.yaml | 6 +- .../CHANGELOG.md | 4 + .../pubspec.yaml | 6 +- .../firebase_data_connect/CHANGELOG.md | 6 + .../example/pubspec.yaml | 6 +- .../lib/src/dataconnect_version.dart | 2 +- .../firebase_data_connect/pubspec.yaml | 12 +- .../firebase_database/CHANGELOG.md | 4 + .../firebase_database/example/pubspec.yaml | 4 +- .../ios/generated_firebase_sdk_version.txt | 2 +- .../firebase_database/pubspec.yaml | 8 +- .../CHANGELOG.md | 4 + .../pubspec.yaml | 6 +- .../firebase_database_web/CHANGELOG.md | 4 + .../lib/src/firebase_database_version.dart | 2 +- .../firebase_database_web/pubspec.yaml | 8 +- .../firebase_in_app_messaging/CHANGELOG.md | 4 + .../example/pubspec.yaml | 8 +- .../ios/generated_firebase_sdk_version.txt | 2 +- .../firebase_in_app_messaging/pubspec.yaml | 6 +- .../CHANGELOG.md | 4 + .../pubspec.yaml | 6 +- .../firebase_messaging/CHANGELOG.md | 4 + .../firebase_messaging/example/pubspec.yaml | 4 +- .../ios/generated_firebase_sdk_version.txt | 2 +- .../firebase_messaging/pubspec.yaml | 8 +- .../CHANGELOG.md | 4 + .../pubspec.yaml | 6 +- .../firebase_messaging_web/CHANGELOG.md | 4 + .../lib/src/firebase_messaging_version.dart | 2 +- .../firebase_messaging_web/pubspec.yaml | 10 +- .../firebase_ml_model_downloader/CHANGELOG.md | 4 + .../example/pubspec.yaml | 4 +- .../Constants.swift | 2 +- .../ios/generated_firebase_sdk_version.txt | 2 +- .../firebase_ml_model_downloader/pubspec.yaml | 6 +- .../CHANGELOG.md | 4 + .../pubspec.yaml | 4 +- .../firebase_performance/CHANGELOG.md | 4 + .../firebase_performance/example/pubspec.yaml | 4 +- .../ios/generated_firebase_sdk_version.txt | 2 +- .../firebase_performance/pubspec.yaml | 8 +- .../CHANGELOG.md | 4 + .../pubspec.yaml | 6 +- .../firebase_performance_web/CHANGELOG.md | 4 + .../lib/src/firebase_performance_version.dart | 2 +- .../firebase_performance_web/pubspec.yaml | 10 +- .../firebase_remote_config/CHANGELOG.md | 5 + .../example/pubspec.yaml | 4 +- .../ios/generated_firebase_sdk_version.txt | 2 +- .../firebase_remote_config/pubspec.yaml | 8 +- .../CHANGELOG.md | 4 + .../pubspec.yaml | 6 +- .../firebase_remote_config_web/CHANGELOG.md | 4 + .../src/firebase_remote_config_version.dart | 2 +- .../firebase_remote_config_web/pubspec.yaml | 10 +- .../firebase_storage/CHANGELOG.md | 4 + .../firebase_storage/example/pubspec.yaml | 4 +- .../ios/generated_firebase_sdk_version.txt | 2 +- .../firebase_storage/pubspec.yaml | 8 +- .../CHANGELOG.md | 4 + .../pubspec.yaml | 6 +- .../firebase_storage_web/CHANGELOG.md | 4 + .../lib/src/firebase_storage_version.dart | 2 +- .../firebase_storage_web/pubspec.yaml | 10 +- scripts/versions.json | 28 ++++ tests/pubspec.yaml | 68 ++++---- 141 files changed, 667 insertions(+), 267 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ff549d5057e..943eacc0fc18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,156 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 2026-03-02 - [BoM 4.10.0](https://github.com/firebase/flutterfire/blob/main/VERSIONS.md#flutter-bom-4100-2026-03-02) + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`_flutterfire_internals` - `v1.3.67`](#_flutterfire_internals---v1367) + - [`cloud_firestore` - `v6.1.3`](#cloud_firestore---v613) + - [`firebase_ai` - `v3.9.0`](#firebase_ai---v390) + - [`firebase_analytics` - `v12.1.3`](#firebase_analytics---v1213) + - [`firebase_auth` - `v6.2.0`](#firebase_auth---v620) + - [`firebase_core` - `v4.5.0`](#firebase_core---v450) + - [`firebase_core_web` - `v3.5.0`](#firebase_core_web---v350) + - [`firebase_data_connect` - `v0.2.3`](#firebase_data_connect---v023) + - [`firebase_remote_config` - `v6.2.0`](#firebase_remote_config---v620) + - [`firebase_remote_config_platform_interface` - `v2.1.0`](#firebase_remote_config_platform_interface---v210) + - [`firebase_storage` - `v13.1.0`](#firebase_storage---v1310) + - [`firebase_in_app_messaging_platform_interface` - `v0.2.5+18`](#firebase_in_app_messaging_platform_interface---v02518) + - [`firebase_crashlytics_platform_interface` - `v3.8.18`](#firebase_crashlytics_platform_interface---v3818) + - [`firebase_remote_config_web` - `v1.10.4`](#firebase_remote_config_web---v1104) + - [`firebase_database_platform_interface` - `v0.3.0+3`](#firebase_database_platform_interface---v0303) + - [`cloud_firestore_web` - `v5.1.3`](#cloud_firestore_web---v513) + - [`firebase_app_installations_platform_interface` - `v0.1.4+66`](#firebase_app_installations_platform_interface---v01466) + - [`firebase_messaging_web` - `v4.1.3`](#firebase_messaging_web---v413) + - [`firebase_app_installations_web` - `v0.1.7+3`](#firebase_app_installations_web---v0173) + - [`firebase_auth_platform_interface` - `v8.1.7`](#firebase_auth_platform_interface---v817) + - [`firebase_messaging_platform_interface` - `v4.7.7`](#firebase_messaging_platform_interface---v477) + - [`cloud_firestore_platform_interface` - `v7.0.7`](#cloud_firestore_platform_interface---v707) + - [`firebase_analytics_web` - `v0.6.1+3`](#firebase_analytics_web---v0613) + - [`firebase_app_check_platform_interface` - `v0.2.1+5`](#firebase_app_check_platform_interface---v0215) + - [`firebase_app_check_web` - `v0.2.2+3`](#firebase_app_check_web---v0223) + - [`firebase_analytics_platform_interface` - `v5.0.7`](#firebase_analytics_platform_interface---v507) + - [`firebase_storage_web` - `v3.11.3`](#firebase_storage_web---v3113) + - [`firebase_performance_platform_interface` - `v0.1.6+5`](#firebase_performance_platform_interface---v0165) + - [`firebase_storage_platform_interface` - `v5.2.18`](#firebase_storage_platform_interface---v5218) + - [`firebase_performance_web` - `v0.1.8+3`](#firebase_performance_web---v0183) + - [`firebase_in_app_messaging` - `v0.9.0+7`](#firebase_in_app_messaging---v0907) + - [`firebase_crashlytics` - `v5.0.8`](#firebase_crashlytics---v508) + - [`firebase_database_web` - `v0.2.7+4`](#firebase_database_web---v0274) + - [`firebase_database` - `v12.1.4`](#firebase_database---v1214) + - [`firebase_app_installations` - `v0.4.0+7`](#firebase_app_installations---v0407) + - [`firebase_messaging` - `v16.1.2`](#firebase_messaging---v1612) + - [`firebase_auth_web` - `v6.1.3`](#firebase_auth_web---v613) + - [`firebase_app_check` - `v0.4.1+5`](#firebase_app_check---v0415) + - [`firebase_performance` - `v0.11.1+5`](#firebase_performance---v01115) + - [`firebase_ml_model_downloader` - `v0.4.0+7`](#firebase_ml_model_downloader---v0407) + - [`firebase_ml_model_downloader_platform_interface` - `v0.1.5+18`](#firebase_ml_model_downloader_platform_interface---v01518) + - [`cloud_functions_web` - `v5.1.3`](#cloud_functions_web---v513) + - [`cloud_functions` - `v6.0.7`](#cloud_functions---v607) + - [`cloud_functions_platform_interface` - `v5.8.10`](#cloud_functions_platform_interface---v5810) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `firebase_in_app_messaging_platform_interface` - `v0.2.5+18` + - `firebase_crashlytics_platform_interface` - `v3.8.18` + - `firebase_remote_config_web` - `v1.10.4` + - `firebase_database_platform_interface` - `v0.3.0+3` + - `cloud_firestore_web` - `v5.1.3` + - `firebase_app_installations_platform_interface` - `v0.1.4+66` + - `firebase_messaging_web` - `v4.1.3` + - `firebase_app_installations_web` - `v0.1.7+3` + - `firebase_auth_platform_interface` - `v8.1.7` + - `firebase_messaging_platform_interface` - `v4.7.7` + - `cloud_firestore_platform_interface` - `v7.0.7` + - `firebase_analytics_web` - `v0.6.1+3` + - `firebase_app_check_platform_interface` - `v0.2.1+5` + - `firebase_app_check_web` - `v0.2.2+3` + - `firebase_analytics_platform_interface` - `v5.0.7` + - `firebase_storage_web` - `v3.11.3` + - `firebase_performance_platform_interface` - `v0.1.6+5` + - `firebase_storage_platform_interface` - `v5.2.18` + - `firebase_performance_web` - `v0.1.8+3` + - `firebase_in_app_messaging` - `v0.9.0+7` + - `firebase_crashlytics` - `v5.0.8` + - `firebase_database_web` - `v0.2.7+4` + - `firebase_database` - `v12.1.4` + - `firebase_app_installations` - `v0.4.0+7` + - `firebase_messaging` - `v16.1.2` + - `firebase_auth_web` - `v6.1.3` + - `firebase_app_check` - `v0.4.1+5` + - `firebase_performance` - `v0.11.1+5` + - `firebase_ml_model_downloader` - `v0.4.0+7` + - `firebase_ml_model_downloader_platform_interface` - `v0.1.5+18` + - `cloud_functions_web` - `v5.1.3` + - `cloud_functions` - `v6.0.7` + - `cloud_functions_platform_interface` - `v5.8.10` + +--- + +#### `_flutterfire_internals` - `v1.3.67` + + - **FIX**(database): improve error handling in `platformExceptionToFirebaseException` ([#18007](https://github.com/firebase/flutterfire/issues/18007)). ([25f92046](https://github.com/firebase/flutterfire/commit/25f92046985b7b7105bb88f31d35d0d793744b23)) + +#### `cloud_firestore` - `v6.1.3` + + - **FIX**: resolve lint issues ([#18017](https://github.com/firebase/flutterfire/issues/18017)). ([e8e85397](https://github.com/firebase/flutterfire/commit/e8e85397ccdcab6c8b84348884b4673f86b79d1c)) + +#### `firebase_ai` - `v3.9.0` + + - **FIX**: resolve lint issues ([#18017](https://github.com/firebase/flutterfire/issues/18017)). ([e8e85397](https://github.com/firebase/flutterfire/commit/e8e85397ccdcab6c8b84348884b4673f86b79d1c)) + - **FEAT**(firebaseai): update Live API sample to add video support. ([#18018](https://github.com/firebase/flutterfire/issues/18018)). ([f91df750](https://github.com/firebase/flutterfire/commit/f91df7503bc4506c66cbebcfa562d65de1ae0e5b)) + +#### `firebase_analytics` - `v12.1.3` + + - **FIX**(analytics,iOS): Update hashedPhoneNumber handling to use hex string conversion ([#17807](https://github.com/firebase/flutterfire/issues/17807)). ([407c2490](https://github.com/firebase/flutterfire/commit/407c2490602484499d1ab5b2ce6860af00a218c8)) + +#### `firebase_auth` - `v6.2.0` + + - **FEAT**(remote-config,windows): add support for windows ([#18006](https://github.com/firebase/flutterfire/issues/18006)). ([a6ec167f](https://github.com/firebase/flutterfire/commit/a6ec167f4ece9c9b455a916366781f482cc380b3)) + +#### `firebase_core` - `v4.5.0` + + - **FEAT**(core,windows): update C++ Desktop SDK to 13.4.0. This may require updating your Visual Studio version and C++ build tools. ([#18006](https://github.com/firebase/flutterfire/issues/18006)). ([a6ec167f](https://github.com/firebase/flutterfire/commit/a6ec167f4ece9c9b455a916366781f482cc380b3)) + - **FIX**: resolve lint issues ([#18017](https://github.com/firebase/flutterfire/issues/18017)). ([e8e85397](https://github.com/firebase/flutterfire/commit/e8e85397ccdcab6c8b84348884b4673f86b79d1c)) + - **FEAT**: bump Firebase iOS SDK to 12.9.0 ([#18034](https://github.com/firebase/flutterfire/issues/18034)). ([c45894e2](https://github.com/firebase/flutterfire/commit/c45894e23895f9add8c152d13324920babe9b708)) + - **FEAT**: bump Firebase android SDK to 34.9.0 ([#18016](https://github.com/firebase/flutterfire/issues/18016)). ([b218dbff](https://github.com/firebase/flutterfire/commit/b218dbffd72d0bf666ff94f79a3de1e24d038df0)) + - **FEAT**(remote-config,windows): add support for windows ([#18006](https://github.com/firebase/flutterfire/issues/18006)). ([a6ec167f](https://github.com/firebase/flutterfire/commit/a6ec167f4ece9c9b455a916366781f482cc380b3)) + +#### `firebase_core_web` - `v3.5.0` + + - **FEAT**: bump Firebase JS SDK to 12.9.0 ([#18043](https://github.com/firebase/flutterfire/issues/18043)). ([1b29c4d4](https://github.com/firebase/flutterfire/commit/1b29c4d432597d12e08990825647f0ac9467a8f3)) + +#### `firebase_data_connect` - `v0.2.3` + + - **REFACTOR**(fdc): Support for entityId path extensions and hardening ([#17988](https://github.com/firebase/flutterfire/issues/17988)). ([fed585f5](https://github.com/firebase/flutterfire/commit/fed585f5a9b65d683cefdc7fa97ed2692e4ec817)) + - **FIX**: resolve lint issues ([#18017](https://github.com/firebase/flutterfire/issues/18017)). ([e8e85397](https://github.com/firebase/flutterfire/commit/e8e85397ccdcab6c8b84348884b4673f86b79d1c)) + - **FEAT**(fdc): Data Connect client sdk caching ([#17890](https://github.com/firebase/flutterfire/issues/17890)). ([02a019bc](https://github.com/firebase/flutterfire/commit/02a019bc25bb4a49d62c1079ed15e0c3aec8a5ec)) + +#### `firebase_remote_config` - `v6.2.0` + + - **FIX**(remote_config): correct `lastFetchTime` calculation ([#18004](https://github.com/firebase/flutterfire/issues/18004)). ([92f03e08](https://github.com/firebase/flutterfire/commit/92f03e08e9b5362c180da16d60d869568daf2c55)) + - **FEAT**(remote-config,windows): add support for windows ([#18006](https://github.com/firebase/flutterfire/issues/18006)). ([a6ec167f](https://github.com/firebase/flutterfire/commit/a6ec167f4ece9c9b455a916366781f482cc380b3)) + +#### `firebase_remote_config_platform_interface` - `v2.1.0` + + - **FEAT**(remote-config,windows): add support for windows ([#18006](https://github.com/firebase/flutterfire/issues/18006)). ([a6ec167f](https://github.com/firebase/flutterfire/commit/a6ec167f4ece9c9b455a916366781f482cc380b3)) + +#### `firebase_storage` - `v13.1.0` + + - **FEAT**(storage,windows): add emulator support ([#18030](https://github.com/firebase/flutterfire/issues/18030)). ([461dfa43](https://github.com/firebase/flutterfire/commit/461dfa43764469b518984052cb7bbc0a2a2675eb)) + + ## 2026-02-09 - [BoM 4.9.0](https://github.com/firebase/flutterfire/blob/main/VERSIONS.md#flutter-bom-490-2026-02-09) ### Changes diff --git a/Package.swift b/Package.swift index 139ffc0574ee..53cd2b38a81a 100644 --- a/Package.swift +++ b/Package.swift @@ -9,8 +9,8 @@ import Foundation import PackageDescription // auto-generated by melos post commit hook script -let firebase_core_version: String = "4.4.0" -let firebase_ios_sdk_version: String = "12.8.0" +let firebase_core_version: String = "4.5.0" +let firebase_ios_sdk_version: String = "12.9.0" /// Shared Swift package manager code for firebase core let package = Package( diff --git a/VERSIONS.md b/VERSIONS.md index e25ad0f1bc35..2616b73c2a36 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -4,6 +4,44 @@ This document is listing all the compatible versions of the FlutterFire plugins. # Versions +## [Flutter BoM 4.10.0 (2026-03-02)](https://github.com/firebase/flutterfire/blob/main/CHANGELOG.md#2026-03-02) + +Install this version using FlutterFire CLI + +```bash +flutterfire install 4.10.0 +``` + +### Included Native Firebase SDK Versions +| Firebase SDK | Version | Link | +|--------------|---------|------| +| Android SDK | 34.9.0 | [Release Notes](https://firebase.google.com/support/release-notes/android) | +| iOS SDK | 12.9.0 | [Release Notes](https://firebase.google.com/support/release-notes/ios) | +| Web SDK | 12.9.0 | [Release Notes](https://firebase.google.com/support/release-notes/js) | +| Windows SDK | 13.4.0 | [Release Notes](https://firebase.google.com/support/release-notes/cpp-relnotes) | + +### FlutterFire Plugin Versions +| Plugin | Version | Dart Version | Flutter Version | +|--------|---------|--------------|-----------------| +| [cloud_firestore](https://pub.dev/packages/cloud_firestore/versions/6.1.3) | 6.1.3 | >=3.2.0 <4.0.0 | >=3.3.0 | +| [cloud_functions](https://pub.dev/packages/cloud_functions/versions/6.0.7) | 6.0.7 | >=3.2.0 <4.0.0 | >=3.3.0 | +| [firebase_ai](https://pub.dev/packages/firebase_ai/versions/3.9.0) | 3.9.0 | >=3.2.0 <4.0.0 | >=3.16.0 | +| [firebase_analytics](https://pub.dev/packages/firebase_analytics/versions/12.1.3) | 12.1.3 | >=3.2.0 <4.0.0 | >=3.3.0 | +| [firebase_app_check](https://pub.dev/packages/firebase_app_check/versions/0.4.1+5) | 0.4.1+5 | >=3.2.0 <4.0.0 | >=3.3.0 | +| [firebase_app_installations](https://pub.dev/packages/firebase_app_installations/versions/0.4.0+7) | 0.4.0+7 | >=3.2.0 <4.0.0 | >=3.3.0 | +| [firebase_auth](https://pub.dev/packages/firebase_auth/versions/6.2.0) | 6.2.0 | >=3.2.0 <4.0.0 | >=3.16.0 | +| [firebase_core](https://pub.dev/packages/firebase_core/versions/4.5.0) | 4.5.0 | >=3.2.0 <4.0.0 | >=3.3.0 | +| [firebase_crashlytics](https://pub.dev/packages/firebase_crashlytics/versions/5.0.8) | 5.0.8 | >=3.2.0 <4.0.0 | >=3.3.0 | +| [firebase_data_connect](https://pub.dev/packages/firebase_data_connect/versions/0.2.3) | 0.2.3 | >=3.2.0 <4.0.0 | >=3.3.0 | +| [firebase_database](https://pub.dev/packages/firebase_database/versions/12.1.4) | 12.1.4 | >=3.2.0 <4.0.0 | >=3.3.0 | +| [firebase_in_app_messaging](https://pub.dev/packages/firebase_in_app_messaging/versions/0.9.0+7) | 0.9.0+7 | >=3.2.0 <4.0.0 | >=3.3.0 | +| [firebase_messaging](https://pub.dev/packages/firebase_messaging/versions/16.1.2) | 16.1.2 | >=3.2.0 <4.0.0 | >=3.3.0 | +| [firebase_ml_model_downloader](https://pub.dev/packages/firebase_ml_model_downloader/versions/0.4.0+7) | 0.4.0+7 | >=3.2.0 <4.0.0 | >=3.3.0 | +| [firebase_performance](https://pub.dev/packages/firebase_performance/versions/0.11.1+5) | 0.11.1+5 | >=3.2.0 <4.0.0 | >=3.3.0 | +| [firebase_remote_config](https://pub.dev/packages/firebase_remote_config/versions/6.2.0) | 6.2.0 | >=3.2.0 <4.0.0 | >=3.3.0 | +| [firebase_storage](https://pub.dev/packages/firebase_storage/versions/13.1.0) | 13.1.0 | >=3.2.0 <4.0.0 | >=3.3.0 | + + ## [Flutter BoM 4.9.0 (2026-02-09)](https://github.com/firebase/flutterfire/blob/main/CHANGELOG.md#2026-02-09) Install this version using FlutterFire CLI diff --git a/packages/_flutterfire_internals/CHANGELOG.md b/packages/_flutterfire_internals/CHANGELOG.md index dfda9b346106..94ea80f71c75 100644 --- a/packages/_flutterfire_internals/CHANGELOG.md +++ b/packages/_flutterfire_internals/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.3.67 + + - **FIX**(database): improve error handling in `platformExceptionToFirebaseException` ([#18007](https://github.com/firebase/flutterfire/issues/18007)). ([25f92046](https://github.com/firebase/flutterfire/commit/25f92046985b7b7105bb88f31d35d0d793744b23)) + ## 1.3.66 - Update a dependency to the latest release. diff --git a/packages/_flutterfire_internals/pubspec.yaml b/packages/_flutterfire_internals/pubspec.yaml index 46dfeda68820..435a4ac3a807 100755 --- a/packages/_flutterfire_internals/pubspec.yaml +++ b/packages/_flutterfire_internals/pubspec.yaml @@ -2,7 +2,7 @@ name: _flutterfire_internals description: A package hosting Dart code shared between FlutterFire plugins. homepage: https://firebase.google.com/docs/firestore repository: https://github.com/firebase/flutterfire/tree/main/packages/_flutterfire_internals -version: 1.3.66 +version: 1.3.67 environment: sdk: '>=3.2.0 <4.0.0' @@ -10,7 +10,7 @@ environment: dependencies: collection: ^1.0.0 - firebase_core: ^4.4.0 + firebase_core: ^4.5.0 firebase_core_platform_interface: ^6.0.2 flutter: sdk: flutter diff --git a/packages/cloud_firestore/cloud_firestore/CHANGELOG.md b/packages/cloud_firestore/cloud_firestore/CHANGELOG.md index 209e9b322228..3f5751eeb1be 100644 --- a/packages/cloud_firestore/cloud_firestore/CHANGELOG.md +++ b/packages/cloud_firestore/cloud_firestore/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.1.3 + + - **FIX**: resolve lint issues ([#18017](https://github.com/firebase/flutterfire/issues/18017)). ([e8e85397](https://github.com/firebase/flutterfire/commit/e8e85397ccdcab6c8b84348884b4673f86b79d1c)) + ## 6.1.2 - **FIX**(firestore,android): avoid ConcurrentModificationException by collecting Firestore instances before termination ([#17956](https://github.com/firebase/flutterfire/issues/17956)). ([f94bbd68](https://github.com/firebase/flutterfire/commit/f94bbd688c3c0aaa62ba9117b23902c10297ea84)) diff --git a/packages/cloud_firestore/cloud_firestore/example/pubspec.yaml b/packages/cloud_firestore/cloud_firestore/example/pubspec.yaml index 8825aa440aea..3895aab98525 100755 --- a/packages/cloud_firestore/cloud_firestore/example/pubspec.yaml +++ b/packages/cloud_firestore/cloud_firestore/example/pubspec.yaml @@ -5,8 +5,8 @@ environment: sdk: '>=3.2.0 <4.0.0' dependencies: - cloud_firestore: ^6.1.2 - firebase_core: ^4.4.0 + cloud_firestore: ^6.1.3 + firebase_core: ^4.5.0 flutter: sdk: flutter http: ^1.0.0 diff --git a/packages/cloud_firestore/cloud_firestore/ios/generated_firebase_sdk_version.txt b/packages/cloud_firestore/cloud_firestore/ios/generated_firebase_sdk_version.txt index a54ec1fce4a4..3eb7353bcd3e 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/generated_firebase_sdk_version.txt +++ b/packages/cloud_firestore/cloud_firestore/ios/generated_firebase_sdk_version.txt @@ -1 +1 @@ -12.8.0 \ No newline at end of file +12.9.0 \ No newline at end of file diff --git a/packages/cloud_firestore/cloud_firestore/pubspec.yaml b/packages/cloud_firestore/cloud_firestore/pubspec.yaml index 206d51178b6d..ca58a8f63461 100755 --- a/packages/cloud_firestore/cloud_firestore/pubspec.yaml +++ b/packages/cloud_firestore/cloud_firestore/pubspec.yaml @@ -4,7 +4,7 @@ description: live synchronization and offline support on Android and iOS. homepage: https://firebase.google.com/docs/firestore repository: https://github.com/firebase/flutterfire/tree/main/packages/cloud_firestore/cloud_firestore -version: 6.1.2 +version: 6.1.3 topics: - firebase - firestore @@ -20,10 +20,10 @@ environment: flutter: '>=3.3.0' dependencies: - cloud_firestore_platform_interface: ^7.0.6 - cloud_firestore_web: ^5.1.2 + cloud_firestore_platform_interface: ^7.0.7 + cloud_firestore_web: ^5.1.3 collection: ^1.0.0 - firebase_core: ^4.4.0 + firebase_core: ^4.5.0 firebase_core_platform_interface: ^6.0.2 flutter: sdk: flutter diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/CHANGELOG.md b/packages/cloud_firestore/cloud_firestore_platform_interface/CHANGELOG.md index 58596721bb42..36ba9c30451c 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/CHANGELOG.md +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 7.0.7 + + - Update a dependency to the latest release. + ## 7.0.6 - Update a dependency to the latest release. diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/pubspec.yaml b/packages/cloud_firestore/cloud_firestore_platform_interface/pubspec.yaml index 632f5dd354c3..7753c04b0dce 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/pubspec.yaml +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/pubspec.yaml @@ -1,6 +1,6 @@ name: cloud_firestore_platform_interface description: A common platform interface for the cloud_firestore plugin. -version: 7.0.6 +version: 7.0.7 homepage: https://github.com/firebase/flutterfire/tree/main/packages/cloud_firestore/cloud_firestore_platform_interface repository: https://github.com/firebase/flutterfire/tree/main/packages/cloud_firestore/cloud_firestore_platform_interface @@ -9,9 +9,9 @@ environment: flutter: '>=3.3.0' dependencies: - _flutterfire_internals: ^1.3.66 + _flutterfire_internals: ^1.3.67 collection: ^1.15.0 - firebase_core: ^4.4.0 + firebase_core: ^4.5.0 flutter: sdk: flutter meta: ^1.8.0 diff --git a/packages/cloud_firestore/cloud_firestore_web/CHANGELOG.md b/packages/cloud_firestore/cloud_firestore_web/CHANGELOG.md index 2adcf57d5b41..21917752a172 100644 --- a/packages/cloud_firestore/cloud_firestore_web/CHANGELOG.md +++ b/packages/cloud_firestore/cloud_firestore_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.1.3 + + - Update a dependency to the latest release. + ## 5.1.2 - Update a dependency to the latest release. diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/cloud_firestore_version.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/cloud_firestore_version.dart index 5c0a9eb18eef..59c18f82c5ec 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/cloud_firestore_version.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/cloud_firestore_version.dart @@ -13,4 +13,4 @@ // limitations under the License. /// generated version number for the package, do not manually edit -const packageVersion = '6.1.2'; +const packageVersion = '6.1.3'; diff --git a/packages/cloud_firestore/cloud_firestore_web/pubspec.yaml b/packages/cloud_firestore/cloud_firestore_web/pubspec.yaml index 6e1623e063f9..e26d2b243d57 100644 --- a/packages/cloud_firestore/cloud_firestore_web/pubspec.yaml +++ b/packages/cloud_firestore/cloud_firestore_web/pubspec.yaml @@ -3,18 +3,18 @@ description: The web implementation of cloud_firestore homepage: https://github.com/firebase/flutterfire/tree/main/packages/cloud_firestore/cloud_firestore_web repository: https://github.com/firebase/flutterfire/tree/main/packages/cloud_firestore/cloud_firestore_web -version: 5.1.2 +version: 5.1.3 environment: sdk: '>=3.4.0 <4.0.0' flutter: '>=3.22.0' dependencies: - _flutterfire_internals: ^1.3.66 - cloud_firestore_platform_interface: ^7.0.6 + _flutterfire_internals: ^1.3.67 + cloud_firestore_platform_interface: ^7.0.7 collection: ^1.0.0 - firebase_core: ^4.4.0 - firebase_core_web: ^3.4.0 + firebase_core: ^4.5.0 + firebase_core_web: ^3.5.0 flutter: sdk: flutter flutter_web_plugins: diff --git a/packages/cloud_functions/cloud_functions/CHANGELOG.md b/packages/cloud_functions/cloud_functions/CHANGELOG.md index 9468ff7808bc..cbdfce5d99fb 100644 --- a/packages/cloud_functions/cloud_functions/CHANGELOG.md +++ b/packages/cloud_functions/cloud_functions/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.0.7 + + - Update a dependency to the latest release. + ## 6.0.6 - **FIX**(cloud_functions): enhance stream response types for better type safety ([#17938](https://github.com/firebase/flutterfire/issues/17938)). ([b89e5890](https://github.com/firebase/flutterfire/commit/b89e5890dfe7ce725022c9e470ee34ff64eb7a99)) diff --git a/packages/cloud_functions/cloud_functions/example/pubspec.yaml b/packages/cloud_functions/cloud_functions/example/pubspec.yaml index 7f8ba3d5313e..56991739d9f4 100644 --- a/packages/cloud_functions/cloud_functions/example/pubspec.yaml +++ b/packages/cloud_functions/cloud_functions/example/pubspec.yaml @@ -6,8 +6,8 @@ environment: flutter: '>=3.3.0' dependencies: - cloud_functions: ^6.0.6 - firebase_core: ^4.4.0 + cloud_functions: ^6.0.7 + firebase_core: ^4.5.0 flutter: sdk: flutter diff --git a/packages/cloud_functions/cloud_functions/ios/cloud_functions/Sources/cloud_functions/Constants.swift b/packages/cloud_functions/cloud_functions/ios/cloud_functions/Sources/cloud_functions/Constants.swift index 7fc9b631b2a4..e39f14a01c32 100644 --- a/packages/cloud_functions/cloud_functions/ios/cloud_functions/Sources/cloud_functions/Constants.swift +++ b/packages/cloud_functions/cloud_functions/ios/cloud_functions/Sources/cloud_functions/Constants.swift @@ -3,4 +3,4 @@ // found in the LICENSE file. /// Auto-generated file. Do not edit. -public let versionNumber = "6.0.6" +public let versionNumber = "6.0.7" diff --git a/packages/cloud_functions/cloud_functions/ios/generated_firebase_sdk_version.txt b/packages/cloud_functions/cloud_functions/ios/generated_firebase_sdk_version.txt index a54ec1fce4a4..3eb7353bcd3e 100644 --- a/packages/cloud_functions/cloud_functions/ios/generated_firebase_sdk_version.txt +++ b/packages/cloud_functions/cloud_functions/ios/generated_firebase_sdk_version.txt @@ -1 +1 @@ -12.8.0 \ No newline at end of file +12.9.0 \ No newline at end of file diff --git a/packages/cloud_functions/cloud_functions/pubspec.yaml b/packages/cloud_functions/cloud_functions/pubspec.yaml index 937a884d77e7..1fd4bfc19b24 100644 --- a/packages/cloud_functions/cloud_functions/pubspec.yaml +++ b/packages/cloud_functions/cloud_functions/pubspec.yaml @@ -1,6 +1,6 @@ name: cloud_functions description: A Flutter plugin allowing you to use Firebase Cloud Functions. -version: 6.0.6 +version: 6.0.7 homepage: https://firebase.google.com/docs/functions repository: https://github.com/firebase/flutterfire/tree/main/packages/cloud_functions/cloud_functions topics: @@ -17,9 +17,9 @@ environment: flutter: '>=3.3.0' dependencies: - cloud_functions_platform_interface: ^5.8.9 - cloud_functions_web: ^5.1.2 - firebase_core: ^4.4.0 + cloud_functions_platform_interface: ^5.8.10 + cloud_functions_web: ^5.1.3 + firebase_core: ^4.5.0 firebase_core_platform_interface: ^6.0.2 flutter: sdk: flutter diff --git a/packages/cloud_functions/cloud_functions_platform_interface/CHANGELOG.md b/packages/cloud_functions/cloud_functions_platform_interface/CHANGELOG.md index 7fc2450de856..1ef9ffbdce83 100644 --- a/packages/cloud_functions/cloud_functions_platform_interface/CHANGELOG.md +++ b/packages/cloud_functions/cloud_functions_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.8.10 + + - Update a dependency to the latest release. + ## 5.8.9 - Update a dependency to the latest release. diff --git a/packages/cloud_functions/cloud_functions_platform_interface/pubspec.yaml b/packages/cloud_functions/cloud_functions_platform_interface/pubspec.yaml index e5b7e0e91ee6..5075b75e3d0e 100644 --- a/packages/cloud_functions/cloud_functions_platform_interface/pubspec.yaml +++ b/packages/cloud_functions/cloud_functions_platform_interface/pubspec.yaml @@ -5,14 +5,14 @@ repository: https://github.com/firebase/flutterfire/tree/main/packages/cloud_fun # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 5.8.9 +version: 5.8.10 environment: sdk: '>=3.2.0 <4.0.0' flutter: '>=3.3.0' dependencies: - firebase_core: ^4.4.0 + firebase_core: ^4.5.0 flutter: sdk: flutter meta: ^1.8.0 diff --git a/packages/cloud_functions/cloud_functions_web/CHANGELOG.md b/packages/cloud_functions/cloud_functions_web/CHANGELOG.md index 8fa9f0090b77..bd0a93ec763a 100644 --- a/packages/cloud_functions/cloud_functions_web/CHANGELOG.md +++ b/packages/cloud_functions/cloud_functions_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.1.3 + + - Update a dependency to the latest release. + ## 5.1.2 - Update a dependency to the latest release. diff --git a/packages/cloud_functions/cloud_functions_web/lib/src/cloud_functions_version.dart b/packages/cloud_functions/cloud_functions_web/lib/src/cloud_functions_version.dart index f3f8436dafd7..a698ecff621f 100644 --- a/packages/cloud_functions/cloud_functions_web/lib/src/cloud_functions_version.dart +++ b/packages/cloud_functions/cloud_functions_web/lib/src/cloud_functions_version.dart @@ -13,4 +13,4 @@ // limitations under the License. /// generated version number for the package, do not manually edit -const packageVersion = '6.0.6'; +const packageVersion = '6.0.7'; diff --git a/packages/cloud_functions/cloud_functions_web/pubspec.yaml b/packages/cloud_functions/cloud_functions_web/pubspec.yaml index 97b4ffcf8da4..7dd3ecd561fa 100644 --- a/packages/cloud_functions/cloud_functions_web/pubspec.yaml +++ b/packages/cloud_functions/cloud_functions_web/pubspec.yaml @@ -3,16 +3,16 @@ description: The web implementation of cloud_functions homepage: https://github.com/firebase/flutterfire/tree/main/packages/cloud_functions/cloud_functions_web repository: https://github.com/firebase/flutterfire/tree/main/packages/cloud_functions/cloud_functions_web -version: 5.1.2 +version: 5.1.3 environment: sdk: '>=3.4.0 <4.0.0' flutter: '>=3.22.0' dependencies: - cloud_functions_platform_interface: ^5.8.9 - firebase_core: ^4.4.0 - firebase_core_web: ^3.4.0 + cloud_functions_platform_interface: ^5.8.10 + firebase_core: ^4.5.0 + firebase_core_web: ^3.5.0 flutter: sdk: flutter flutter_web_plugins: diff --git a/packages/firebase_ai/firebase_ai/CHANGELOG.md b/packages/firebase_ai/firebase_ai/CHANGELOG.md index 953d937fcd92..71aa4901e610 100644 --- a/packages/firebase_ai/firebase_ai/CHANGELOG.md +++ b/packages/firebase_ai/firebase_ai/CHANGELOG.md @@ -1,3 +1,8 @@ +## 3.9.0 + + - **FIX**: resolve lint issues ([#18017](https://github.com/firebase/flutterfire/issues/18017)). ([e8e85397](https://github.com/firebase/flutterfire/commit/e8e85397ccdcab6c8b84348884b4673f86b79d1c)) + - **FEAT**(firebaseai): update Live API sample to add video support. ([#18018](https://github.com/firebase/flutterfire/issues/18018)). ([f91df750](https://github.com/firebase/flutterfire/commit/f91df7503bc4506c66cbebcfa562d65de1ae0e5b)) + ## 3.8.0 - **FIX**(firebase_ai): Rename `groundingSupport` to `groundingSupports` ([#17961](https://github.com/firebase/flutterfire/issues/17961)). ([cfb90989](https://github.com/firebase/flutterfire/commit/cfb909896d8ae9edc49b10f1def5b64dcc3dfb35)) diff --git a/packages/firebase_ai/firebase_ai/example/pubspec.yaml b/packages/firebase_ai/firebase_ai/example/pubspec.yaml index e65649e5db79..6150596fcb89 100644 --- a/packages/firebase_ai/firebase_ai/example/pubspec.yaml +++ b/packages/firebase_ai/firebase_ai/example/pubspec.yaml @@ -22,9 +22,9 @@ dependencies: camera: ^0.11.2+1 camera_macos: ^0.0.9 cupertino_icons: ^1.0.6 - firebase_ai: ^3.8.0 - firebase_core: ^4.4.0 - firebase_storage: ^13.0.6 + firebase_ai: ^3.9.0 + firebase_core: ^4.5.0 + firebase_storage: ^13.1.0 flutter: sdk: flutter flutter_animate: ^4.5.2 diff --git a/packages/firebase_ai/firebase_ai/lib/src/firebaseai_version.dart b/packages/firebase_ai/firebase_ai/lib/src/firebaseai_version.dart index 800106e2d40f..1fa94cbdf38b 100644 --- a/packages/firebase_ai/firebase_ai/lib/src/firebaseai_version.dart +++ b/packages/firebase_ai/firebase_ai/lib/src/firebaseai_version.dart @@ -13,4 +13,4 @@ // limitations under the License. /// generated version number for the package, do not manually edit -const packageVersion = '0.2.2+2'; +const packageVersion = '0.2.3'; diff --git a/packages/firebase_ai/firebase_ai/pubspec.yaml b/packages/firebase_ai/firebase_ai/pubspec.yaml index 13145cb7ec2d..e545f84b43b2 100644 --- a/packages/firebase_ai/firebase_ai/pubspec.yaml +++ b/packages/firebase_ai/firebase_ai/pubspec.yaml @@ -1,6 +1,6 @@ name: firebase_ai description: Firebase AI Logic SDK. -version: 3.8.0 +version: 3.9.0 homepage: https://firebase.google.com/docs/vertex-ai/get-started?platform=flutter topics: - firebase @@ -20,9 +20,9 @@ environment: flutter: ">=3.16.0" dependencies: - firebase_app_check: ^0.4.1+4 - firebase_auth: ^6.1.4 - firebase_core: ^4.4.0 + firebase_app_check: ^0.4.1+5 + firebase_auth: ^6.2.0 + firebase_core: ^4.5.0 firebase_core_platform_interface: ^6.0.2 flutter: sdk: flutter diff --git a/packages/firebase_analytics/firebase_analytics/CHANGELOG.md b/packages/firebase_analytics/firebase_analytics/CHANGELOG.md index 70938372ff1c..721bd08cfafe 100644 --- a/packages/firebase_analytics/firebase_analytics/CHANGELOG.md +++ b/packages/firebase_analytics/firebase_analytics/CHANGELOG.md @@ -1,3 +1,7 @@ +## 12.1.3 + + - **FIX**(analytics,iOS): Update hashedPhoneNumber handling to use hex string conversion ([#17807](https://github.com/firebase/flutterfire/issues/17807)). ([407c2490](https://github.com/firebase/flutterfire/commit/407c2490602484499d1ab5b2ce6860af00a218c8)) + ## 12.1.2 - **FIX**(firebase_analytics): update logInAppPurchase documentation to specify iOS support only ([#17968](https://github.com/firebase/flutterfire/issues/17968)). ([b3caa545](https://github.com/firebase/flutterfire/commit/b3caa54592d431a1ac1b7007a154cdf739b0e406)) diff --git a/packages/firebase_analytics/firebase_analytics/example/pubspec.yaml b/packages/firebase_analytics/firebase_analytics/example/pubspec.yaml index 85d39997dda7..02a21d62da40 100755 --- a/packages/firebase_analytics/firebase_analytics/example/pubspec.yaml +++ b/packages/firebase_analytics/firebase_analytics/example/pubspec.yaml @@ -6,8 +6,8 @@ environment: flutter: '>=3.3.0' dependencies: - firebase_analytics: ^12.1.2 - firebase_core: ^4.4.0 + firebase_analytics: ^12.1.3 + firebase_core: ^4.5.0 flutter: sdk: flutter diff --git a/packages/firebase_analytics/firebase_analytics/ios/generated_firebase_sdk_version.txt b/packages/firebase_analytics/firebase_analytics/ios/generated_firebase_sdk_version.txt index a54ec1fce4a4..3eb7353bcd3e 100644 --- a/packages/firebase_analytics/firebase_analytics/ios/generated_firebase_sdk_version.txt +++ b/packages/firebase_analytics/firebase_analytics/ios/generated_firebase_sdk_version.txt @@ -1 +1 @@ -12.8.0 \ No newline at end of file +12.9.0 \ No newline at end of file diff --git a/packages/firebase_analytics/firebase_analytics/pubspec.yaml b/packages/firebase_analytics/firebase_analytics/pubspec.yaml index fba973a0cd69..ef1dbd2277d1 100755 --- a/packages/firebase_analytics/firebase_analytics/pubspec.yaml +++ b/packages/firebase_analytics/firebase_analytics/pubspec.yaml @@ -4,7 +4,7 @@ description: solution that provides insight on app usage and user engagement on Android and iOS. homepage: https://firebase.google.com/docs/analytics repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_analytics/firebase_analytics -version: 12.1.2 +version: 12.1.3 topics: - firebase - analytics @@ -19,9 +19,9 @@ environment: flutter: '>=3.3.0' dependencies: - firebase_analytics_platform_interface: ^5.0.6 - firebase_analytics_web: ^0.6.1+2 - firebase_core: ^4.4.0 + firebase_analytics_platform_interface: ^5.0.7 + firebase_analytics_web: ^0.6.1+3 + firebase_core: ^4.5.0 firebase_core_platform_interface: ^6.0.2 flutter: sdk: flutter diff --git a/packages/firebase_analytics/firebase_analytics_platform_interface/CHANGELOG.md b/packages/firebase_analytics/firebase_analytics_platform_interface/CHANGELOG.md index 1942237f8d1a..ad86b4e3fa8c 100644 --- a/packages/firebase_analytics/firebase_analytics_platform_interface/CHANGELOG.md +++ b/packages/firebase_analytics/firebase_analytics_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.0.7 + + - Update a dependency to the latest release. + ## 5.0.6 - Update a dependency to the latest release. diff --git a/packages/firebase_analytics/firebase_analytics_platform_interface/pubspec.yaml b/packages/firebase_analytics/firebase_analytics_platform_interface/pubspec.yaml index 12f2a2117f5c..24fd16722d61 100644 --- a/packages/firebase_analytics/firebase_analytics_platform_interface/pubspec.yaml +++ b/packages/firebase_analytics/firebase_analytics_platform_interface/pubspec.yaml @@ -2,15 +2,15 @@ name: firebase_analytics_platform_interface description: A common platform interface for the firebase_analytics plugin. homepage: https://github.com/firebase/flutterfire/tree/main/packages/firebase_analytics/firebase_analytics_platform_interface repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_analytics/firebase_analytics_platform_interface -version: 5.0.6 +version: 5.0.7 environment: sdk: '>=3.2.0 <4.0.0' flutter: '>=3.3.0' dependencies: - _flutterfire_internals: ^1.3.66 - firebase_core: ^4.4.0 + _flutterfire_internals: ^1.3.67 + firebase_core: ^4.5.0 flutter: sdk: flutter meta: ^1.8.0 diff --git a/packages/firebase_analytics/firebase_analytics_web/CHANGELOG.md b/packages/firebase_analytics/firebase_analytics_web/CHANGELOG.md index 6e835a6c0cbc..ee6be702e5f0 100644 --- a/packages/firebase_analytics/firebase_analytics_web/CHANGELOG.md +++ b/packages/firebase_analytics/firebase_analytics_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.1+3 + + - Update a dependency to the latest release. + ## 0.6.1+2 - Update a dependency to the latest release. diff --git a/packages/firebase_analytics/firebase_analytics_web/lib/src/firebase_analytics_version.dart b/packages/firebase_analytics/firebase_analytics_web/lib/src/firebase_analytics_version.dart index 51e1d43bf8bd..6162ddc5882f 100644 --- a/packages/firebase_analytics/firebase_analytics_web/lib/src/firebase_analytics_version.dart +++ b/packages/firebase_analytics/firebase_analytics_web/lib/src/firebase_analytics_version.dart @@ -13,4 +13,4 @@ // limitations under the License. /// generated version number for the package, do not manually edit -const packageVersion = '12.1.2'; +const packageVersion = '12.1.3'; diff --git a/packages/firebase_analytics/firebase_analytics_web/pubspec.yaml b/packages/firebase_analytics/firebase_analytics_web/pubspec.yaml index 75d2bff5206f..8e04d196c61f 100644 --- a/packages/firebase_analytics/firebase_analytics_web/pubspec.yaml +++ b/packages/firebase_analytics/firebase_analytics_web/pubspec.yaml @@ -2,17 +2,17 @@ name: firebase_analytics_web description: The web implementation of firebase_analytics homepage: https://github.com/firebase/flutterfire/tree/main/packages/firebase_analytics/firebase_analytics_web repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_analytics/firebase_analytics_web -version: 0.6.1+2 +version: 0.6.1+3 environment: sdk: '>=3.4.0 <4.0.0' flutter: '>=3.22.0' dependencies: - _flutterfire_internals: ^1.3.66 - firebase_analytics_platform_interface: ^5.0.6 - firebase_core: ^4.4.0 - firebase_core_web: ^3.4.0 + _flutterfire_internals: ^1.3.67 + firebase_analytics_platform_interface: ^5.0.7 + firebase_core: ^4.5.0 + firebase_core_web: ^3.5.0 flutter: sdk: flutter flutter_web_plugins: diff --git a/packages/firebase_app_check/firebase_app_check/CHANGELOG.md b/packages/firebase_app_check/firebase_app_check/CHANGELOG.md index 21f0c8c9bbc6..97de1656084c 100644 --- a/packages/firebase_app_check/firebase_app_check/CHANGELOG.md +++ b/packages/firebase_app_check/firebase_app_check/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.1+5 + + - Update a dependency to the latest release. + ## 0.4.1+4 - Update a dependency to the latest release. diff --git a/packages/firebase_app_check/firebase_app_check/example/pubspec.yaml b/packages/firebase_app_check/firebase_app_check/example/pubspec.yaml index d320cc700dc5..6362bc129a1c 100644 --- a/packages/firebase_app_check/firebase_app_check/example/pubspec.yaml +++ b/packages/firebase_app_check/firebase_app_check/example/pubspec.yaml @@ -9,9 +9,9 @@ environment: sdk: '>=3.2.0 <4.0.0' dependencies: - cloud_firestore: ^6.1.2 - firebase_app_check: ^0.4.1+4 - firebase_core: ^4.4.0 + cloud_firestore: ^6.1.3 + firebase_app_check: ^0.4.1+5 + firebase_core: ^4.5.0 flutter: sdk: flutter diff --git a/packages/firebase_app_check/firebase_app_check/ios/generated_firebase_sdk_version.txt b/packages/firebase_app_check/firebase_app_check/ios/generated_firebase_sdk_version.txt index a54ec1fce4a4..3eb7353bcd3e 100644 --- a/packages/firebase_app_check/firebase_app_check/ios/generated_firebase_sdk_version.txt +++ b/packages/firebase_app_check/firebase_app_check/ios/generated_firebase_sdk_version.txt @@ -1 +1 @@ -12.8.0 \ No newline at end of file +12.9.0 \ No newline at end of file diff --git a/packages/firebase_app_check/firebase_app_check/pubspec.yaml b/packages/firebase_app_check/firebase_app_check/pubspec.yaml index 59d7ad260051..41bad99db192 100644 --- a/packages/firebase_app_check/firebase_app_check/pubspec.yaml +++ b/packages/firebase_app_check/firebase_app_check/pubspec.yaml @@ -2,7 +2,7 @@ name: firebase_app_check description: App Check works alongside other Firebase services to help protect your backend resources from abuse, such as billing fraud or phishing. homepage: https://firebase.google.com/docs/app-check repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_app_check/firebase_app_check -version: 0.4.1+4 +version: 0.4.1+5 topics: - firebase - app-check @@ -17,9 +17,9 @@ environment: flutter: '>=3.3.0' dependencies: - firebase_app_check_platform_interface: ^0.2.1+4 - firebase_app_check_web: ^0.2.2+2 - firebase_core: ^4.4.0 + firebase_app_check_platform_interface: ^0.2.1+5 + firebase_app_check_web: ^0.2.2+3 + firebase_core: ^4.5.0 firebase_core_platform_interface: ^6.0.2 flutter: sdk: flutter diff --git a/packages/firebase_app_check/firebase_app_check_platform_interface/CHANGELOG.md b/packages/firebase_app_check/firebase_app_check_platform_interface/CHANGELOG.md index 5a0d6664d005..f13b7d53885c 100644 --- a/packages/firebase_app_check/firebase_app_check_platform_interface/CHANGELOG.md +++ b/packages/firebase_app_check/firebase_app_check_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.1+5 + + - Update a dependency to the latest release. + ## 0.2.1+4 - Update a dependency to the latest release. diff --git a/packages/firebase_app_check/firebase_app_check_platform_interface/pubspec.yaml b/packages/firebase_app_check/firebase_app_check_platform_interface/pubspec.yaml index fb944c5fb2e9..fd1ef215d65d 100644 --- a/packages/firebase_app_check/firebase_app_check_platform_interface/pubspec.yaml +++ b/packages/firebase_app_check/firebase_app_check_platform_interface/pubspec.yaml @@ -1,15 +1,15 @@ name: firebase_app_check_platform_interface description: A common platform interface for the firebase_app_check plugin. homepage: https://github.com/firebase/flutterfire/tree/main/packages/firebase_app_check/firebase_app_check_platform_interface -version: 0.2.1+4 +version: 0.2.1+5 environment: sdk: '>=3.2.0 <4.0.0' flutter: '>=3.3.0' dependencies: - _flutterfire_internals: ^1.3.66 - firebase_core: ^4.4.0 + _flutterfire_internals: ^1.3.67 + firebase_core: ^4.5.0 flutter: sdk: flutter meta: ^1.8.0 diff --git a/packages/firebase_app_check/firebase_app_check_web/CHANGELOG.md b/packages/firebase_app_check/firebase_app_check_web/CHANGELOG.md index 9dd60b35623f..ddb611320113 100644 --- a/packages/firebase_app_check/firebase_app_check_web/CHANGELOG.md +++ b/packages/firebase_app_check/firebase_app_check_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.2+3 + + - Update a dependency to the latest release. + ## 0.2.2+2 - Update a dependency to the latest release. diff --git a/packages/firebase_app_check/firebase_app_check_web/lib/src/firebase_app_check_version.dart b/packages/firebase_app_check/firebase_app_check_web/lib/src/firebase_app_check_version.dart index 70f0585c6815..1e16bda3acdf 100644 --- a/packages/firebase_app_check/firebase_app_check_web/lib/src/firebase_app_check_version.dart +++ b/packages/firebase_app_check/firebase_app_check_web/lib/src/firebase_app_check_version.dart @@ -13,4 +13,4 @@ // limitations under the License. /// generated version number for the package, do not manually edit -const packageVersion = '0.4.1+4'; +const packageVersion = '0.4.1+5'; diff --git a/packages/firebase_app_check/firebase_app_check_web/pubspec.yaml b/packages/firebase_app_check/firebase_app_check_web/pubspec.yaml index 1d81257e93b3..ab8e2cc2c50a 100644 --- a/packages/firebase_app_check/firebase_app_check_web/pubspec.yaml +++ b/packages/firebase_app_check/firebase_app_check_web/pubspec.yaml @@ -1,17 +1,17 @@ name: firebase_app_check_web description: The web implementation of firebase_app_check homepage: https://github.com/firebase/flutterfire/tree/main/packages/firebase_app_check/firebase_app_check_web -version: 0.2.2+2 +version: 0.2.2+3 environment: sdk: '>=3.4.0 <4.0.0' flutter: '>=3.22.0' dependencies: - _flutterfire_internals: ^1.3.66 - firebase_app_check_platform_interface: ^0.2.1+4 - firebase_core: ^4.4.0 - firebase_core_web: ^3.4.0 + _flutterfire_internals: ^1.3.67 + firebase_app_check_platform_interface: ^0.2.1+5 + firebase_core: ^4.5.0 + firebase_core_web: ^3.5.0 flutter: sdk: flutter flutter_web_plugins: diff --git a/packages/firebase_app_installations/firebase_app_installations/CHANGELOG.md b/packages/firebase_app_installations/firebase_app_installations/CHANGELOG.md index 03ce1ca89db8..f978f0c8bd1d 100644 --- a/packages/firebase_app_installations/firebase_app_installations/CHANGELOG.md +++ b/packages/firebase_app_installations/firebase_app_installations/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.0+7 + + - Update a dependency to the latest release. + ## 0.4.0+6 - Update a dependency to the latest release. diff --git a/packages/firebase_app_installations/firebase_app_installations/example/pubspec.yaml b/packages/firebase_app_installations/firebase_app_installations/example/pubspec.yaml index 9a46153e57fb..90f31154854b 100644 --- a/packages/firebase_app_installations/firebase_app_installations/example/pubspec.yaml +++ b/packages/firebase_app_installations/firebase_app_installations/example/pubspec.yaml @@ -9,8 +9,8 @@ environment: sdk: '>=3.2.0 <4.0.0' dependencies: - firebase_core: ^4.4.0 - firebase_app_installations: ^0.4.0+6 + firebase_core: ^4.5.0 + firebase_app_installations: ^0.4.0+7 flutter: sdk: flutter diff --git a/packages/firebase_app_installations/firebase_app_installations/ios/firebase_app_installations/Sources/firebase_app_installations/Constants.swift b/packages/firebase_app_installations/firebase_app_installations/ios/firebase_app_installations/Sources/firebase_app_installations/Constants.swift index bbd958ad5897..6d856d7c77e6 100644 --- a/packages/firebase_app_installations/firebase_app_installations/ios/firebase_app_installations/Sources/firebase_app_installations/Constants.swift +++ b/packages/firebase_app_installations/firebase_app_installations/ios/firebase_app_installations/Sources/firebase_app_installations/Constants.swift @@ -3,4 +3,4 @@ // found in the LICENSE file. /// Auto-generated file. Do not edit. -public let versionNumber = "0.4.0+6" +public let versionNumber = "0.4.0+7" diff --git a/packages/firebase_app_installations/firebase_app_installations/ios/generated_firebase_sdk_version.txt b/packages/firebase_app_installations/firebase_app_installations/ios/generated_firebase_sdk_version.txt index a54ec1fce4a4..3eb7353bcd3e 100644 --- a/packages/firebase_app_installations/firebase_app_installations/ios/generated_firebase_sdk_version.txt +++ b/packages/firebase_app_installations/firebase_app_installations/ios/generated_firebase_sdk_version.txt @@ -1 +1 @@ -12.8.0 \ No newline at end of file +12.9.0 \ No newline at end of file diff --git a/packages/firebase_app_installations/firebase_app_installations/pubspec.yaml b/packages/firebase_app_installations/firebase_app_installations/pubspec.yaml index 583ad6606bbe..8a5fed240709 100644 --- a/packages/firebase_app_installations/firebase_app_installations/pubspec.yaml +++ b/packages/firebase_app_installations/firebase_app_installations/pubspec.yaml @@ -1,6 +1,6 @@ name: firebase_app_installations description: A Flutter plugin allowing you to use Firebase Installations. -version: 0.4.0+6 +version: 0.4.0+7 homepage: https://firebase.google.com/docs/projects/manage-installations#flutter repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_app_installations/firebase_app_installations topics: @@ -17,9 +17,9 @@ environment: flutter: '>=3.3.0' dependencies: - firebase_app_installations_platform_interface: ^0.1.4+65 - firebase_app_installations_web: ^0.1.7+2 - firebase_core: ^4.4.0 + firebase_app_installations_platform_interface: ^0.1.4+66 + firebase_app_installations_web: ^0.1.7+3 + firebase_core: ^4.5.0 firebase_core_platform_interface: ^6.0.2 flutter: sdk: flutter diff --git a/packages/firebase_app_installations/firebase_app_installations_platform_interface/CHANGELOG.md b/packages/firebase_app_installations/firebase_app_installations_platform_interface/CHANGELOG.md index e7a47ed3ca13..870f130fd5db 100644 --- a/packages/firebase_app_installations/firebase_app_installations_platform_interface/CHANGELOG.md +++ b/packages/firebase_app_installations/firebase_app_installations_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.4+66 + + - Update a dependency to the latest release. + ## 0.1.4+65 - Update a dependency to the latest release. diff --git a/packages/firebase_app_installations/firebase_app_installations_platform_interface/pubspec.yaml b/packages/firebase_app_installations/firebase_app_installations_platform_interface/pubspec.yaml index 3be0580b713b..568ee1323bc4 100644 --- a/packages/firebase_app_installations/firebase_app_installations_platform_interface/pubspec.yaml +++ b/packages/firebase_app_installations/firebase_app_installations_platform_interface/pubspec.yaml @@ -1,6 +1,6 @@ name: firebase_app_installations_platform_interface description: A common platform interface for the firebase_app_installations plugin. -version: 0.1.4+65 +version: 0.1.4+66 homepage: https://github.com/firebase/flutterfire/tree/main/packages/firebase_app_installations/firebase_app_installations_platform_interface repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_app_installations/firebase_app_installations_platform_interface @@ -9,8 +9,8 @@ environment: flutter: '>=3.3.0' dependencies: - _flutterfire_internals: ^1.3.66 - firebase_core: ^4.4.0 + _flutterfire_internals: ^1.3.67 + firebase_core: ^4.5.0 flutter: sdk: flutter meta: ^1.8.0 diff --git a/packages/firebase_app_installations/firebase_app_installations_web/CHANGELOG.md b/packages/firebase_app_installations/firebase_app_installations_web/CHANGELOG.md index 072d593b01c3..0f9b5525a432 100644 --- a/packages/firebase_app_installations/firebase_app_installations_web/CHANGELOG.md +++ b/packages/firebase_app_installations/firebase_app_installations_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.7+3 + + - Update a dependency to the latest release. + ## 0.1.7+2 - Update a dependency to the latest release. diff --git a/packages/firebase_app_installations/firebase_app_installations_web/lib/src/firebase_app_installations_version.dart b/packages/firebase_app_installations/firebase_app_installations_web/lib/src/firebase_app_installations_version.dart index 70a2f9ad6b25..4995f1941d53 100644 --- a/packages/firebase_app_installations/firebase_app_installations_web/lib/src/firebase_app_installations_version.dart +++ b/packages/firebase_app_installations/firebase_app_installations_web/lib/src/firebase_app_installations_version.dart @@ -13,4 +13,4 @@ // limitations under the License. /// generated version number for the package, do not manually edit -const packageVersion = '0.4.0+6'; +const packageVersion = '0.4.0+7'; diff --git a/packages/firebase_app_installations/firebase_app_installations_web/pubspec.yaml b/packages/firebase_app_installations/firebase_app_installations_web/pubspec.yaml index aa616877d08a..ceab46ce6456 100644 --- a/packages/firebase_app_installations/firebase_app_installations_web/pubspec.yaml +++ b/packages/firebase_app_installations/firebase_app_installations_web/pubspec.yaml @@ -1,6 +1,6 @@ name: firebase_app_installations_web description: The web implementation of firebase_app_installations. -version: 0.1.7+2 +version: 0.1.7+3 homepage: https://github.com/firebase/flutterfire/tree/main/packages/firebase_app_installations/firebase_app_installations_web repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_app_installations/firebase_app_installations_web @@ -9,10 +9,10 @@ environment: flutter: '>=3.22.0' dependencies: - _flutterfire_internals: ^1.3.66 - firebase_app_installations_platform_interface: ^0.1.4+65 - firebase_core: ^4.4.0 - firebase_core_web: ^3.4.0 + _flutterfire_internals: ^1.3.67 + firebase_app_installations_platform_interface: ^0.1.4+66 + firebase_core: ^4.5.0 + firebase_core_web: ^3.5.0 flutter: sdk: flutter flutter_web_plugins: diff --git a/packages/firebase_auth/firebase_auth/CHANGELOG.md b/packages/firebase_auth/firebase_auth/CHANGELOG.md index fe3401976991..872fc395b59c 100644 --- a/packages/firebase_auth/firebase_auth/CHANGELOG.md +++ b/packages/firebase_auth/firebase_auth/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.2.0 + + - **FEAT**(remote-config,windows): add support for windows ([#18006](https://github.com/firebase/flutterfire/issues/18006)). ([a6ec167f](https://github.com/firebase/flutterfire/commit/a6ec167f4ece9c9b455a916366781f482cc380b3)) + ## 6.1.4 - Update a dependency to the latest release. diff --git a/packages/firebase_auth/firebase_auth/example/pubspec.yaml b/packages/firebase_auth/firebase_auth/example/pubspec.yaml index c894e45410e2..9e30d983d8c5 100644 --- a/packages/firebase_auth/firebase_auth/example/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth/example/pubspec.yaml @@ -6,9 +6,9 @@ environment: dependencies: barcode_widget: ^2.0.4 - firebase_auth: ^6.1.4 - firebase_core: ^4.4.0 - firebase_messaging: ^16.1.1 + firebase_auth: ^6.2.0 + firebase_core: ^4.5.0 + firebase_messaging: ^16.1.2 flutter: sdk: flutter flutter_facebook_auth: ^7.0.1 diff --git a/packages/firebase_auth/firebase_auth/ios/generated_firebase_sdk_version.txt b/packages/firebase_auth/firebase_auth/ios/generated_firebase_sdk_version.txt index a54ec1fce4a4..3eb7353bcd3e 100644 --- a/packages/firebase_auth/firebase_auth/ios/generated_firebase_sdk_version.txt +++ b/packages/firebase_auth/firebase_auth/ios/generated_firebase_sdk_version.txt @@ -1 +1 @@ -12.8.0 \ No newline at end of file +12.9.0 \ No newline at end of file diff --git a/packages/firebase_auth/firebase_auth/pubspec.yaml b/packages/firebase_auth/firebase_auth/pubspec.yaml index af71825278dd..960bb0260d0a 100755 --- a/packages/firebase_auth/firebase_auth/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth/pubspec.yaml @@ -4,7 +4,7 @@ description: Flutter plugin for Firebase Auth, enabling like Google, Facebook and Twitter. homepage: https://firebase.google.com/docs/auth repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_auth/firebase_auth -version: 6.1.4 +version: 6.2.0 topics: - firebase - authentication @@ -20,9 +20,9 @@ environment: flutter: '>=3.16.0' dependencies: - firebase_auth_platform_interface: ^8.1.6 - firebase_auth_web: ^6.1.2 - firebase_core: ^4.4.0 + firebase_auth_platform_interface: ^8.1.7 + firebase_auth_web: ^6.1.3 + firebase_core: ^4.5.0 firebase_core_platform_interface: ^6.0.2 flutter: sdk: flutter diff --git a/packages/firebase_auth/firebase_auth_platform_interface/CHANGELOG.md b/packages/firebase_auth/firebase_auth_platform_interface/CHANGELOG.md index 752214a4a9e9..a1a1c74b1d54 100644 --- a/packages/firebase_auth/firebase_auth_platform_interface/CHANGELOG.md +++ b/packages/firebase_auth/firebase_auth_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 8.1.7 + + - Update a dependency to the latest release. + ## 8.1.6 - Update a dependency to the latest release. diff --git a/packages/firebase_auth/firebase_auth_platform_interface/pubspec.yaml b/packages/firebase_auth/firebase_auth_platform_interface/pubspec.yaml index 656b8e2061d2..d747cd4dfccc 100644 --- a/packages/firebase_auth/firebase_auth_platform_interface/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_platform_interface/pubspec.yaml @@ -4,16 +4,16 @@ homepage: https://github.com/firebase/flutterfire/tree/main/packages/firebase_au repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_auth/firebase_auth_platform_interface # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 8.1.6 +version: 8.1.7 environment: sdk: '>=3.2.0 <4.0.0' flutter: '>=3.16.0' dependencies: - _flutterfire_internals: ^1.3.66 + _flutterfire_internals: ^1.3.67 collection: ^1.16.0 - firebase_core: ^4.4.0 + firebase_core: ^4.5.0 flutter: sdk: flutter http: ^1.1.0 diff --git a/packages/firebase_auth/firebase_auth_web/CHANGELOG.md b/packages/firebase_auth/firebase_auth_web/CHANGELOG.md index 90e7cc3f3c2b..34ff02fc17d3 100644 --- a/packages/firebase_auth/firebase_auth_web/CHANGELOG.md +++ b/packages/firebase_auth/firebase_auth_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.1.3 + + - Update a dependency to the latest release. + ## 6.1.2 - Update a dependency to the latest release. diff --git a/packages/firebase_auth/firebase_auth_web/lib/src/firebase_auth_version.dart b/packages/firebase_auth/firebase_auth_web/lib/src/firebase_auth_version.dart index 9623ca56f4d7..305156baf450 100644 --- a/packages/firebase_auth/firebase_auth_web/lib/src/firebase_auth_version.dart +++ b/packages/firebase_auth/firebase_auth_web/lib/src/firebase_auth_version.dart @@ -13,4 +13,4 @@ // limitations under the License. /// generated version number for the package, do not manually edit -const packageVersion = '6.1.4'; +const packageVersion = '6.2.0'; diff --git a/packages/firebase_auth/firebase_auth_web/pubspec.yaml b/packages/firebase_auth/firebase_auth_web/pubspec.yaml index 5c61b6f1da6c..07b25884a54b 100644 --- a/packages/firebase_auth/firebase_auth_web/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_web/pubspec.yaml @@ -2,16 +2,16 @@ name: firebase_auth_web description: The web implementation of firebase_auth homepage: https://github.com/firebase/flutterfire/tree/main/packages/firebase_auth/firebase_auth_web repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_auth/firebase_auth_web -version: 6.1.2 +version: 6.1.3 environment: sdk: '>=3.4.0 <4.0.0' flutter: '>=3.22.0' dependencies: - firebase_auth_platform_interface: ^8.1.6 - firebase_core: ^4.4.0 - firebase_core_web: ^3.4.0 + firebase_auth_platform_interface: ^8.1.7 + firebase_core: ^4.5.0 + firebase_core_web: ^3.5.0 flutter: sdk: flutter flutter_web_plugins: diff --git a/packages/firebase_core/firebase_core/CHANGELOG.md b/packages/firebase_core/firebase_core/CHANGELOG.md index 0b4c3f34e47f..fbba8a7b29a8 100644 --- a/packages/firebase_core/firebase_core/CHANGELOG.md +++ b/packages/firebase_core/firebase_core/CHANGELOG.md @@ -1,3 +1,11 @@ +## 4.5.0 + + - **FEAT**(core,windows): update C++ Desktop SDK to 13.4.0. This may require updating your Visual Studio version and C++ build tools. ([#18006](https://github.com/firebase/flutterfire/issues/18006)). ([a6ec167f](https://github.com/firebase/flutterfire/commit/a6ec167f4ece9c9b455a916366781f482cc380b3)) + - **FIX**: resolve lint issues ([#18017](https://github.com/firebase/flutterfire/issues/18017)). ([e8e85397](https://github.com/firebase/flutterfire/commit/e8e85397ccdcab6c8b84348884b4673f86b79d1c)) + - **FEAT**: bump Firebase iOS SDK to 12.9.0 ([#18034](https://github.com/firebase/flutterfire/issues/18034)). ([c45894e2](https://github.com/firebase/flutterfire/commit/c45894e23895f9add8c152d13324920babe9b708)) + - **FEAT**: bump Firebase android SDK to 34.9.0 ([#18016](https://github.com/firebase/flutterfire/issues/18016)). ([b218dbff](https://github.com/firebase/flutterfire/commit/b218dbffd72d0bf666ff94f79a3de1e24d038df0)) + - **FEAT**(remote-config,windows): add support for windows ([#18006](https://github.com/firebase/flutterfire/issues/18006)). ([a6ec167f](https://github.com/firebase/flutterfire/commit/a6ec167f4ece9c9b455a916366781f482cc380b3)) + ## 4.4.0 - **FEAT**: bump Firebase iOS SDK to 12.8.0 ([#17947](https://github.com/firebase/flutterfire/issues/17947)). ([4eb249ec](https://github.com/firebase/flutterfire/commit/4eb249ec5d870a960d3834e40fd0f3c3b871430c)) diff --git a/packages/firebase_core/firebase_core/example/pubspec.yaml b/packages/firebase_core/firebase_core/example/pubspec.yaml index d898a878519e..c1495f17a811 100644 --- a/packages/firebase_core/firebase_core/example/pubspec.yaml +++ b/packages/firebase_core/firebase_core/example/pubspec.yaml @@ -5,7 +5,7 @@ environment: sdk: '>=3.2.0 <4.0.0' dependencies: - firebase_core: ^4.4.0 + firebase_core: ^4.5.0 flutter: sdk: flutter diff --git a/packages/firebase_core/firebase_core/pubspec.yaml b/packages/firebase_core/firebase_core/pubspec.yaml index 89f700a80ce6..30f18802c1dc 100644 --- a/packages/firebase_core/firebase_core/pubspec.yaml +++ b/packages/firebase_core/firebase_core/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for Firebase Core, enabling connecting to multiple Firebase apps. homepage: https://firebase.google.com/docs/flutter/setup repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_core/firebase_core -version: 4.4.0 +version: 4.5.0 topics: - firebase - core @@ -17,7 +17,7 @@ environment: dependencies: firebase_core_platform_interface: ^6.0.2 - firebase_core_web: ^3.4.0 + firebase_core_web: ^3.5.0 flutter: sdk: flutter meta: ^1.8.0 diff --git a/packages/firebase_core/firebase_core_web/CHANGELOG.md b/packages/firebase_core/firebase_core_web/CHANGELOG.md index 5a1d2761e7ae..4f0c85a42b77 100644 --- a/packages/firebase_core/firebase_core_web/CHANGELOG.md +++ b/packages/firebase_core/firebase_core_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.5.0 + + - **FEAT**: bump Firebase JS SDK to 12.9.0 ([#18043](https://github.com/firebase/flutterfire/issues/18043)). ([1b29c4d4](https://github.com/firebase/flutterfire/commit/1b29c4d432597d12e08990825647f0ac9467a8f3)) + ## 3.4.0 - **FIX**(firebase_core,web): return empty list from apps getter in WASM mode ([#17919](https://github.com/firebase/flutterfire/issues/17919)). ([0eea9f81](https://github.com/firebase/flutterfire/commit/0eea9f814e7f8bace50e8c1e5973c231cf9a4e3a)) diff --git a/packages/firebase_core/firebase_core_web/lib/src/firebase_core_version.dart b/packages/firebase_core/firebase_core_web/lib/src/firebase_core_version.dart index c7f26e8f49c6..ab81ec074242 100644 --- a/packages/firebase_core/firebase_core_web/lib/src/firebase_core_version.dart +++ b/packages/firebase_core/firebase_core_web/lib/src/firebase_core_version.dart @@ -13,4 +13,4 @@ // limitations under the License. /// generated version number for the package, do not manually edit -const packageVersion = '4.4.0'; +const packageVersion = '4.5.0'; diff --git a/packages/firebase_core/firebase_core_web/pubspec.yaml b/packages/firebase_core/firebase_core_web/pubspec.yaml index 147a38d9d9f4..e91ee92d82f1 100644 --- a/packages/firebase_core/firebase_core_web/pubspec.yaml +++ b/packages/firebase_core/firebase_core_web/pubspec.yaml @@ -2,7 +2,7 @@ name: firebase_core_web description: The web implementation of firebase_core homepage: https://github.com/firebase/flutterfire/tree/main/packages/firebase_core/firebase_core_web repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_core/firebase_core_web -version: 3.4.0 +version: 3.5.0 environment: sdk: '>=3.4.0 <4.0.0' diff --git a/packages/firebase_crashlytics/firebase_crashlytics/CHANGELOG.md b/packages/firebase_crashlytics/firebase_crashlytics/CHANGELOG.md index e125985721f3..00c1620d1b3d 100644 --- a/packages/firebase_crashlytics/firebase_crashlytics/CHANGELOG.md +++ b/packages/firebase_crashlytics/firebase_crashlytics/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.0.8 + + - Update a dependency to the latest release. + ## 5.0.7 - Update a dependency to the latest release. diff --git a/packages/firebase_crashlytics/firebase_crashlytics/example/pubspec.yaml b/packages/firebase_crashlytics/firebase_crashlytics/example/pubspec.yaml index 6ca0411eb57a..7702022198d1 100644 --- a/packages/firebase_crashlytics/firebase_crashlytics/example/pubspec.yaml +++ b/packages/firebase_crashlytics/firebase_crashlytics/example/pubspec.yaml @@ -6,9 +6,9 @@ environment: flutter: '>=3.3.0' dependencies: - firebase_analytics: ^12.1.2 - firebase_core: ^4.4.0 - firebase_crashlytics: ^5.0.7 + firebase_analytics: ^12.1.3 + firebase_core: ^4.5.0 + firebase_crashlytics: ^5.0.8 flutter: sdk: flutter diff --git a/packages/firebase_crashlytics/firebase_crashlytics/ios/generated_firebase_sdk_version.txt b/packages/firebase_crashlytics/firebase_crashlytics/ios/generated_firebase_sdk_version.txt index a54ec1fce4a4..3eb7353bcd3e 100644 --- a/packages/firebase_crashlytics/firebase_crashlytics/ios/generated_firebase_sdk_version.txt +++ b/packages/firebase_crashlytics/firebase_crashlytics/ios/generated_firebase_sdk_version.txt @@ -1 +1 @@ -12.8.0 \ No newline at end of file +12.9.0 \ No newline at end of file diff --git a/packages/firebase_crashlytics/firebase_crashlytics/pubspec.yaml b/packages/firebase_crashlytics/firebase_crashlytics/pubspec.yaml index 83df717b3776..beb36c017df5 100644 --- a/packages/firebase_crashlytics/firebase_crashlytics/pubspec.yaml +++ b/packages/firebase_crashlytics/firebase_crashlytics/pubspec.yaml @@ -2,7 +2,7 @@ name: firebase_crashlytics description: Flutter plugin for Firebase Crashlytics. It reports uncaught errors to the Firebase console. -version: 5.0.7 +version: 5.0.8 homepage: https://firebase.google.com/docs/crashlytics repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_crashlytics/firebase_crashlytics topics: @@ -19,9 +19,9 @@ environment: flutter: '>=3.3.0' dependencies: - firebase_core: ^4.4.0 + firebase_core: ^4.5.0 firebase_core_platform_interface: ^6.0.2 - firebase_crashlytics_platform_interface: ^3.8.17 + firebase_crashlytics_platform_interface: ^3.8.18 flutter: sdk: flutter stack_trace: ^1.10.0 diff --git a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/CHANGELOG.md b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/CHANGELOG.md index 8c0910d5acb7..b2aa9a7d2c6a 100644 --- a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/CHANGELOG.md +++ b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.8.18 + + - Update a dependency to the latest release. + ## 3.8.17 - Update a dependency to the latest release. diff --git a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/pubspec.yaml b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/pubspec.yaml index c2d14b66ac80..ba32b6af7f28 100644 --- a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/pubspec.yaml +++ b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/pubspec.yaml @@ -1,6 +1,6 @@ name: firebase_crashlytics_platform_interface description: A common platform interface for the firebase_crashlytics plugin. -version: 3.8.17 +version: 3.8.18 homepage: https://github.com/firebase/flutterfire/tree/main/packages/firebase_crashlytics/firebase_crashlytics_platform_interface repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_crashlytics/firebase_crashlytics_platform_interface @@ -9,9 +9,9 @@ environment: flutter: '>=3.3.0' dependencies: - _flutterfire_internals: ^1.3.66 + _flutterfire_internals: ^1.3.67 collection: ^1.15.0 - firebase_core: ^4.4.0 + firebase_core: ^4.5.0 flutter: sdk: flutter meta: ^1.8.0 diff --git a/packages/firebase_data_connect/firebase_data_connect/CHANGELOG.md b/packages/firebase_data_connect/firebase_data_connect/CHANGELOG.md index fd908f0b3c95..6b2db45fb457 100644 --- a/packages/firebase_data_connect/firebase_data_connect/CHANGELOG.md +++ b/packages/firebase_data_connect/firebase_data_connect/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.2.3 + + - **REFACTOR**(fdc): Support for entityId path extensions and hardening ([#17988](https://github.com/firebase/flutterfire/issues/17988)). ([fed585f5](https://github.com/firebase/flutterfire/commit/fed585f5a9b65d683cefdc7fa97ed2692e4ec817)) + - **FIX**: resolve lint issues ([#18017](https://github.com/firebase/flutterfire/issues/18017)). ([e8e85397](https://github.com/firebase/flutterfire/commit/e8e85397ccdcab6c8b84348884b4673f86b79d1c)) + - **FEAT**(fdc): Data Connect client sdk caching ([#17890](https://github.com/firebase/flutterfire/issues/17890)). ([02a019bc](https://github.com/firebase/flutterfire/commit/02a019bc25bb4a49d62c1079ed15e0c3aec8a5ec)) + ## 0.2.2+2 - Update a dependency to the latest release. diff --git a/packages/firebase_data_connect/firebase_data_connect/example/pubspec.yaml b/packages/firebase_data_connect/firebase_data_connect/example/pubspec.yaml index bb84d06a7c5a..dc796e7ec424 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/pubspec.yaml +++ b/packages/firebase_data_connect/firebase_data_connect/example/pubspec.yaml @@ -11,16 +11,16 @@ environment: dependencies: flutter: sdk: flutter - firebase_core: ^4.4.0 + firebase_core: ^4.5.0 google_sign_in: ^6.1.0 - firebase_auth: ^6.1.4 + firebase_auth: ^6.2.0 firebase_data_connect: path: ../ cupertino_icons: ^1.0.6 flutter_rating_bar: ^4.0.1 protobuf: ^3.1.0 - firebase_app_check: ^0.4.1+4 + firebase_app_check: ^0.4.1+5 dev_dependencies: build_runner: ^2.3.3 diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/dataconnect_version.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/dataconnect_version.dart index 436cdea88e95..8220eb06de2d 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/dataconnect_version.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/dataconnect_version.dart @@ -13,4 +13,4 @@ // limitations under the License. /// version number for the package, should be align with pubspec.yaml. -const packageVersion = '0.2.2+2'; +const packageVersion = '0.2.3'; diff --git a/packages/firebase_data_connect/firebase_data_connect/pubspec.yaml b/packages/firebase_data_connect/firebase_data_connect/pubspec.yaml index 971425f165dc..014fa8b79b3d 100644 --- a/packages/firebase_data_connect/firebase_data_connect/pubspec.yaml +++ b/packages/firebase_data_connect/firebase_data_connect/pubspec.yaml @@ -1,6 +1,6 @@ name: firebase_data_connect description: 'Flutter plugin for Firebase Data Connect, a relational database service that lets you build and scale using a fully-managed PostgreSQL database powered by Cloud SQL.' -version: 0.2.2+2 +version: 0.2.3 homepage: https://firebase.google.com/docs/data-connect/quickstart?platform=flutter false_secrets: - example/** @@ -12,9 +12,9 @@ environment: dependencies: crypto: ^3.0.6 - firebase_app_check: ^0.4.1+3 - firebase_auth: ^6.1.3 - firebase_core: ^4.3.0 + firebase_app_check: ^0.4.1+5 + firebase_auth: ^6.2.0 + firebase_core: ^4.5.0 firebase_core_platform_interface: ^6.0.2 fixnum: ^1.1.1 flutter: @@ -30,8 +30,8 @@ dependencies: dev_dependencies: build_runner: ^2.4.12 - firebase_app_check_platform_interface: ^0.2.1+4 - firebase_auth_platform_interface: ^8.1.6 + firebase_app_check_platform_interface: ^0.2.1+5 + firebase_auth_platform_interface: ^8.1.7 flutter_lints: ^4.0.0 flutter_test: sdk: flutter diff --git a/packages/firebase_database/firebase_database/CHANGELOG.md b/packages/firebase_database/firebase_database/CHANGELOG.md index af7fde6f7ffc..95074c6e12ef 100644 --- a/packages/firebase_database/firebase_database/CHANGELOG.md +++ b/packages/firebase_database/firebase_database/CHANGELOG.md @@ -1,3 +1,7 @@ +## 12.1.4 + + - Update a dependency to the latest release. + ## 12.1.3 - **FIX**(firebase_database): Add modifiers to keepSynced ref in android ([#17978](https://github.com/firebase/flutterfire/issues/17978)). ([8b1e05f6](https://github.com/firebase/flutterfire/commit/8b1e05f69544f22eaac568ea217cdce1299ded47)) diff --git a/packages/firebase_database/firebase_database/example/pubspec.yaml b/packages/firebase_database/firebase_database/example/pubspec.yaml index 0e8629e2c835..a2bb075ff7fd 100755 --- a/packages/firebase_database/firebase_database/example/pubspec.yaml +++ b/packages/firebase_database/firebase_database/example/pubspec.yaml @@ -6,8 +6,8 @@ environment: flutter: '>=3.3.0' dependencies: - firebase_core: ^4.4.0 - firebase_database: ^12.1.3 + firebase_core: ^4.5.0 + firebase_database: ^12.1.4 flutter: sdk: flutter diff --git a/packages/firebase_database/firebase_database/ios/generated_firebase_sdk_version.txt b/packages/firebase_database/firebase_database/ios/generated_firebase_sdk_version.txt index a54ec1fce4a4..3eb7353bcd3e 100644 --- a/packages/firebase_database/firebase_database/ios/generated_firebase_sdk_version.txt +++ b/packages/firebase_database/firebase_database/ios/generated_firebase_sdk_version.txt @@ -1 +1 @@ -12.8.0 \ No newline at end of file +12.9.0 \ No newline at end of file diff --git a/packages/firebase_database/firebase_database/pubspec.yaml b/packages/firebase_database/firebase_database/pubspec.yaml index 735010227d2f..708d05cfe5ac 100755 --- a/packages/firebase_database/firebase_database/pubspec.yaml +++ b/packages/firebase_database/firebase_database/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for Firebase Database, a cloud-hosted NoSQL database with realtime data syncing across Android and iOS clients, and offline access. homepage: https://firebase.google.com/docs/database repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_database/firebase_database -version: 12.1.3 +version: 12.1.4 topics: - firebase - database @@ -17,10 +17,10 @@ environment: flutter: '>=3.3.0' dependencies: - firebase_core: ^4.4.0 + firebase_core: ^4.5.0 firebase_core_platform_interface: ^6.0.2 - firebase_database_platform_interface: ^0.3.0+2 - firebase_database_web: ^0.2.7+3 + firebase_database_platform_interface: ^0.3.0+3 + firebase_database_web: ^0.2.7+4 flutter: sdk: flutter diff --git a/packages/firebase_database/firebase_database_platform_interface/CHANGELOG.md b/packages/firebase_database/firebase_database_platform_interface/CHANGELOG.md index a8beaf72e012..fc1fd4215bcb 100755 --- a/packages/firebase_database/firebase_database_platform_interface/CHANGELOG.md +++ b/packages/firebase_database/firebase_database_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.0+3 + + - Update a dependency to the latest release. + ## 0.3.0+2 - Update a dependency to the latest release. diff --git a/packages/firebase_database/firebase_database_platform_interface/pubspec.yaml b/packages/firebase_database/firebase_database_platform_interface/pubspec.yaml index 6dcff2707418..cdac9d9cc00d 100755 --- a/packages/firebase_database/firebase_database_platform_interface/pubspec.yaml +++ b/packages/firebase_database/firebase_database_platform_interface/pubspec.yaml @@ -1,6 +1,6 @@ name: firebase_database_platform_interface description: A common platform interface for the firebase_database plugin. -version: 0.3.0+2 +version: 0.3.0+3 homepage: https://github.com/firebase/flutterfire/tree/main/packages/firebase_database/firebase_database_platform_interface environment: @@ -8,9 +8,9 @@ environment: flutter: '>=3.3.0' dependencies: - _flutterfire_internals: ^1.3.66 + _flutterfire_internals: ^1.3.67 collection: ^1.14.3 - firebase_core: ^4.4.0 + firebase_core: ^4.5.0 flutter: sdk: flutter meta: ^1.8.0 diff --git a/packages/firebase_database/firebase_database_web/CHANGELOG.md b/packages/firebase_database/firebase_database_web/CHANGELOG.md index ea6ee854301f..c6aa5b02655c 100644 --- a/packages/firebase_database/firebase_database_web/CHANGELOG.md +++ b/packages/firebase_database/firebase_database_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.7+4 + + - Update a dependency to the latest release. + ## 0.2.7+3 - Update a dependency to the latest release. diff --git a/packages/firebase_database/firebase_database_web/lib/src/firebase_database_version.dart b/packages/firebase_database/firebase_database_web/lib/src/firebase_database_version.dart index 6162ddc5882f..acc01b4460cc 100644 --- a/packages/firebase_database/firebase_database_web/lib/src/firebase_database_version.dart +++ b/packages/firebase_database/firebase_database_web/lib/src/firebase_database_version.dart @@ -13,4 +13,4 @@ // limitations under the License. /// generated version number for the package, do not manually edit -const packageVersion = '12.1.3'; +const packageVersion = '12.1.4'; diff --git a/packages/firebase_database/firebase_database_web/pubspec.yaml b/packages/firebase_database/firebase_database_web/pubspec.yaml index 3c1fd199f608..308584e5bc44 100644 --- a/packages/firebase_database/firebase_database_web/pubspec.yaml +++ b/packages/firebase_database/firebase_database_web/pubspec.yaml @@ -1,6 +1,6 @@ name: firebase_database_web description: The web implementation of firebase_database -version: 0.2.7+3 +version: 0.2.7+4 homepage: https://github.com/firebase/flutterfire/tree/main/packages/firebase_database/firebase_database_web environment: @@ -9,9 +9,9 @@ environment: dependencies: collection: ^1.18.0 - firebase_core: ^4.4.0 - firebase_core_web: ^3.4.0 - firebase_database_platform_interface: ^0.3.0+2 + firebase_core: ^4.5.0 + firebase_core_web: ^3.5.0 + firebase_database_platform_interface: ^0.3.0+3 flutter: sdk: flutter flutter_web_plugins: diff --git a/packages/firebase_in_app_messaging/firebase_in_app_messaging/CHANGELOG.md b/packages/firebase_in_app_messaging/firebase_in_app_messaging/CHANGELOG.md index 7576907f481a..7e2a73fc49e9 100644 --- a/packages/firebase_in_app_messaging/firebase_in_app_messaging/CHANGELOG.md +++ b/packages/firebase_in_app_messaging/firebase_in_app_messaging/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.0+7 + + - Update a dependency to the latest release. + ## 0.9.0+6 - Update a dependency to the latest release. diff --git a/packages/firebase_in_app_messaging/firebase_in_app_messaging/example/pubspec.yaml b/packages/firebase_in_app_messaging/firebase_in_app_messaging/example/pubspec.yaml index 1f18d855a1b5..3a2cf9b32352 100644 --- a/packages/firebase_in_app_messaging/firebase_in_app_messaging/example/pubspec.yaml +++ b/packages/firebase_in_app_messaging/firebase_in_app_messaging/example/pubspec.yaml @@ -6,10 +6,10 @@ environment: sdk: '>=3.2.0 <4.0.0' dependencies: - firebase_analytics: ^12.1.2 - firebase_core: ^4.4.0 - firebase_in_app_messaging: ^0.9.0+6 - firebase_in_app_messaging_platform_interface: ^0.2.5+17 + firebase_analytics: ^12.1.3 + firebase_core: ^4.5.0 + firebase_in_app_messaging: ^0.9.0+7 + firebase_in_app_messaging_platform_interface: ^0.2.5+18 flutter: sdk: flutter diff --git a/packages/firebase_in_app_messaging/firebase_in_app_messaging/ios/generated_firebase_sdk_version.txt b/packages/firebase_in_app_messaging/firebase_in_app_messaging/ios/generated_firebase_sdk_version.txt index a54ec1fce4a4..3eb7353bcd3e 100644 --- a/packages/firebase_in_app_messaging/firebase_in_app_messaging/ios/generated_firebase_sdk_version.txt +++ b/packages/firebase_in_app_messaging/firebase_in_app_messaging/ios/generated_firebase_sdk_version.txt @@ -1 +1 @@ -12.8.0 \ No newline at end of file +12.9.0 \ No newline at end of file diff --git a/packages/firebase_in_app_messaging/firebase_in_app_messaging/pubspec.yaml b/packages/firebase_in_app_messaging/firebase_in_app_messaging/pubspec.yaml index 5903e53d8c69..4382d500ea75 100644 --- a/packages/firebase_in_app_messaging/firebase_in_app_messaging/pubspec.yaml +++ b/packages/firebase_in_app_messaging/firebase_in_app_messaging/pubspec.yaml @@ -1,6 +1,6 @@ name: firebase_in_app_messaging description: Flutter plugin for Firebase In-App Messaging. -version: 0.9.0+6 +version: 0.9.0+7 homepage: https://firebase.google.com/docs/in-app-messaging repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_in_app_messaging topics: @@ -17,9 +17,9 @@ environment: flutter: '>=3.3.0' dependencies: - firebase_core: ^4.4.0 + firebase_core: ^4.5.0 firebase_core_platform_interface: ^6.0.2 - firebase_in_app_messaging_platform_interface: ^0.2.5+17 + firebase_in_app_messaging_platform_interface: ^0.2.5+18 flutter: sdk: flutter meta: ^1.8.0 diff --git a/packages/firebase_in_app_messaging/firebase_in_app_messaging_platform_interface/CHANGELOG.md b/packages/firebase_in_app_messaging/firebase_in_app_messaging_platform_interface/CHANGELOG.md index 53f9369fec42..07873b04d704 100644 --- a/packages/firebase_in_app_messaging/firebase_in_app_messaging_platform_interface/CHANGELOG.md +++ b/packages/firebase_in_app_messaging/firebase_in_app_messaging_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.5+18 + + - Update a dependency to the latest release. + ## 0.2.5+17 - Update a dependency to the latest release. diff --git a/packages/firebase_in_app_messaging/firebase_in_app_messaging_platform_interface/pubspec.yaml b/packages/firebase_in_app_messaging/firebase_in_app_messaging_platform_interface/pubspec.yaml index 8eef6daa5b00..28f50abc46c6 100644 --- a/packages/firebase_in_app_messaging/firebase_in_app_messaging_platform_interface/pubspec.yaml +++ b/packages/firebase_in_app_messaging/firebase_in_app_messaging_platform_interface/pubspec.yaml @@ -3,15 +3,15 @@ description: A common platform interface for the firebase_in_app_messaging plugi homepage: https://github.com/firebase/flutterfire/tree/main/packages/firebase_in_app_messaging/firebase_in_app_messagin_platform_interface repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_in_app_messaging/firebase_in_app_messagin_platform_interface -version: 0.2.5+17 +version: 0.2.5+18 environment: sdk: '>=3.2.0 <4.0.0' flutter: '>=3.3.0' dependencies: - _flutterfire_internals: ^1.3.66 - firebase_core: ^4.4.0 + _flutterfire_internals: ^1.3.67 + firebase_core: ^4.5.0 flutter: sdk: flutter meta: ^1.8.0 diff --git a/packages/firebase_messaging/firebase_messaging/CHANGELOG.md b/packages/firebase_messaging/firebase_messaging/CHANGELOG.md index f52c156f59e8..3b440b50f9e4 100644 --- a/packages/firebase_messaging/firebase_messaging/CHANGELOG.md +++ b/packages/firebase_messaging/firebase_messaging/CHANGELOG.md @@ -1,3 +1,7 @@ +## 16.1.2 + + - Update a dependency to the latest release. + ## 16.1.1 - **FIX**(messaging,iOS): scope iOS 18 duplicate notification workaround to iOS 18.0 only ([#17932](https://github.com/firebase/flutterfire/issues/17932)). ([c78f56ea](https://github.com/firebase/flutterfire/commit/c78f56ea0fd0d5ba0b565a11cbf9acce73f93401)) diff --git a/packages/firebase_messaging/firebase_messaging/example/pubspec.yaml b/packages/firebase_messaging/firebase_messaging/example/pubspec.yaml index 43f7db38d69a..cca6f25b826a 100644 --- a/packages/firebase_messaging/firebase_messaging/example/pubspec.yaml +++ b/packages/firebase_messaging/firebase_messaging/example/pubspec.yaml @@ -6,8 +6,8 @@ environment: flutter: '>=3.3.0' dependencies: - firebase_core: ^4.4.0 - firebase_messaging: ^16.1.1 + firebase_core: ^4.5.0 + firebase_messaging: ^16.1.2 flutter: sdk: flutter flutter_local_notifications: ^17.2.1 diff --git a/packages/firebase_messaging/firebase_messaging/ios/generated_firebase_sdk_version.txt b/packages/firebase_messaging/firebase_messaging/ios/generated_firebase_sdk_version.txt index a54ec1fce4a4..3eb7353bcd3e 100644 --- a/packages/firebase_messaging/firebase_messaging/ios/generated_firebase_sdk_version.txt +++ b/packages/firebase_messaging/firebase_messaging/ios/generated_firebase_sdk_version.txt @@ -1 +1 @@ -12.8.0 \ No newline at end of file +12.9.0 \ No newline at end of file diff --git a/packages/firebase_messaging/firebase_messaging/pubspec.yaml b/packages/firebase_messaging/firebase_messaging/pubspec.yaml index d421f08fbb23..69a11acf8dcc 100644 --- a/packages/firebase_messaging/firebase_messaging/pubspec.yaml +++ b/packages/firebase_messaging/firebase_messaging/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for Firebase Cloud Messaging, a cross-platform messaging solution that lets you reliably deliver messages on Android and iOS. homepage: https://firebase.google.com/docs/cloud-messaging repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_messaging/firebase_messaging -version: 16.1.1 +version: 16.1.2 topics: - firebase - messaging @@ -17,10 +17,10 @@ environment: flutter: '>=3.3.0' dependencies: - firebase_core: ^4.4.0 + firebase_core: ^4.5.0 firebase_core_platform_interface: ^6.0.2 - firebase_messaging_platform_interface: ^4.7.6 - firebase_messaging_web: ^4.1.2 + firebase_messaging_platform_interface: ^4.7.7 + firebase_messaging_web: ^4.1.3 flutter: sdk: flutter meta: ^1.8.0 diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/CHANGELOG.md b/packages/firebase_messaging/firebase_messaging_platform_interface/CHANGELOG.md index 7c0eda5a8b10..ec9679d2badc 100644 --- a/packages/firebase_messaging/firebase_messaging_platform_interface/CHANGELOG.md +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.7.7 + + - Update a dependency to the latest release. + ## 4.7.6 - Update a dependency to the latest release. diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/pubspec.yaml b/packages/firebase_messaging/firebase_messaging_platform_interface/pubspec.yaml index ae3e26f60fd1..1f6febcf8338 100644 --- a/packages/firebase_messaging/firebase_messaging_platform_interface/pubspec.yaml +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/pubspec.yaml @@ -1,6 +1,6 @@ name: firebase_messaging_platform_interface description: A common platform interface for the firebase_messaging plugin. -version: 4.7.6 +version: 4.7.7 homepage: https://github.com/firebase/flutterfire/tree/main/packages/firebase_messaging/firebase_messaging_platform_interface repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_messaging/firebase_messaging_platform_interface @@ -9,8 +9,8 @@ environment: flutter: '>=3.3.0' dependencies: - _flutterfire_internals: ^1.3.66 - firebase_core: ^4.4.0 + _flutterfire_internals: ^1.3.67 + firebase_core: ^4.5.0 flutter: sdk: flutter meta: ^1.8.0 diff --git a/packages/firebase_messaging/firebase_messaging_web/CHANGELOG.md b/packages/firebase_messaging/firebase_messaging_web/CHANGELOG.md index bde922be65e5..81fa2fbb72bc 100644 --- a/packages/firebase_messaging/firebase_messaging_web/CHANGELOG.md +++ b/packages/firebase_messaging/firebase_messaging_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.1.3 + + - Update a dependency to the latest release. + ## 4.1.2 - Update a dependency to the latest release. diff --git a/packages/firebase_messaging/firebase_messaging_web/lib/src/firebase_messaging_version.dart b/packages/firebase_messaging/firebase_messaging_web/lib/src/firebase_messaging_version.dart index 4251b1c60353..3456e0d882eb 100644 --- a/packages/firebase_messaging/firebase_messaging_web/lib/src/firebase_messaging_version.dart +++ b/packages/firebase_messaging/firebase_messaging_web/lib/src/firebase_messaging_version.dart @@ -13,4 +13,4 @@ // limitations under the License. /// generated version number for the package, do not manually edit -const packageVersion = '16.1.1'; +const packageVersion = '16.1.2'; diff --git a/packages/firebase_messaging/firebase_messaging_web/pubspec.yaml b/packages/firebase_messaging/firebase_messaging_web/pubspec.yaml index 97cd36fe2fa6..760d0da5f84b 100644 --- a/packages/firebase_messaging/firebase_messaging_web/pubspec.yaml +++ b/packages/firebase_messaging/firebase_messaging_web/pubspec.yaml @@ -2,17 +2,17 @@ name: firebase_messaging_web description: The web implementation of firebase_messaging homepage: https://github.com/firebase/flutterfire/tree/main/packages/firebase_messaging/firebase_messaging_web repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_messaging/firebase_messaging_web -version: 4.1.2 +version: 4.1.3 environment: sdk: '>=3.4.0 <4.0.0' flutter: '>=3.22.0' dependencies: - _flutterfire_internals: ^1.3.66 - firebase_core: ^4.4.0 - firebase_core_web: ^3.4.0 - firebase_messaging_platform_interface: ^4.7.6 + _flutterfire_internals: ^1.3.67 + firebase_core: ^4.5.0 + firebase_core_web: ^3.5.0 + firebase_messaging_platform_interface: ^4.7.7 flutter: sdk: flutter flutter_web_plugins: diff --git a/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/CHANGELOG.md b/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/CHANGELOG.md index d6b0ebaaf1f9..0e28d560937b 100644 --- a/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/CHANGELOG.md +++ b/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.0+7 + + - Update a dependency to the latest release. + ## 0.4.0+6 - Update a dependency to the latest release. diff --git a/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/example/pubspec.yaml b/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/example/pubspec.yaml index 5d6e3977e102..c1d72027fb5e 100644 --- a/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/example/pubspec.yaml +++ b/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/example/pubspec.yaml @@ -10,8 +10,8 @@ dependencies: flutter: sdk: flutter - firebase_core: ^4.4.0 - firebase_ml_model_downloader: ^0.4.0+6 + firebase_core: ^4.5.0 + firebase_ml_model_downloader: ^0.4.0+7 dev_dependencies: flutter_lints: ^4.0.0 diff --git a/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/ios/firebase_ml_model_downloader/Sources/firebase_ml_model_downloader/Constants.swift b/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/ios/firebase_ml_model_downloader/Sources/firebase_ml_model_downloader/Constants.swift index bbd958ad5897..6d856d7c77e6 100644 --- a/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/ios/firebase_ml_model_downloader/Sources/firebase_ml_model_downloader/Constants.swift +++ b/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/ios/firebase_ml_model_downloader/Sources/firebase_ml_model_downloader/Constants.swift @@ -3,4 +3,4 @@ // found in the LICENSE file. /// Auto-generated file. Do not edit. -public let versionNumber = "0.4.0+6" +public let versionNumber = "0.4.0+7" diff --git a/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/ios/generated_firebase_sdk_version.txt b/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/ios/generated_firebase_sdk_version.txt index a54ec1fce4a4..3eb7353bcd3e 100644 --- a/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/ios/generated_firebase_sdk_version.txt +++ b/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/ios/generated_firebase_sdk_version.txt @@ -1 +1 @@ -12.8.0 \ No newline at end of file +12.9.0 \ No newline at end of file diff --git a/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/pubspec.yaml b/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/pubspec.yaml index 61b4fa0a70e5..be29d4acb54b 100644 --- a/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/pubspec.yaml +++ b/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/pubspec.yaml @@ -1,6 +1,6 @@ name: firebase_ml_model_downloader description: A Flutter plugin allowing you to use Firebase Ml Model Downloader. -version: 0.4.0+6 +version: 0.4.0+7 homepage: https://firebase.google.com/docs/ml/flutter/use-custom-models repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_ml_model_downloader/firebase_ml_model_downloader topics: @@ -17,9 +17,9 @@ environment: flutter: '>=3.3.0' dependencies: - firebase_core: ^4.4.0 + firebase_core: ^4.5.0 firebase_core_platform_interface: ^6.0.2 - firebase_ml_model_downloader_platform_interface: ^0.1.5+17 + firebase_ml_model_downloader_platform_interface: ^0.1.5+18 flutter: sdk: flutter diff --git a/packages/firebase_ml_model_downloader/firebase_ml_model_downloader_platform_interface/CHANGELOG.md b/packages/firebase_ml_model_downloader/firebase_ml_model_downloader_platform_interface/CHANGELOG.md index 038bd21c6b1f..8ed3b54c9b62 100644 --- a/packages/firebase_ml_model_downloader/firebase_ml_model_downloader_platform_interface/CHANGELOG.md +++ b/packages/firebase_ml_model_downloader/firebase_ml_model_downloader_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.5+18 + + - Update a dependency to the latest release. + ## 0.1.5+17 - Update a dependency to the latest release. diff --git a/packages/firebase_ml_model_downloader/firebase_ml_model_downloader_platform_interface/pubspec.yaml b/packages/firebase_ml_model_downloader/firebase_ml_model_downloader_platform_interface/pubspec.yaml index 328dcf6f94dc..d0486c2cf8fc 100644 --- a/packages/firebase_ml_model_downloader/firebase_ml_model_downloader_platform_interface/pubspec.yaml +++ b/packages/firebase_ml_model_downloader/firebase_ml_model_downloader_platform_interface/pubspec.yaml @@ -1,6 +1,6 @@ name: firebase_ml_model_downloader_platform_interface description: A common platform interface for the firebase_ml_model_downloader plugin. -version: 0.1.5+17 +version: 0.1.5+18 homepage: https://github.com/firebase/flutterfire/tree/main/packages/firebase_ml_model_downloader/firebase_ml_model_downloader_platform_interface repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_ml_model_downloader/firebase_ml_model_downloader_platform_interface @@ -9,7 +9,7 @@ environment: flutter: '>=3.3.0' dependencies: - firebase_core: ^4.4.0 + firebase_core: ^4.5.0 flutter: sdk: flutter meta: ^1.8.0 diff --git a/packages/firebase_performance/firebase_performance/CHANGELOG.md b/packages/firebase_performance/firebase_performance/CHANGELOG.md index 43159e7fa328..666252e35f93 100644 --- a/packages/firebase_performance/firebase_performance/CHANGELOG.md +++ b/packages/firebase_performance/firebase_performance/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.11.1+5 + + - Update a dependency to the latest release. + ## 0.11.1+4 - Update a dependency to the latest release. diff --git a/packages/firebase_performance/firebase_performance/example/pubspec.yaml b/packages/firebase_performance/firebase_performance/example/pubspec.yaml index f4f7413a1f22..4803754e3538 100644 --- a/packages/firebase_performance/firebase_performance/example/pubspec.yaml +++ b/packages/firebase_performance/firebase_performance/example/pubspec.yaml @@ -7,8 +7,8 @@ environment: sdk: '>=3.2.0 <4.0.0' dependencies: - firebase_core: ^4.4.0 - firebase_performance: ^0.11.1+4 + firebase_core: ^4.5.0 + firebase_performance: ^0.11.1+5 flutter: sdk: flutter http: ^1.0.0 diff --git a/packages/firebase_performance/firebase_performance/ios/generated_firebase_sdk_version.txt b/packages/firebase_performance/firebase_performance/ios/generated_firebase_sdk_version.txt index a54ec1fce4a4..3eb7353bcd3e 100644 --- a/packages/firebase_performance/firebase_performance/ios/generated_firebase_sdk_version.txt +++ b/packages/firebase_performance/firebase_performance/ios/generated_firebase_sdk_version.txt @@ -1 +1 @@ -12.8.0 \ No newline at end of file +12.9.0 \ No newline at end of file diff --git a/packages/firebase_performance/firebase_performance/pubspec.yaml b/packages/firebase_performance/firebase_performance/pubspec.yaml index 17f1cc50952d..780301eec68b 100644 --- a/packages/firebase_performance/firebase_performance/pubspec.yaml +++ b/packages/firebase_performance/firebase_performance/pubspec.yaml @@ -5,7 +5,7 @@ description: iOS. homepage: https://firebase.google.com/docs/perf-mon repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_performance/firebase_performance -version: 0.11.1+4 +version: 0.11.1+5 topics: - firebase - performance @@ -20,10 +20,10 @@ environment: flutter: '>=3.3.0' dependencies: - firebase_core: ^4.4.0 + firebase_core: ^4.5.0 firebase_core_platform_interface: ^6.0.2 - firebase_performance_platform_interface: ^0.1.6+4 - firebase_performance_web: ^0.1.8+2 + firebase_performance_platform_interface: ^0.1.6+5 + firebase_performance_web: ^0.1.8+3 flutter: sdk: flutter diff --git a/packages/firebase_performance/firebase_performance_platform_interface/CHANGELOG.md b/packages/firebase_performance/firebase_performance_platform_interface/CHANGELOG.md index 2dc917eaa159..314267213ea4 100644 --- a/packages/firebase_performance/firebase_performance_platform_interface/CHANGELOG.md +++ b/packages/firebase_performance/firebase_performance_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.6+5 + + - Update a dependency to the latest release. + ## 0.1.6+4 - Update a dependency to the latest release. diff --git a/packages/firebase_performance/firebase_performance_platform_interface/pubspec.yaml b/packages/firebase_performance/firebase_performance_platform_interface/pubspec.yaml index 92467cbba293..13b75dc56a38 100644 --- a/packages/firebase_performance/firebase_performance_platform_interface/pubspec.yaml +++ b/packages/firebase_performance/firebase_performance_platform_interface/pubspec.yaml @@ -1,6 +1,6 @@ name: firebase_performance_platform_interface description: A common platform interface for the firebase_performance plugin. -version: 0.1.6+4 +version: 0.1.6+5 homepage: https://firebase.google.com/docs/perf-mon/flutter/get-started environment: @@ -8,8 +8,8 @@ environment: flutter: '>=3.3.0' dependencies: - _flutterfire_internals: ^1.3.66 - firebase_core: ^4.4.0 + _flutterfire_internals: ^1.3.67 + firebase_core: ^4.5.0 flutter: sdk: flutter plugin_platform_interface: ^2.1.3 diff --git a/packages/firebase_performance/firebase_performance_web/CHANGELOG.md b/packages/firebase_performance/firebase_performance_web/CHANGELOG.md index df84010dd68a..d7bd6783190a 100644 --- a/packages/firebase_performance/firebase_performance_web/CHANGELOG.md +++ b/packages/firebase_performance/firebase_performance_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.8+3 + + - Update a dependency to the latest release. + ## 0.1.8+2 - Update a dependency to the latest release. diff --git a/packages/firebase_performance/firebase_performance_web/lib/src/firebase_performance_version.dart b/packages/firebase_performance/firebase_performance_web/lib/src/firebase_performance_version.dart index 4b8c052c0488..efb1dedfae05 100644 --- a/packages/firebase_performance/firebase_performance_web/lib/src/firebase_performance_version.dart +++ b/packages/firebase_performance/firebase_performance_web/lib/src/firebase_performance_version.dart @@ -13,4 +13,4 @@ // limitations under the License. /// generated version number for the package, do not manually edit -const packageVersion = '0.11.1+4'; +const packageVersion = '0.11.1+5'; diff --git a/packages/firebase_performance/firebase_performance_web/pubspec.yaml b/packages/firebase_performance/firebase_performance_web/pubspec.yaml index f638c10a0396..ade3fa02adc1 100644 --- a/packages/firebase_performance/firebase_performance_web/pubspec.yaml +++ b/packages/firebase_performance/firebase_performance_web/pubspec.yaml @@ -1,17 +1,17 @@ name: firebase_performance_web description: Web implementation of Firebase Performance monitoring. homepage: https://github.com/firebase/flutterfire/tree/main/packages/firebase_performance/firebase_performance_web -version: 0.1.8+2 +version: 0.1.8+3 environment: sdk: '>=3.4.0 <4.0.0' flutter: '>=3.22.0' dependencies: - _flutterfire_internals: ^1.3.66 - firebase_core: ^4.4.0 - firebase_core_web: ^3.4.0 - firebase_performance_platform_interface: ^0.1.6+4 + _flutterfire_internals: ^1.3.67 + firebase_core: ^4.5.0 + firebase_core_web: ^3.5.0 + firebase_performance_platform_interface: ^0.1.6+5 flutter: sdk: flutter flutter_web_plugins: diff --git a/packages/firebase_remote_config/firebase_remote_config/CHANGELOG.md b/packages/firebase_remote_config/firebase_remote_config/CHANGELOG.md index 1941e7ee00d5..6e203596be7e 100644 --- a/packages/firebase_remote_config/firebase_remote_config/CHANGELOG.md +++ b/packages/firebase_remote_config/firebase_remote_config/CHANGELOG.md @@ -1,3 +1,8 @@ +## 6.2.0 + + - **FIX**(remote_config): correct `lastFetchTime` calculation ([#18004](https://github.com/firebase/flutterfire/issues/18004)). ([92f03e08](https://github.com/firebase/flutterfire/commit/92f03e08e9b5362c180da16d60d869568daf2c55)) + - **FEAT**(remote-config,windows): add support for windows ([#18006](https://github.com/firebase/flutterfire/issues/18006)). ([a6ec167f](https://github.com/firebase/flutterfire/commit/a6ec167f4ece9c9b455a916366781f482cc380b3)) + ## 6.1.4 - Update a dependency to the latest release. diff --git a/packages/firebase_remote_config/firebase_remote_config/example/pubspec.yaml b/packages/firebase_remote_config/firebase_remote_config/example/pubspec.yaml index 61a977ff182a..8cc22d0854aa 100644 --- a/packages/firebase_remote_config/firebase_remote_config/example/pubspec.yaml +++ b/packages/firebase_remote_config/firebase_remote_config/example/pubspec.yaml @@ -8,8 +8,8 @@ environment: dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - firebase_core: ^4.4.0 - firebase_remote_config: ^6.1.4 + firebase_core: ^4.5.0 + firebase_remote_config: ^6.2.0 flutter: sdk: flutter diff --git a/packages/firebase_remote_config/firebase_remote_config/ios/generated_firebase_sdk_version.txt b/packages/firebase_remote_config/firebase_remote_config/ios/generated_firebase_sdk_version.txt index a54ec1fce4a4..3eb7353bcd3e 100644 --- a/packages/firebase_remote_config/firebase_remote_config/ios/generated_firebase_sdk_version.txt +++ b/packages/firebase_remote_config/firebase_remote_config/ios/generated_firebase_sdk_version.txt @@ -1 +1 @@ -12.8.0 \ No newline at end of file +12.9.0 \ No newline at end of file diff --git a/packages/firebase_remote_config/firebase_remote_config/pubspec.yaml b/packages/firebase_remote_config/firebase_remote_config/pubspec.yaml index efff370cab2b..5a42eb285061 100644 --- a/packages/firebase_remote_config/firebase_remote_config/pubspec.yaml +++ b/packages/firebase_remote_config/firebase_remote_config/pubspec.yaml @@ -4,7 +4,7 @@ description: re-releasing. homepage: https://firebase.google.com/docs/remote-config repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_remote_config/firebase_remote_config -version: 6.1.4 +version: 6.2.0 topics: - firebase - remote @@ -19,10 +19,10 @@ environment: flutter: '>=3.3.0' dependencies: - firebase_core: ^4.4.0 + firebase_core: ^4.5.0 firebase_core_platform_interface: ^6.0.2 - firebase_remote_config_platform_interface: ^2.0.7 - firebase_remote_config_web: ^1.10.3 + firebase_remote_config_platform_interface: ^2.1.0 + firebase_remote_config_web: ^1.10.4 flutter: sdk: flutter diff --git a/packages/firebase_remote_config/firebase_remote_config_platform_interface/CHANGELOG.md b/packages/firebase_remote_config/firebase_remote_config_platform_interface/CHANGELOG.md index b779393285d0..1e51e6fb3683 100644 --- a/packages/firebase_remote_config/firebase_remote_config_platform_interface/CHANGELOG.md +++ b/packages/firebase_remote_config/firebase_remote_config_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.1.0 + + - **FEAT**(remote-config,windows): add support for windows ([#18006](https://github.com/firebase/flutterfire/issues/18006)). ([a6ec167f](https://github.com/firebase/flutterfire/commit/a6ec167f4ece9c9b455a916366781f482cc380b3)) + ## 2.0.7 - Update a dependency to the latest release. diff --git a/packages/firebase_remote_config/firebase_remote_config_platform_interface/pubspec.yaml b/packages/firebase_remote_config/firebase_remote_config_platform_interface/pubspec.yaml index 45f4ed222532..0d069acf615c 100644 --- a/packages/firebase_remote_config/firebase_remote_config_platform_interface/pubspec.yaml +++ b/packages/firebase_remote_config/firebase_remote_config_platform_interface/pubspec.yaml @@ -4,15 +4,15 @@ homepage: https://github.com/firebase/flutterfire/tree/main/packages/firebase_re repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_remote_config/firebase_remote_config_platform_interface # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 2.0.7 +version: 2.1.0 environment: sdk: '>=3.2.0 <4.0.0' flutter: '>=3.3.0' dependencies: - _flutterfire_internals: ^1.3.66 - firebase_core: ^4.4.0 + _flutterfire_internals: ^1.3.67 + firebase_core: ^4.5.0 flutter: sdk: flutter meta: ^1.8.0 diff --git a/packages/firebase_remote_config/firebase_remote_config_web/CHANGELOG.md b/packages/firebase_remote_config/firebase_remote_config_web/CHANGELOG.md index 5fb12fd22384..85dde9a875e3 100644 --- a/packages/firebase_remote_config/firebase_remote_config_web/CHANGELOG.md +++ b/packages/firebase_remote_config/firebase_remote_config_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.10.4 + + - Update a dependency to the latest release. + ## 1.10.3 - Update a dependency to the latest release. diff --git a/packages/firebase_remote_config/firebase_remote_config_web/lib/src/firebase_remote_config_version.dart b/packages/firebase_remote_config/firebase_remote_config_web/lib/src/firebase_remote_config_version.dart index 9623ca56f4d7..305156baf450 100644 --- a/packages/firebase_remote_config/firebase_remote_config_web/lib/src/firebase_remote_config_version.dart +++ b/packages/firebase_remote_config/firebase_remote_config_web/lib/src/firebase_remote_config_version.dart @@ -13,4 +13,4 @@ // limitations under the License. /// generated version number for the package, do not manually edit -const packageVersion = '6.1.4'; +const packageVersion = '6.2.0'; diff --git a/packages/firebase_remote_config/firebase_remote_config_web/pubspec.yaml b/packages/firebase_remote_config/firebase_remote_config_web/pubspec.yaml index 0b5100130542..ca0e953aaf4e 100644 --- a/packages/firebase_remote_config/firebase_remote_config_web/pubspec.yaml +++ b/packages/firebase_remote_config/firebase_remote_config_web/pubspec.yaml @@ -3,17 +3,17 @@ description: The web implementation of firebase_remote_config homepage: https://github.com/firebase/flutterfire/tree/main/packages/firebase_remote_config/firebase_remote_config_web repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_remote_config/firebase_remote_config_web -version: 1.10.3 +version: 1.10.4 environment: sdk: '>=3.4.0 <4.0.0' flutter: '>=3.22.0' dependencies: - _flutterfire_internals: ^1.3.66 - firebase_core: ^4.4.0 - firebase_core_web: ^3.4.0 - firebase_remote_config_platform_interface: ^2.0.7 + _flutterfire_internals: ^1.3.67 + firebase_core: ^4.5.0 + firebase_core_web: ^3.5.0 + firebase_remote_config_platform_interface: ^2.1.0 flutter: sdk: flutter flutter_web_plugins: diff --git a/packages/firebase_storage/firebase_storage/CHANGELOG.md b/packages/firebase_storage/firebase_storage/CHANGELOG.md index 1667f6cc8fc0..fbd8800cc7eb 100644 --- a/packages/firebase_storage/firebase_storage/CHANGELOG.md +++ b/packages/firebase_storage/firebase_storage/CHANGELOG.md @@ -1,3 +1,7 @@ +## 13.1.0 + + - **FEAT**(storage,windows): add emulator support ([#18030](https://github.com/firebase/flutterfire/issues/18030)). ([461dfa43](https://github.com/firebase/flutterfire/commit/461dfa43764469b518984052cb7bbc0a2a2675eb)) + ## 13.0.6 - Update a dependency to the latest release. diff --git a/packages/firebase_storage/firebase_storage/example/pubspec.yaml b/packages/firebase_storage/firebase_storage/example/pubspec.yaml index 94ba0f665cdd..44110a521de4 100755 --- a/packages/firebase_storage/firebase_storage/example/pubspec.yaml +++ b/packages/firebase_storage/firebase_storage/example/pubspec.yaml @@ -5,8 +5,8 @@ environment: sdk: '>=3.4.0 <4.0.0' dependencies: - firebase_core: ^4.4.0 - firebase_storage: ^13.0.6 + firebase_core: ^4.5.0 + firebase_storage: ^13.1.0 flutter: sdk: flutter image_picker: ^1.1.2 diff --git a/packages/firebase_storage/firebase_storage/ios/generated_firebase_sdk_version.txt b/packages/firebase_storage/firebase_storage/ios/generated_firebase_sdk_version.txt index a54ec1fce4a4..3eb7353bcd3e 100644 --- a/packages/firebase_storage/firebase_storage/ios/generated_firebase_sdk_version.txt +++ b/packages/firebase_storage/firebase_storage/ios/generated_firebase_sdk_version.txt @@ -1 +1 @@ -12.8.0 \ No newline at end of file +12.9.0 \ No newline at end of file diff --git a/packages/firebase_storage/firebase_storage/pubspec.yaml b/packages/firebase_storage/firebase_storage/pubspec.yaml index d099f6808884..56902a170449 100755 --- a/packages/firebase_storage/firebase_storage/pubspec.yaml +++ b/packages/firebase_storage/firebase_storage/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for Firebase Cloud Storage, a powerful, simple, and cost-effective object storage service for Android and iOS. homepage: https://firebase.google.com/docs/storage/flutter/start repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_storage/firebase_storage -version: 13.0.6 +version: 13.1.0 topics: - firebase - storage @@ -19,10 +19,10 @@ environment: flutter: '>=3.3.0' dependencies: - firebase_core: ^4.4.0 + firebase_core: ^4.5.0 firebase_core_platform_interface: ^6.0.2 - firebase_storage_platform_interface: ^5.2.17 - firebase_storage_web: ^3.11.2 + firebase_storage_platform_interface: ^5.2.18 + firebase_storage_web: ^3.11.3 flutter: sdk: flutter diff --git a/packages/firebase_storage/firebase_storage_platform_interface/CHANGELOG.md b/packages/firebase_storage/firebase_storage_platform_interface/CHANGELOG.md index b315b5db3180..40742c080982 100644 --- a/packages/firebase_storage/firebase_storage_platform_interface/CHANGELOG.md +++ b/packages/firebase_storage/firebase_storage_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.2.18 + + - Update a dependency to the latest release. + ## 5.2.17 - Update a dependency to the latest release. diff --git a/packages/firebase_storage/firebase_storage_platform_interface/pubspec.yaml b/packages/firebase_storage/firebase_storage_platform_interface/pubspec.yaml index aa3b74f77c99..94a97519737e 100644 --- a/packages/firebase_storage/firebase_storage_platform_interface/pubspec.yaml +++ b/packages/firebase_storage/firebase_storage_platform_interface/pubspec.yaml @@ -1,6 +1,6 @@ name: firebase_storage_platform_interface description: A common platform interface for the firebase_storage plugin. -version: 5.2.17 +version: 5.2.18 homepage: https://github.com/firebase/flutterfire/tree/main/packages/firebase_storage/firebase_storage_platform_interface repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_storage/firebase_storage_platform_interface @@ -9,9 +9,9 @@ environment: flutter: '>=3.3.0' dependencies: - _flutterfire_internals: ^1.3.66 + _flutterfire_internals: ^1.3.67 collection: ^1.15.0 - firebase_core: ^4.4.0 + firebase_core: ^4.5.0 flutter: sdk: flutter meta: ^1.8.0 diff --git a/packages/firebase_storage/firebase_storage_web/CHANGELOG.md b/packages/firebase_storage/firebase_storage_web/CHANGELOG.md index d5995ac6ab69..9964e8328c65 100644 --- a/packages/firebase_storage/firebase_storage_web/CHANGELOG.md +++ b/packages/firebase_storage/firebase_storage_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.11.3 + + - Update a dependency to the latest release. + ## 3.11.2 - Update a dependency to the latest release. diff --git a/packages/firebase_storage/firebase_storage_web/lib/src/firebase_storage_version.dart b/packages/firebase_storage/firebase_storage_web/lib/src/firebase_storage_version.dart index bbf6df008c51..73ee2b26de13 100644 --- a/packages/firebase_storage/firebase_storage_web/lib/src/firebase_storage_version.dart +++ b/packages/firebase_storage/firebase_storage_web/lib/src/firebase_storage_version.dart @@ -13,4 +13,4 @@ // limitations under the License. /// generated version number for the package, do not manually edit -const packageVersion = '13.0.6'; +const packageVersion = '13.1.0'; diff --git a/packages/firebase_storage/firebase_storage_web/pubspec.yaml b/packages/firebase_storage/firebase_storage_web/pubspec.yaml index 236477eff4d0..3b6609c708c6 100644 --- a/packages/firebase_storage/firebase_storage_web/pubspec.yaml +++ b/packages/firebase_storage/firebase_storage_web/pubspec.yaml @@ -2,18 +2,18 @@ name: firebase_storage_web description: The web implementation of firebase_storage homepage: https://github.com/firebase/flutterfire/tree/main/packages/firebase_storage/firebase_storage_web repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_storage/firebase_storage_web -version: 3.11.2 +version: 3.11.3 environment: sdk: '>=3.4.0 <4.0.0' flutter: '>=3.22.0' dependencies: - _flutterfire_internals: ^1.3.66 + _flutterfire_internals: ^1.3.67 async: ^2.5.0 - firebase_core: ^4.4.0 - firebase_core_web: ^3.4.0 - firebase_storage_platform_interface: ^5.2.17 + firebase_core: ^4.5.0 + firebase_core_web: ^3.5.0 + firebase_storage_platform_interface: ^5.2.18 flutter: sdk: flutter flutter_web_plugins: diff --git a/scripts/versions.json b/scripts/versions.json index 537849fa0a7e..cfce66daa3a8 100644 --- a/scripts/versions.json +++ b/scripts/versions.json @@ -1,4 +1,32 @@ { + "4.10.0": { + "date": "2026-03-02", + "firebase_sdk": { + "android": "34.9.0", + "ios": "12.9.0", + "web": "12.9.0", + "windows": "13.4.0" + }, + "packages": { + "cloud_firestore": "6.1.3", + "cloud_functions": "6.0.7", + "firebase_ai": "3.9.0", + "firebase_analytics": "12.1.3", + "firebase_app_check": "0.4.1+5", + "firebase_app_installations": "0.4.0+7", + "firebase_auth": "6.2.0", + "firebase_core": "4.5.0", + "firebase_crashlytics": "5.0.8", + "firebase_data_connect": "0.2.3", + "firebase_database": "12.1.4", + "firebase_in_app_messaging": "0.9.0+7", + "firebase_messaging": "16.1.2", + "firebase_ml_model_downloader": "0.4.0+7", + "firebase_performance": "0.11.1+5", + "firebase_remote_config": "6.2.0", + "firebase_storage": "13.1.0" + } + }, "4.9.0": { "date": "2026-02-09", "firebase_sdk": { diff --git a/tests/pubspec.yaml b/tests/pubspec.yaml index 79194bc216c8..0fb1e979b9cd 100644 --- a/tests/pubspec.yaml +++ b/tests/pubspec.yaml @@ -9,42 +9,42 @@ environment: flutter: '>=3.22.0' dependencies: - cloud_functions: ^6.0.6 - cloud_functions_platform_interface: ^5.8.9 - cloud_functions_web: ^5.1.2 + cloud_functions: ^6.0.7 + cloud_functions_platform_interface: ^5.8.10 + cloud_functions_web: ^5.1.3 collection: ^1.15.0 - firebase_analytics: ^12.1.2 - firebase_analytics_platform_interface: ^5.0.6 - firebase_analytics_web: ^0.6.1+2 - firebase_app_check: ^0.4.1+4 - firebase_app_check_platform_interface: ^0.2.1+4 - firebase_app_check_web: ^0.2.2+2 - firebase_app_installations: ^0.4.0+6 - firebase_app_installations_platform_interface: ^0.1.4+65 - firebase_app_installations_web: ^0.1.7+2 - firebase_auth: ^6.1.4 - firebase_auth_platform_interface: ^8.1.6 - firebase_auth_web: ^6.1.2 - firebase_core: ^4.4.0 + firebase_analytics: ^12.1.3 + firebase_analytics_platform_interface: ^5.0.7 + firebase_analytics_web: ^0.6.1+3 + firebase_app_check: ^0.4.1+5 + firebase_app_check_platform_interface: ^0.2.1+5 + firebase_app_check_web: ^0.2.2+3 + firebase_app_installations: ^0.4.0+7 + firebase_app_installations_platform_interface: ^0.1.4+66 + firebase_app_installations_web: ^0.1.7+3 + firebase_auth: ^6.2.0 + firebase_auth_platform_interface: ^8.1.7 + firebase_auth_web: ^6.1.3 + firebase_core: ^4.5.0 firebase_core_platform_interface: ^6.0.2 - firebase_core_web: ^3.4.0 - firebase_crashlytics: ^5.0.7 - firebase_crashlytics_platform_interface: ^3.8.17 - firebase_database: ^12.1.3 - firebase_database_platform_interface: ^0.3.0+2 - firebase_database_web: ^0.2.7+3 - firebase_messaging: ^16.1.1 - firebase_messaging_platform_interface: ^4.7.6 - firebase_messaging_web: ^4.1.2 - firebase_ml_model_downloader: ^0.4.0+6 - firebase_ml_model_downloader_platform_interface: ^0.1.5+17 - firebase_performance: ^0.11.1+4 - firebase_remote_config: ^6.1.4 - firebase_remote_config_platform_interface: ^2.0.7 - firebase_remote_config_web: ^1.10.3 - firebase_storage: ^13.0.6 - firebase_storage_platform_interface: ^5.2.17 - firebase_storage_web: ^3.11.2 + firebase_core_web: ^3.5.0 + firebase_crashlytics: ^5.0.8 + firebase_crashlytics_platform_interface: ^3.8.18 + firebase_database: ^12.1.4 + firebase_database_platform_interface: ^0.3.0+3 + firebase_database_web: ^0.2.7+4 + firebase_messaging: ^16.1.2 + firebase_messaging_platform_interface: ^4.7.7 + firebase_messaging_web: ^4.1.3 + firebase_ml_model_downloader: ^0.4.0+7 + firebase_ml_model_downloader_platform_interface: ^0.1.5+18 + firebase_performance: ^0.11.1+5 + firebase_remote_config: ^6.2.0 + firebase_remote_config_platform_interface: ^2.1.0 + firebase_remote_config_web: ^1.10.4 + firebase_storage: ^13.1.0 + firebase_storage_platform_interface: ^5.2.18 + firebase_storage_web: ^3.11.3 flutter: sdk: flutter http: ^1.0.0 From 8d715a777a4827bff59f820d9978007bd7568a7d Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Tue, 3 Mar 2026 11:36:51 +0100 Subject: [PATCH 36/72] fix(auth, windows): add pluginregistry to properly restore state on Windows (#18049) * fix(auth, windows): add pluginregistry to properly restore state on Windows * format * update * clean format --- .../firebase_auth/example/pubspec.yaml | 2 +- .../windows/firebase_auth_plugin.cpp | 34 +++++++++++++++ .../windows/firebase_auth_plugin.h | 9 +++- .../firebase_core/windows/CMakeLists.txt | 2 + .../windows/firebase_core_plugin.cpp | 10 ++++- .../windows/firebase_core_plugin_c_api.cpp | 7 +++ .../flutter_firebase_plugin_registry.cpp | 39 +++++++++++++++++ .../flutter_firebase_plugin_registry.h | 43 +++++++++++++++++++ .../firebase_core_plugin_c_api.h | 8 ++++ .../firebase_core/flutter_firebase_plugin.h | 29 +++++++++++++ 10 files changed, 179 insertions(+), 4 deletions(-) create mode 100644 packages/firebase_core/firebase_core/windows/flutter_firebase_plugin_registry.cpp create mode 100644 packages/firebase_core/firebase_core/windows/flutter_firebase_plugin_registry.h create mode 100644 packages/firebase_core/firebase_core/windows/include/firebase_core/flutter_firebase_plugin.h diff --git a/packages/firebase_auth/firebase_auth/example/pubspec.yaml b/packages/firebase_auth/firebase_auth/example/pubspec.yaml index 9e30d983d8c5..9857751e4eb9 100644 --- a/packages/firebase_auth/firebase_auth/example/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth/example/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: firebase_messaging: ^16.1.2 flutter: sdk: flutter - flutter_facebook_auth: ^7.0.1 + flutter_facebook_auth: ^7.1.5 flutter_signin_button: ^2.0.0 google_sign_in: ^6.1.0 google_sign_in_dartio: ^0.3.0 diff --git a/packages/firebase_auth/firebase_auth/windows/firebase_auth_plugin.cpp b/packages/firebase_auth/firebase_auth/windows/firebase_auth_plugin.cpp index 8986fb26e613..5a4f1bf27faf 100644 --- a/packages/firebase_auth/firebase_auth/windows/firebase_auth_plugin.cpp +++ b/packages/firebase_auth/firebase_auth/windows/firebase_auth_plugin.cpp @@ -53,6 +53,9 @@ void FirebaseAuthPlugin::RegisterWithRegistrar( FirebaseAuthHostApi::SetUp(registrar->messenger(), plugin.get()); FirebaseAuthUserHostApi::SetUp(registrar->messenger(), plugin.get()); + RegisterFlutterFirebasePlugin("plugins.flutter.io/firebase_auth", + plugin.get()); + registrar->AddPlugin(std::move(plugin)); binaryMessenger = registrar->messenger(); @@ -1283,4 +1286,35 @@ void FirebaseAuthPlugin::RevokeTokenWithAuthorizationCode( nullptr)); } +flutter::EncodableMap FirebaseAuthPlugin::GetPluginConstantsForFirebaseApp( + const firebase::App& app) { + flutter::EncodableMap constants; + + Auth* auth = Auth::GetAuth(const_cast(&app)); + firebase::auth::User user = auth->current_user(); + + if (user.is_valid()) { + PigeonUserDetails userDetails = ParseUserDetails(user); + flutter::EncodableList userDetailsList; + userDetailsList.push_back( + flutter::EncodableValue(userDetails.user_info().ToEncodableList())); + userDetailsList.push_back( + flutter::EncodableValue(userDetails.provider_data())); + constants[flutter::EncodableValue("APP_CURRENT_USER")] = + flutter::EncodableValue(userDetailsList); + } + + std::string lang = auth->language_code(); + if (!lang.empty()) { + constants[flutter::EncodableValue("APP_LANGUAGE_CODE")] = + flutter::EncodableValue(lang); + } + + return constants; +} + +void FirebaseAuthPlugin::DidReinitializeFirebaseCore() { + // No-op for now. Could be used to reset cached auth instances. +} + } // namespace firebase_auth_windows diff --git a/packages/firebase_auth/firebase_auth/windows/firebase_auth_plugin.h b/packages/firebase_auth/firebase_auth/windows/firebase_auth_plugin.h index 419c83236fb6..a75ff33e6cf5 100644 --- a/packages/firebase_auth/firebase_auth/windows/firebase_auth_plugin.h +++ b/packages/firebase_auth/firebase_auth/windows/firebase_auth_plugin.h @@ -16,6 +16,7 @@ #include "firebase/auth.h" #include "firebase/auth/types.h" #include "firebase/future.h" +#include "firebase_core/flutter_firebase_plugin.h" #include "messages.g.h" using firebase::auth::AuthError; @@ -24,7 +25,8 @@ namespace firebase_auth_windows { class FirebaseAuthPlugin : public flutter::Plugin, public FirebaseAuthHostApi, - public FirebaseAuthUserHostApi { + public FirebaseAuthUserHostApi, + public FlutterFirebasePlugin { public: static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar); @@ -180,6 +182,11 @@ class FirebaseAuthPlugin : public flutter::Plugin, const AuthPigeonFirebaseApp& app, const std::string& authorization_code, std::function reply)> result) override; + // FlutterFirebasePlugin methods. + flutter::EncodableMap GetPluginConstantsForFirebaseApp( + const firebase::App& app) override; + void DidReinitializeFirebaseCore() override; + private: static flutter::BinaryMessenger* binaryMessenger; }; diff --git a/packages/firebase_core/firebase_core/windows/CMakeLists.txt b/packages/firebase_core/firebase_core/windows/CMakeLists.txt index 40a880ba426b..1df3eeb43f59 100644 --- a/packages/firebase_core/firebase_core/windows/CMakeLists.txt +++ b/packages/firebase_core/firebase_core/windows/CMakeLists.txt @@ -65,6 +65,8 @@ list(APPEND PLUGIN_SOURCES "firebase_core_plugin.h" "messages.g.cpp" "messages.g.h" + "flutter_firebase_plugin_registry.h" + "flutter_firebase_plugin_registry.cpp" ) # Read version from pubspec.yaml diff --git a/packages/firebase_core/firebase_core/windows/firebase_core_plugin.cpp b/packages/firebase_core/firebase_core/windows/firebase_core_plugin.cpp index b544e85ecb36..0644dc8cf7ea 100644 --- a/packages/firebase_core/firebase_core/windows/firebase_core_plugin.cpp +++ b/packages/firebase_core/firebase_core/windows/firebase_core_plugin.cpp @@ -9,6 +9,7 @@ #include "firebase/app.h" #include "firebase_core/plugin_version.h" +#include "flutter_firebase_plugin_registry.h" #include "messages.g.h" // For getPlatformVersion; remove unless needed for your plugin implementation. @@ -95,7 +96,8 @@ CoreFirebaseOptions optionsFromFIROptions(const firebase::AppOptions& options) { // Convert a firebase::App to CoreInitializeResponse CoreInitializeResponse AppToCoreInitializeResponse(const App& app) { - flutter::EncodableMap plugin_constants; + flutter::EncodableMap plugin_constants = + FlutterFirebasePluginRegistry::GetPluginConstantsForFirebaseApp(app); CoreInitializeResponse response = CoreInitializeResponse( app.name(), optionsFromFIROptions(app.options()), plugin_constants); return response; @@ -116,7 +118,11 @@ void FirebaseCorePlugin::InitializeApp( void FirebaseCorePlugin::InitializeCore( std::function reply)> result) { - // TODO: Missing function to get the list of currently initialized apps + if (coreInitialized) { + FlutterFirebasePluginRegistry::DidReinitializeFirebaseCore(); + } + coreInitialized = true; + std::vector initializedApps; std::vector all_apps = App::GetApps(); for (const App* app : all_apps) { diff --git a/packages/firebase_core/firebase_core/windows/firebase_core_plugin_c_api.cpp b/packages/firebase_core/firebase_core/windows/firebase_core_plugin_c_api.cpp index d8215be27a40..3615a59064b6 100644 --- a/packages/firebase_core/firebase_core/windows/firebase_core_plugin_c_api.cpp +++ b/packages/firebase_core/firebase_core/windows/firebase_core_plugin_c_api.cpp @@ -10,6 +10,7 @@ #include #include "firebase_core_plugin.h" +#include "flutter_firebase_plugin_registry.h" void FirebaseCorePluginCApiRegisterWithRegistrar( FlutterDesktopPluginRegistrarRef registrar) { @@ -17,3 +18,9 @@ void FirebaseCorePluginCApiRegisterWithRegistrar( flutter::PluginRegistrarManager::GetInstance() ->GetRegistrar(registrar)); } + +void RegisterFlutterFirebasePlugin(const std::string& channel_name, + FlutterFirebasePlugin* plugin) { + firebase_core_windows::FlutterFirebasePluginRegistry::RegisterPlugin( + channel_name, plugin); +} diff --git a/packages/firebase_core/firebase_core/windows/flutter_firebase_plugin_registry.cpp b/packages/firebase_core/firebase_core/windows/flutter_firebase_plugin_registry.cpp new file mode 100644 index 000000000000..3d4c1d80fe21 --- /dev/null +++ b/packages/firebase_core/firebase_core/windows/flutter_firebase_plugin_registry.cpp @@ -0,0 +1,39 @@ +// Copyright 2023, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#include "flutter_firebase_plugin_registry.h" + +namespace firebase_core_windows { + +std::unordered_map& +FlutterFirebasePluginRegistry::GetRegisteredPlugins() { + static std::unordered_map plugins; + return plugins; +} + +void FlutterFirebasePluginRegistry::RegisterPlugin( + const std::string& channel_name, FlutterFirebasePlugin* plugin) { + GetRegisteredPlugins()[channel_name] = plugin; +} + +flutter::EncodableMap +FlutterFirebasePluginRegistry::GetPluginConstantsForFirebaseApp( + const firebase::App& app) { + flutter::EncodableMap all_constants; + for (const auto& entry : GetRegisteredPlugins()) { + flutter::EncodableMap plugin_constants = + entry.second->GetPluginConstantsForFirebaseApp(app); + all_constants[flutter::EncodableValue(entry.first)] = + flutter::EncodableValue(plugin_constants); + } + return all_constants; +} + +void FlutterFirebasePluginRegistry::DidReinitializeFirebaseCore() { + for (const auto& entry : GetRegisteredPlugins()) { + entry.second->DidReinitializeFirebaseCore(); + } +} + +} // namespace firebase_core_windows diff --git a/packages/firebase_core/firebase_core/windows/flutter_firebase_plugin_registry.h b/packages/firebase_core/firebase_core/windows/flutter_firebase_plugin_registry.h new file mode 100644 index 000000000000..a6c5ddb3a068 --- /dev/null +++ b/packages/firebase_core/firebase_core/windows/flutter_firebase_plugin_registry.h @@ -0,0 +1,43 @@ +// Copyright 2023, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#ifndef FLUTTER_FIREBASE_PLUGIN_REGISTRY_H_ +#define FLUTTER_FIREBASE_PLUGIN_REGISTRY_H_ + +#include + +#include +#include + +#include "firebase/app.h" +#include "include/firebase_core/flutter_firebase_plugin.h" + +namespace firebase_core_windows { + +// Static registry that collects plugin constants from all registered Firebase +// plugins during initializeCore, mirroring Android's +// FlutterFirebasePluginRegistry. +class FlutterFirebasePluginRegistry { + public: + // Registers a plugin with the given channel name. + static void RegisterPlugin(const std::string& channel_name, + FlutterFirebasePlugin* plugin); + + // Collects constants from all registered plugins for the given app. + // Returns a map keyed by channel name, with each value being the plugin's + // constants map. + static flutter::EncodableMap GetPluginConstantsForFirebaseApp( + const firebase::App& app); + + // Notifies all registered plugins that Firebase core was re-initialized. + static void DidReinitializeFirebaseCore(); + + private: + static std::unordered_map& + GetRegisteredPlugins(); +}; + +} // namespace firebase_core_windows + +#endif // FLUTTER_FIREBASE_PLUGIN_REGISTRY_H_ diff --git a/packages/firebase_core/firebase_core/windows/include/firebase_core/firebase_core_plugin_c_api.h b/packages/firebase_core/firebase_core/windows/include/firebase_core/firebase_core_plugin_c_api.h index 68f3d1d314d6..93023e96a4c3 100644 --- a/packages/firebase_core/firebase_core/windows/include/firebase_core/firebase_core_plugin_c_api.h +++ b/packages/firebase_core/firebase_core/windows/include/firebase_core/firebase_core_plugin_c_api.h @@ -12,6 +12,8 @@ #include #include +#include "flutter_firebase_plugin.h" + #ifdef FLUTTER_PLUGIN_IMPL #define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) #else @@ -21,4 +23,10 @@ FLUTTER_PLUGIN_EXPORT void FirebaseCorePluginCApiRegisterWithRegistrar( FlutterDesktopPluginRegistrarRef registrar); +// Registers a FlutterFirebasePlugin so that its constants are collected during +// Firebase.initializeApp(). The channel_name should match the Dart +// MethodChannel name (e.g. "plugins.flutter.io/firebase_auth"). +FLUTTER_PLUGIN_EXPORT void RegisterFlutterFirebasePlugin( + const std::string& channel_name, FlutterFirebasePlugin* plugin); + #endif // FLUTTER_PLUGIN_FIREBASE_CORE_PLUGIN_C_API_H_ diff --git a/packages/firebase_core/firebase_core/windows/include/firebase_core/flutter_firebase_plugin.h b/packages/firebase_core/firebase_core/windows/include/firebase_core/flutter_firebase_plugin.h new file mode 100644 index 000000000000..ade655151cda --- /dev/null +++ b/packages/firebase_core/firebase_core/windows/include/firebase_core/flutter_firebase_plugin.h @@ -0,0 +1,29 @@ +// Copyright 2023, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#ifndef FLUTTER_FIREBASE_PLUGIN_H_ +#define FLUTTER_FIREBASE_PLUGIN_H_ + +#include + +#include "firebase/app.h" + +// Abstract interface mirroring Android's FlutterFirebasePlugin.java and iOS's +// FLTFirebasePlugin.h. Each Firebase plugin implements this to provide initial +// constants (e.g. current user) during Firebase.initializeApp(). +class FlutterFirebasePlugin { + public: + virtual ~FlutterFirebasePlugin() {} + + // Returns a map of plugin-specific constants for the given Firebase app. + // Called synchronously during initializeCore to populate pluginConstants. + virtual flutter::EncodableMap GetPluginConstantsForFirebaseApp( + const firebase::App& app) = 0; + + // Called when Firebase core is re-initialized, allowing plugins to reset + // their state. + virtual void DidReinitializeFirebaseCore() = 0; +}; + +#endif // FLUTTER_FIREBASE_PLUGIN_H_ From c13040e15a42deddbf61b3180bbd002d58edca29 Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Tue, 3 Mar 2026 14:22:59 +0100 Subject: [PATCH 37/72] fix(functions): prevent collision when listening multiple times to the same stream (#18052) --- .../method_channel_https_callable.dart | 23 ++++++++++--------- .../cloud_functions_e2e_test.dart | 21 +++++++++++++++++ 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/packages/cloud_functions/cloud_functions_platform_interface/lib/src/method_channel/method_channel_https_callable.dart b/packages/cloud_functions/cloud_functions_platform_interface/lib/src/method_channel/method_channel_https_callable.dart index e74e76988714..d17ea0bb612b 100644 --- a/packages/cloud_functions/cloud_functions_platform_interface/lib/src/method_channel/method_channel_https_callable.dart +++ b/packages/cloud_functions/cloud_functions_platform_interface/lib/src/method_channel/method_channel_https_callable.dart @@ -16,16 +16,12 @@ class MethodChannelHttpsCallable extends HttpsCallablePlatform { /// Creates a new [MethodChannelHttpsCallable] instance. MethodChannelHttpsCallable(FirebaseFunctionsPlatform functions, String? origin, String? name, HttpsCallableOptions options, Uri? uri) - : _transformedUri = uri?.pathSegments.join('_').replaceAll('.', '_'), - super(functions, origin, name, options, uri) { - _eventChannelId = name ?? _transformedUri ?? ''; - _channel = - EventChannel('plugins.flutter.io/firebase_functions/$_eventChannelId'); - } + : _baseEventChannelId = + name ?? uri?.pathSegments.join('_').replaceAll('.', '_') ?? '', + super(functions, origin, name, options, uri); - late final EventChannel _channel; - final String? _transformedUri; - late String _eventChannelId; + static int _streamIdCounter = 0; + final String _baseEventChannelId; @override Future call([Object? parameters]) async { @@ -54,10 +50,15 @@ class MethodChannelHttpsCallable extends HttpsCallablePlatform { @override Stream stream(Object? parameters) async* { + // Each stream() call gets a unique channel ID to prevent collisions + // when invoking the same function concurrently. See #18036. + final eventChannelId = '${_baseEventChannelId}_${_streamIdCounter++}'; + final channel = + EventChannel('plugins.flutter.io/firebase_functions/$eventChannelId'); try { await MethodChannelFirebaseFunctions.pigeonChannel .registerEventChannel({ - 'eventChannelId': _eventChannelId, + 'eventChannelId': eventChannelId, 'appName': functions.app!.name, 'region': functions.region, }); @@ -69,7 +70,7 @@ class MethodChannelHttpsCallable extends HttpsCallablePlatform { 'limitedUseAppCheckToken': options.limitedUseAppCheckToken, 'timeout': options.timeout.inMilliseconds, }; - yield* _channel.receiveBroadcastStream(eventData).map((message) { + yield* channel.receiveBroadcastStream(eventData).map((message) { if (message is Map) { return Map.from(message); } diff --git a/tests/integration_test/cloud_functions/cloud_functions_e2e_test.dart b/tests/integration_test/cloud_functions/cloud_functions_e2e_test.dart index f97c71cf737e..375def3359bc 100644 --- a/tests/integration_test/cloud_functions/cloud_functions_e2e_test.dart +++ b/tests/integration_test/cloud_functions/cloud_functions_e2e_test.dart @@ -346,6 +346,27 @@ void main() { await expectLater(stream, emits(isA())); }); + test( + 'concurrent streams on the same callable do not collide', + () async { + // Regression test for https://github.com/firebase/flutterfire/issues/18036 + final stream1 = callable + .stream('foo') + .where((event) => event is Chunk) + .map((event) => (event as Chunk).partialData) + .first; + final stream2 = callable + .stream(123) + .where((event) => event is Chunk) + .map((event) => (event as Chunk).partialData) + .first; + + final results = await Future.wait([stream1, stream2]); + expect(results[0], equals('string')); + expect(results[1], equals('number')); + }, + ); + test('should emit a [Result] as last value', () async { final stream = await callable.stream().last; expect( From baf6543aa0ea98888b5e4b36a19f9afbfd0f6489 Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Tue, 3 Mar 2026 14:23:12 +0100 Subject: [PATCH 38/72] fix(firestore,windows): fix an issue that could happen when querying by DocumentReference value (#18053) * fix(firestore,windows): fix an issue that could happen when querying by DocumentReference value * format --- .../document_reference_e2e.dart | 34 +++++++++++++++++++ .../windows/firestore_codec.cpp | 13 ++++--- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/example/integration_test/document_reference_e2e.dart b/packages/cloud_firestore/cloud_firestore/example/integration_test/document_reference_e2e.dart index c668ea8f6e38..5a39ff84d445 100644 --- a/packages/cloud_firestore/cloud_firestore/example/integration_test/document_reference_e2e.dart +++ b/packages/cloud_firestore/cloud_firestore/example/integration_test/document_reference_e2e.dart @@ -629,5 +629,39 @@ void runDocumentReferenceTests() { timeout: const Timeout.factor(3), ); }); + + group('DocumentReference as field value', () { + // Regression test for https://github.com/firebase/flutterfire/issues/18028 + test('can store and read a DocumentReference as a field value', () async { + final doc = await initializeTest('doc-ref-field'); + final targetDoc = firestore.doc('flutter-tests/target-doc'); + + await doc.set({'ref': targetDoc}); + + final snapshot = await doc.get(); + final refValue = snapshot.data()!['ref']; + expect(refValue, isA()); + expect((refValue as DocumentReference).path, targetDoc.path); + }); + + test('can query by DocumentReference value', () async { + final collection = + firestore.collection('flutter-tests/doc-ref-query/items'); + final targetDoc = firestore.doc('flutter-tests/target-doc'); + + // Clean up + final existing = await collection.get(); + for (final doc in existing.docs) { + await doc.reference.delete(); + } + + await collection.add({'ref': targetDoc, 'name': 'test'}); + + final querySnapshot = + await collection.where('ref', isEqualTo: targetDoc).get(); + expect(querySnapshot.docs, hasLength(1)); + expect(querySnapshot.docs.first.data()['name'], 'test'); + }); + }); }); } diff --git a/packages/cloud_firestore/cloud_firestore/windows/firestore_codec.cpp b/packages/cloud_firestore/cloud_firestore/windows/firestore_codec.cpp index 573636581ae4..14be5a1776b0 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/firestore_codec.cpp +++ b/packages/cloud_firestore/cloud_firestore/windows/firestore_codec.cpp @@ -212,18 +212,23 @@ cloud_firestore_windows::FirestoreCodec::ReadValueOfType( std::get( FirestoreCodec::ReadValue(stream))); - if (CloudFirestorePlugin::firestoreInstances_.find(appName) != + // Use composite key matching GetFirestoreFromPigeon to avoid + // creating a duplicate unique_ptr for the same Firestore instance. + // See https://github.com/firebase/flutterfire/issues/18028 + std::string cacheKey = appName + "-" + databaseUrl; + + if (CloudFirestorePlugin::firestoreInstances_.find(cacheKey) != CloudFirestorePlugin::firestoreInstances_.end()) { return CustomEncodableValue( - CloudFirestorePlugin::firestoreInstances_[appName].get()); + CloudFirestorePlugin::firestoreInstances_[cacheKey].get()); } firebase::App* app = firebase::App::GetInstance(appName.c_str()); - Firestore* firestore = Firestore::GetInstance(app); + Firestore* firestore = Firestore::GetInstance(app, databaseUrl.c_str()); firestore->set_settings(settings); - CloudFirestorePlugin::firestoreInstances_[appName] = + CloudFirestorePlugin::firestoreInstances_[cacheKey] = std::unique_ptr(firestore); return CustomEncodableValue(firestore); From 3ffa411098132fd5182a84be4e7a226106bc7451 Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Tue, 3 Mar 2026 14:23:26 +0100 Subject: [PATCH 39/72] feat(ios): migrate iOS to UIScene lifecycle (#18054) * feat(ios): migrate all iOS example apps to UIScene lifecycle and add UIScene support to * feat(ios): migrate all iOS example apps to UIScene lifecycle --- .../example/ios/Runner/Info.plist | 21 +++++++++ .../example/ios/Runner/AppDelegate.h | 2 +- .../example/ios/Runner/AppDelegate.m | 6 ++- .../example/ios/Runner/Info.plist | 21 +++++++++ .../firebase_ai/example/ios/Runner/Info.plist | 21 +++++++++ .../example/ios/Runner/AppDelegate.h | 2 +- .../example/ios/Runner/AppDelegate.m | 5 ++- .../example/ios/Runner/Info.plist | 21 +++++++++ .../example/ios/Runner/AppDelegate.h | 2 +- .../example/ios/Runner/AppDelegate.m | 6 ++- .../example/ios/Runner/Info.plist | 21 +++++++++ .../example/ios/Runner/Info.plist | 21 +++++++++ .../example/ios/Runner/Info.plist | 21 +++++++++ .../firebase_auth/FLTFirebaseAuthPlugin.m | 44 +++++++++++++++++++ .../include/Public/FLTFirebaseAuthPlugin.h | 14 +++++- .../example/ios/Runner/AppDelegate.h | 2 +- .../example/ios/Runner/AppDelegate.m | 6 ++- .../example/ios/Runner/Info.plist | 21 +++++++++ .../example/ios/Runner/AppDelegate.h | 2 +- .../example/ios/Runner/AppDelegate.m | 6 ++- .../example/ios/Runner/Info.plist | 21 +++++++++ .../example/ios/Runner/Info.plist | 21 +++++++++ .../example/ios/Runner/Info.plist | 21 +++++++++ .../example/ios/Runner/AppDelegate.h | 2 +- .../example/ios/Runner/AppDelegate.m | 6 ++- .../example/ios/Runner/Info.plist | 21 +++++++++ .../example/ios/Runner/Info.plist | 21 +++++++++ .../example/ios/Runner/AppDelegate.h | 2 +- .../example/ios/Runner/AppDelegate.m | 6 ++- .../example/ios/Runner/Info.plist | 21 +++++++++ .../example/ios/Runner/AppDelegate.h | 2 +- .../example/ios/Runner/AppDelegate.m | 6 ++- .../example/ios/Runner/Info.plist | 21 +++++++++ .../example/ios/Runner/AppDelegate.h | 2 +- .../example/ios/Runner/AppDelegate.m | 5 ++- .../example/ios/Runner/Info.plist | 21 +++++++++ tests/ios/Runner/Info.plist | 21 +++++++++ 37 files changed, 459 insertions(+), 26 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/example/ios/Runner/Info.plist b/packages/cloud_firestore/cloud_firestore/example/ios/Runner/Info.plist index 5131b325e791..4326ea3865a3 100644 --- a/packages/cloud_firestore/cloud_firestore/example/ios/Runner/Info.plist +++ b/packages/cloud_firestore/cloud_firestore/example/ios/Runner/Info.plist @@ -47,5 +47,26 @@ UIApplicationSupportsIndirectInputEvents + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + diff --git a/packages/cloud_functions/cloud_functions/example/ios/Runner/AppDelegate.h b/packages/cloud_functions/cloud_functions/example/ios/Runner/AppDelegate.h index 36e21bbf9cf4..01e6e1d4793a 100644 --- a/packages/cloud_functions/cloud_functions/example/ios/Runner/AppDelegate.h +++ b/packages/cloud_functions/cloud_functions/example/ios/Runner/AppDelegate.h @@ -1,6 +1,6 @@ #import #import -@interface AppDelegate : FlutterAppDelegate +@interface AppDelegate : FlutterAppDelegate @end diff --git a/packages/cloud_functions/cloud_functions/example/ios/Runner/AppDelegate.m b/packages/cloud_functions/cloud_functions/example/ios/Runner/AppDelegate.m index 59a72e90be12..9c45e766f906 100644 --- a/packages/cloud_functions/cloud_functions/example/ios/Runner/AppDelegate.m +++ b/packages/cloud_functions/cloud_functions/example/ios/Runner/AppDelegate.m @@ -5,9 +5,11 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [GeneratedPluginRegistrant registerWithRegistry:self]; - // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } +- (void)didInitializeImplicitFlutterEngine:(NSObject *)engineBridge { + [GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry]; +} + @end diff --git a/packages/cloud_functions/cloud_functions/example/ios/Runner/Info.plist b/packages/cloud_functions/cloud_functions/example/ios/Runner/Info.plist index 73f87a4eb1fa..319a574f5ffe 100644 --- a/packages/cloud_functions/cloud_functions/example/ios/Runner/Info.plist +++ b/packages/cloud_functions/cloud_functions/example/ios/Runner/Info.plist @@ -50,5 +50,26 @@ UIApplicationSupportsIndirectInputEvents + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + diff --git a/packages/firebase_ai/firebase_ai/example/ios/Runner/Info.plist b/packages/firebase_ai/firebase_ai/example/ios/Runner/Info.plist index babcfb712863..32b85a81f400 100644 --- a/packages/firebase_ai/firebase_ai/example/ios/Runner/Info.plist +++ b/packages/firebase_ai/firebase_ai/example/ios/Runner/Info.plist @@ -49,5 +49,26 @@ We need camera access to take pictures and record video. NSMicrophoneUsageDescription We need access to the microphone to record audio. + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + diff --git a/packages/firebase_analytics/firebase_analytics/example/ios/Runner/AppDelegate.h b/packages/firebase_analytics/firebase_analytics/example/ios/Runner/AppDelegate.h index d9e18e990f2e..6020ddf58053 100644 --- a/packages/firebase_analytics/firebase_analytics/example/ios/Runner/AppDelegate.h +++ b/packages/firebase_analytics/firebase_analytics/example/ios/Runner/AppDelegate.h @@ -5,6 +5,6 @@ #import #import -@interface AppDelegate : FlutterAppDelegate +@interface AppDelegate : FlutterAppDelegate @end diff --git a/packages/firebase_analytics/firebase_analytics/example/ios/Runner/AppDelegate.m b/packages/firebase_analytics/firebase_analytics/example/ios/Runner/AppDelegate.m index a4b51c88eb60..b915a48d031c 100644 --- a/packages/firebase_analytics/firebase_analytics/example/ios/Runner/AppDelegate.m +++ b/packages/firebase_analytics/firebase_analytics/example/ios/Runner/AppDelegate.m @@ -9,8 +9,11 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [GeneratedPluginRegistrant registerWithRegistry:self]; return [super application:application didFinishLaunchingWithOptions:launchOptions]; } +- (void)didInitializeImplicitFlutterEngine:(NSObject *)engineBridge { + [GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry]; +} + @end diff --git a/packages/firebase_analytics/firebase_analytics/example/ios/Runner/Info.plist b/packages/firebase_analytics/firebase_analytics/example/ios/Runner/Info.plist index 76eb7953efc4..248bf3883db1 100755 --- a/packages/firebase_analytics/firebase_analytics/example/ios/Runner/Info.plist +++ b/packages/firebase_analytics/firebase_analytics/example/ios/Runner/Info.plist @@ -49,5 +49,26 @@ UIApplicationSupportsIndirectInputEvents + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + diff --git a/packages/firebase_app_check/firebase_app_check/example/ios/Runner/AppDelegate.h b/packages/firebase_app_check/firebase_app_check/example/ios/Runner/AppDelegate.h index 36e21bbf9cf4..01e6e1d4793a 100644 --- a/packages/firebase_app_check/firebase_app_check/example/ios/Runner/AppDelegate.h +++ b/packages/firebase_app_check/firebase_app_check/example/ios/Runner/AppDelegate.h @@ -1,6 +1,6 @@ #import #import -@interface AppDelegate : FlutterAppDelegate +@interface AppDelegate : FlutterAppDelegate @end diff --git a/packages/firebase_app_check/firebase_app_check/example/ios/Runner/AppDelegate.m b/packages/firebase_app_check/firebase_app_check/example/ios/Runner/AppDelegate.m index b6e4f92f4e00..7171162c055c 100644 --- a/packages/firebase_app_check/firebase_app_check/example/ios/Runner/AppDelegate.m +++ b/packages/firebase_app_check/firebase_app_check/example/ios/Runner/AppDelegate.m @@ -6,9 +6,11 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [GeneratedPluginRegistrant registerWithRegistry:self]; - // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } +- (void)didInitializeImplicitFlutterEngine:(NSObject *)engineBridge { + [GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry]; +} + @end diff --git a/packages/firebase_app_check/firebase_app_check/example/ios/Runner/Info.plist b/packages/firebase_app_check/firebase_app_check/example/ios/Runner/Info.plist index 4c88b5e5d4c8..3a8a7a2f7159 100644 --- a/packages/firebase_app_check/firebase_app_check/example/ios/Runner/Info.plist +++ b/packages/firebase_app_check/firebase_app_check/example/ios/Runner/Info.plist @@ -45,5 +45,26 @@ UIApplicationSupportsIndirectInputEvents + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + diff --git a/packages/firebase_app_installations/firebase_app_installations/example/ios/Runner/Info.plist b/packages/firebase_app_installations/firebase_app_installations/example/ios/Runner/Info.plist index 53f00185bf94..5080e4ae257c 100644 --- a/packages/firebase_app_installations/firebase_app_installations/example/ios/Runner/Info.plist +++ b/packages/firebase_app_installations/firebase_app_installations/example/ios/Runner/Info.plist @@ -47,5 +47,26 @@ UIApplicationSupportsIndirectInputEvents + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + diff --git a/packages/firebase_auth/firebase_auth/example/ios/Runner/Info.plist b/packages/firebase_auth/firebase_auth/example/ios/Runner/Info.plist index 1cc8cb984344..e1ffa4fee645 100644 --- a/packages/firebase_auth/firebase_auth/example/ios/Runner/Info.plist +++ b/packages/firebase_auth/firebase_auth/example/ios/Runner/Info.plist @@ -73,5 +73,26 @@ UIApplicationSupportsIndirectInputEvents + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + diff --git a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/FLTFirebaseAuthPlugin.m b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/FLTFirebaseAuthPlugin.m index c55ea4144e03..ee84d102644d 100644 --- a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/FLTFirebaseAuthPlugin.m +++ b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/FLTFirebaseAuthPlugin.m @@ -148,6 +148,13 @@ + (void)registerWithRegistrar:(NSObject *)registrar { [registrar publish:instance]; [registrar addApplicationDelegate:instance]; +#if !TARGET_OS_OSX + if (@available(iOS 13.0, *)) { + if ([registrar respondsToSelector:@selector(addSceneDelegate:)]) { + [registrar performSelector:@selector(addSceneDelegate:) withObject:instance]; + } + } +#endif SetUpFirebaseAuthHostApi(registrar.messenger, instance); SetUpFirebaseAuthUserHostApi(registrar.messenger, instance); SetUpMultiFactorUserHostApi(registrar.messenger, instance); @@ -274,6 +281,18 @@ - (void)application:(UIApplication *)application - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { return [[FIRAuth auth] canHandleURL:url]; } + +#pragma mark - SceneDelegate + +- (BOOL)scene:(UIScene *)scene + openURLContexts:(NSSet *)URLContexts API_AVAILABLE(ios(13.0)) { + for (UIOpenURLContext *urlContext in URLContexts) { + if ([[FIRAuth auth] canHandleURL:urlContext.URL]) { + return YES; + } + } + return NO; +} #endif #pragma mark - FLTFirebasePlugin @@ -831,6 +850,31 @@ - (nonnull ASPresentationAnchor)presentationAnchorForAuthorizationController: #if TARGET_OS_OSX return [[NSApplication sharedApplication] keyWindow]; #else + // UIApplication.keyWindow is deprecated in iOS 13+ with UIScene lifecycle. + // Walk the connected scenes to find the foreground active window. + if (@available(iOS 15.0, *)) { + for (UIScene *scene in [UIApplication sharedApplication].connectedScenes) { + if (scene.activationState == UISceneActivationStateForegroundActive && + [scene isKindOfClass:[UIWindowScene class]]) { + UIWindowScene *windowScene = (UIWindowScene *)scene; + if (windowScene.keyWindow) { + return windowScene.keyWindow; + } + } + } + } else if (@available(iOS 13.0, *)) { + for (UIScene *scene in [UIApplication sharedApplication].connectedScenes) { + if (scene.activationState == UISceneActivationStateForegroundActive && + [scene isKindOfClass:[UIWindowScene class]]) { + UIWindowScene *windowScene = (UIWindowScene *)scene; + for (UIWindow *window in windowScene.windows) { + if (window.isKeyWindow) { + return window; + } + } + } + } + } return [[UIApplication sharedApplication] keyWindow]; #endif } diff --git a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/include/Public/FLTFirebaseAuthPlugin.h b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/include/Public/FLTFirebaseAuthPlugin.h index ea4a0168a18d..552728aab958 100644 --- a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/include/Public/FLTFirebaseAuthPlugin.h +++ b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/include/Public/FLTFirebaseAuthPlugin.h @@ -19,6 +19,10 @@ #endif #import "firebase_auth_messages.g.h" +#if !TARGET_OS_OSX +@protocol FlutterSceneLifeCycleDelegate; +#endif + @interface FLTFirebaseAuthPlugin : FLTFirebasePlugin + ASAuthorizationControllerPresentationContextProviding +#if !TARGET_OS_OSX +#if __has_include() || \ + defined(FlutterSceneLifeCycleDelegate) + , + FlutterSceneLifeCycleDelegate +#endif +#endif + > + (FlutterError *)convertToFlutterError:(NSError *)error; @end diff --git a/packages/firebase_core/firebase_core/example/ios/Runner/AppDelegate.h b/packages/firebase_core/firebase_core/example/ios/Runner/AppDelegate.h index 36e21bbf9cf4..01e6e1d4793a 100644 --- a/packages/firebase_core/firebase_core/example/ios/Runner/AppDelegate.h +++ b/packages/firebase_core/firebase_core/example/ios/Runner/AppDelegate.h @@ -1,6 +1,6 @@ #import #import -@interface AppDelegate : FlutterAppDelegate +@interface AppDelegate : FlutterAppDelegate @end diff --git a/packages/firebase_core/firebase_core/example/ios/Runner/AppDelegate.m b/packages/firebase_core/firebase_core/example/ios/Runner/AppDelegate.m index 70e83933db14..90e3db78c8bb 100644 --- a/packages/firebase_core/firebase_core/example/ios/Runner/AppDelegate.m +++ b/packages/firebase_core/firebase_core/example/ios/Runner/AppDelegate.m @@ -5,9 +5,11 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [GeneratedPluginRegistrant registerWithRegistry:self]; - // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } +- (void)didInitializeImplicitFlutterEngine:(NSObject *)engineBridge { + [GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry]; +} + @end diff --git a/packages/firebase_core/firebase_core/example/ios/Runner/Info.plist b/packages/firebase_core/firebase_core/example/ios/Runner/Info.plist index b66b3b91d998..e88ddaa2946e 100644 --- a/packages/firebase_core/firebase_core/example/ios/Runner/Info.plist +++ b/packages/firebase_core/firebase_core/example/ios/Runner/Info.plist @@ -45,5 +45,26 @@ UIApplicationSupportsIndirectInputEvents + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + diff --git a/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/AppDelegate.h b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/AppDelegate.h index 36e21bbf9cf4..01e6e1d4793a 100644 --- a/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/AppDelegate.h +++ b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/AppDelegate.h @@ -1,6 +1,6 @@ #import #import -@interface AppDelegate : FlutterAppDelegate +@interface AppDelegate : FlutterAppDelegate @end diff --git a/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/AppDelegate.m b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/AppDelegate.m index 59a72e90be12..9c45e766f906 100644 --- a/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/AppDelegate.m +++ b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/AppDelegate.m @@ -5,9 +5,11 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [GeneratedPluginRegistrant registerWithRegistry:self]; - // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } +- (void)didInitializeImplicitFlutterEngine:(NSObject *)engineBridge { + [GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry]; +} + @end diff --git a/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Info.plist b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Info.plist index bb85eb3fe72b..8efef89a5b8a 100644 --- a/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Info.plist +++ b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Info.plist @@ -45,5 +45,26 @@ UIApplicationSupportsIndirectInputEvents + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + diff --git a/packages/firebase_data_connect/firebase_data_connect/example/ios/Runner/Info.plist b/packages/firebase_data_connect/firebase_data_connect/example/ios/Runner/Info.plist index fcfd18a5b8a3..f7b4b42c7db6 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/ios/Runner/Info.plist +++ b/packages/firebase_data_connect/firebase_data_connect/example/ios/Runner/Info.plist @@ -58,5 +58,26 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + diff --git a/packages/firebase_database/firebase_database/example/ios/Runner/Info.plist b/packages/firebase_database/firebase_database/example/ios/Runner/Info.plist index 5458fc4188bf..52cf36cf82eb 100644 --- a/packages/firebase_database/firebase_database/example/ios/Runner/Info.plist +++ b/packages/firebase_database/firebase_database/example/ios/Runner/Info.plist @@ -45,5 +45,26 @@ UIApplicationSupportsIndirectInputEvents + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + diff --git a/packages/firebase_in_app_messaging/firebase_in_app_messaging/example/ios/Runner/AppDelegate.h b/packages/firebase_in_app_messaging/firebase_in_app_messaging/example/ios/Runner/AppDelegate.h index 36e21bbf9cf4..01e6e1d4793a 100644 --- a/packages/firebase_in_app_messaging/firebase_in_app_messaging/example/ios/Runner/AppDelegate.h +++ b/packages/firebase_in_app_messaging/firebase_in_app_messaging/example/ios/Runner/AppDelegate.h @@ -1,6 +1,6 @@ #import #import -@interface AppDelegate : FlutterAppDelegate +@interface AppDelegate : FlutterAppDelegate @end diff --git a/packages/firebase_in_app_messaging/firebase_in_app_messaging/example/ios/Runner/AppDelegate.m b/packages/firebase_in_app_messaging/firebase_in_app_messaging/example/ios/Runner/AppDelegate.m index 59a72e90be12..9c45e766f906 100644 --- a/packages/firebase_in_app_messaging/firebase_in_app_messaging/example/ios/Runner/AppDelegate.m +++ b/packages/firebase_in_app_messaging/firebase_in_app_messaging/example/ios/Runner/AppDelegate.m @@ -5,9 +5,11 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [GeneratedPluginRegistrant registerWithRegistry:self]; - // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } +- (void)didInitializeImplicitFlutterEngine:(NSObject *)engineBridge { + [GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry]; +} + @end diff --git a/packages/firebase_in_app_messaging/firebase_in_app_messaging/example/ios/Runner/Info.plist b/packages/firebase_in_app_messaging/firebase_in_app_messaging/example/ios/Runner/Info.plist index bd55b3460d46..c6ae23d68982 100644 --- a/packages/firebase_in_app_messaging/firebase_in_app_messaging/example/ios/Runner/Info.plist +++ b/packages/firebase_in_app_messaging/firebase_in_app_messaging/example/ios/Runner/Info.plist @@ -45,5 +45,26 @@ UIViewControllerBasedStatusBarAppearance + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + diff --git a/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/example/ios/Runner/Info.plist b/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/example/ios/Runner/Info.plist index 71d372f7c1c9..15835549ad8c 100644 --- a/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/example/ios/Runner/Info.plist +++ b/packages/firebase_ml_model_downloader/firebase_ml_model_downloader/example/ios/Runner/Info.plist @@ -45,5 +45,26 @@ UIApplicationSupportsIndirectInputEvents + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + diff --git a/packages/firebase_performance/firebase_performance/example/ios/Runner/AppDelegate.h b/packages/firebase_performance/firebase_performance/example/ios/Runner/AppDelegate.h index 36e21bbf9cf4..01e6e1d4793a 100644 --- a/packages/firebase_performance/firebase_performance/example/ios/Runner/AppDelegate.h +++ b/packages/firebase_performance/firebase_performance/example/ios/Runner/AppDelegate.h @@ -1,6 +1,6 @@ #import #import -@interface AppDelegate : FlutterAppDelegate +@interface AppDelegate : FlutterAppDelegate @end diff --git a/packages/firebase_performance/firebase_performance/example/ios/Runner/AppDelegate.m b/packages/firebase_performance/firebase_performance/example/ios/Runner/AppDelegate.m index 59a72e90be12..9c45e766f906 100644 --- a/packages/firebase_performance/firebase_performance/example/ios/Runner/AppDelegate.m +++ b/packages/firebase_performance/firebase_performance/example/ios/Runner/AppDelegate.m @@ -5,9 +5,11 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [GeneratedPluginRegistrant registerWithRegistry:self]; - // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } +- (void)didInitializeImplicitFlutterEngine:(NSObject *)engineBridge { + [GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry]; +} + @end diff --git a/packages/firebase_performance/firebase_performance/example/ios/Runner/Info.plist b/packages/firebase_performance/firebase_performance/example/ios/Runner/Info.plist index fa3d95238741..93ccf4a11c44 100644 --- a/packages/firebase_performance/firebase_performance/example/ios/Runner/Info.plist +++ b/packages/firebase_performance/firebase_performance/example/ios/Runner/Info.plist @@ -47,5 +47,26 @@ CADisableMinimumFrameDurationOnPhone + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + diff --git a/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/AppDelegate.h b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/AppDelegate.h index 36e21bbf9cf4..01e6e1d4793a 100644 --- a/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/AppDelegate.h +++ b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/AppDelegate.h @@ -1,6 +1,6 @@ #import #import -@interface AppDelegate : FlutterAppDelegate +@interface AppDelegate : FlutterAppDelegate @end diff --git a/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/AppDelegate.m b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/AppDelegate.m index 59a72e90be12..9c45e766f906 100644 --- a/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/AppDelegate.m +++ b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/AppDelegate.m @@ -5,9 +5,11 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [GeneratedPluginRegistrant registerWithRegistry:self]; - // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } +- (void)didInitializeImplicitFlutterEngine:(NSObject *)engineBridge { + [GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry]; +} + @end diff --git a/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Info.plist b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Info.plist index c8812fec2507..d47a3d12a1e1 100644 --- a/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Info.plist +++ b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Info.plist @@ -49,5 +49,26 @@ UIApplicationSupportsIndirectInputEvents + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + diff --git a/packages/firebase_storage/firebase_storage/example/ios/Runner/AppDelegate.h b/packages/firebase_storage/firebase_storage/example/ios/Runner/AppDelegate.h index d9e18e990f2e..6020ddf58053 100644 --- a/packages/firebase_storage/firebase_storage/example/ios/Runner/AppDelegate.h +++ b/packages/firebase_storage/firebase_storage/example/ios/Runner/AppDelegate.h @@ -5,6 +5,6 @@ #import #import -@interface AppDelegate : FlutterAppDelegate +@interface AppDelegate : FlutterAppDelegate @end diff --git a/packages/firebase_storage/firebase_storage/example/ios/Runner/AppDelegate.m b/packages/firebase_storage/firebase_storage/example/ios/Runner/AppDelegate.m index a4b51c88eb60..b915a48d031c 100644 --- a/packages/firebase_storage/firebase_storage/example/ios/Runner/AppDelegate.m +++ b/packages/firebase_storage/firebase_storage/example/ios/Runner/AppDelegate.m @@ -9,8 +9,11 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [GeneratedPluginRegistrant registerWithRegistry:self]; return [super application:application didFinishLaunchingWithOptions:launchOptions]; } +- (void)didInitializeImplicitFlutterEngine:(NSObject *)engineBridge { + [GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry]; +} + @end diff --git a/packages/firebase_storage/firebase_storage/example/ios/Runner/Info.plist b/packages/firebase_storage/firebase_storage/example/ios/Runner/Info.plist index 364cf68705cd..21ed54231166 100755 --- a/packages/firebase_storage/firebase_storage/example/ios/Runner/Info.plist +++ b/packages/firebase_storage/firebase_storage/example/ios/Runner/Info.plist @@ -60,5 +60,26 @@ UIApplicationSupportsIndirectInputEvents + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + diff --git a/tests/ios/Runner/Info.plist b/tests/ios/Runner/Info.plist index 3dd732018fc0..79a2b4c4b828 100644 --- a/tests/ios/Runner/Info.plist +++ b/tests/ios/Runner/Info.plist @@ -69,5 +69,26 @@ UIApplicationSupportsIndirectInputEvents + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + From a6a0554d011d0490e6ed22d576aabdbc40a9364b Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Tue, 3 Mar 2026 14:23:40 +0100 Subject: [PATCH 40/72] fix(auth, android): fix an error casing that wasn't consistent accross platforms (#18056) --- .../lib/src/method_channel/utils/exception.dart | 2 +- .../test/method_channel_tests/utils_tests/exception_test.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/method_channel/utils/exception.dart b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/method_channel/utils/exception.dart index 583a43f273dc..f78a4e99669d 100644 --- a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/method_channel/utils/exception.dart +++ b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/method_channel/utils/exception.dart @@ -154,7 +154,7 @@ String? _getCustomCode(Map? additionalData, String? message) { for (final recognizedCode in listOfRecognizedCode) { if (additionalData?['message'] == recognizedCode || (message?.contains(recognizedCode) ?? false)) { - return recognizedCode; + return recognizedCode.toLowerCase().replaceAll('_', '-'); } } diff --git a/packages/firebase_auth/firebase_auth_platform_interface/test/method_channel_tests/utils_tests/exception_test.dart b/packages/firebase_auth/firebase_auth_platform_interface/test/method_channel_tests/utils_tests/exception_test.dart index 43c00f88eff8..2fdfb6df6271 100644 --- a/packages/firebase_auth/firebase_auth_platform_interface/test/method_channel_tests/utils_tests/exception_test.dart +++ b/packages/firebase_auth/firebase_auth_platform_interface/test/method_channel_tests/utils_tests/exception_test.dart @@ -44,7 +44,7 @@ void main() { () => convertPlatformException(platformException, StackTrace.empty), throwsA( isA() - .having((e) => e.code, 'code', 'BLOCKING_FUNCTION_ERROR_RESPONSE') + .having((e) => e.code, 'code', 'blocking-function-error-response') .having((e) => e.message, 'message', '{"error":{"details":"The user is not allowed to log in","message":"","status":"PERMISSION_DENIED"}}'), ), From 65dbd4bd3995411a14d4efcf35c945cf344e56a9 Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Tue, 3 Mar 2026 15:02:01 +0100 Subject: [PATCH 41/72] feat(analytics,ios): add support for FirebaseAnalyticsWithoutAdIdSupport with SPM (#18061) --- docs/analytics/_get-started.md | 26 +++++++++++++++++++ .../ios/firebase_analytics/Package.swift | 7 ++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/docs/analytics/_get-started.md b/docs/analytics/_get-started.md index b4460dfd9bf1..78bbe531e1e4 100644 --- a/docs/analytics/_get-started.md +++ b/docs/analytics/_get-started.md @@ -83,6 +83,32 @@ await FirebaseAnalytics.instance ); ``` +## Using Analytics without Ad ID support (iOS) {:#without-ad-id} + +If your app doesn't use IDFA, you can use `FirebaseAnalyticsWithoutAdIdSupport` +instead of the default `FirebaseAnalytics` iOS dependency to avoid App Store +review questions about advertising identifiers. + +### Swift Package Manager + +Set the `FIREBASE_ANALYTICS_WITHOUT_ADID` environment variable when building: + +```bash +FIREBASE_ANALYTICS_WITHOUT_ADID=true flutter build ios +``` + +You can also add this variable to your Xcode scheme's environment variables +for persistent configuration. + +### CocoaPods + +Add this to your app's `Podfile`: + +```ruby +pod 'FirebaseAnalytics', :modular_headers => true +pod 'FirebaseAnalyticsWithoutAdIdSupport', :modular_headers => true +``` + ## Next steps * Use the [DebugView](/docs/analytics/debugview) to verify your events. diff --git a/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/Package.swift b/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/Package.swift index 0e717ea4144b..6483af79396a 100644 --- a/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/Package.swift +++ b/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/Package.swift @@ -79,6 +79,11 @@ guard let shared_spm_version = Version("\(firebase_core_version_string)\(shared_ fatalError("Invalid firebase_core version: \(firebase_core_version_string)\(shared_spm_tag)") } +// Set FIREBASE_ANALYTICS_WITHOUT_ADID=true to use FirebaseAnalyticsWithoutAdIdSupport +// e.g. FIREBASE_ANALYTICS_WITHOUT_ADID=true flutter build ios +let useWithoutAdId = ProcessInfo.processInfo.environment["FIREBASE_ANALYTICS_WITHOUT_ADID"] != nil +let analyticsProduct = useWithoutAdId ? "FirebaseAnalyticsWithoutAdIdSupport" : "FirebaseAnalytics" + let package = Package( name: "firebase_analytics", platforms: [ @@ -95,7 +100,7 @@ let package = Package( .target( name: "firebase_analytics", dependencies: [ - .product(name: "FirebaseAnalytics", package: "firebase-ios-sdk"), + .product(name: analyticsProduct, package: "firebase-ios-sdk"), // Wrapper dependency .product(name: "firebase-core-shared", package: "flutterfire"), ], From 1e39ad1f146ce23742731ceeb30ff36c440b816f Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Tue, 3 Mar 2026 15:18:32 +0100 Subject: [PATCH 42/72] fix(android): remove kotlin-android since AGP 9 supports it (#18059) * fix(android): remove kotlin-android since AGP 9 supports it * cleaning * clean --- .../cloud_functions/android/build.gradle | 20 ++++++++++++------- .../firebase_analytics/android/build.gradle | 13 +++++++++--- .../firebase_database/android/build.gradle | 13 +++++++++--- .../firebase_performance/android/build.gradle | 17 +++++++++++----- .../android/build.gradle | 12 ++++++++--- .../firebase_storage/android/build.gradle | 12 ++++++++--- 6 files changed, 63 insertions(+), 24 deletions(-) diff --git a/packages/cloud_functions/cloud_functions/android/build.gradle b/packages/cloud_functions/cloud_functions/android/build.gradle index 763d75805fda..9d1295a9567c 100644 --- a/packages/cloud_functions/cloud_functions/android/build.gradle +++ b/packages/cloud_functions/cloud_functions/android/build.gradle @@ -3,7 +3,12 @@ version '1.0-SNAPSHOT' apply plugin: 'com.android.library' apply from: file("local-config.gradle") -apply plugin: 'kotlin-android' + +// AGP 9+ has built-in Kotlin support; older versions need the plugin explicitly. +def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0] as int +if (agpMajor < 9) { + apply plugin: 'kotlin-android' +} buildscript { ext.kotlin_version = "1.8.22" @@ -56,6 +61,12 @@ android { targetCompatibility project.ext.javaVersion } + if (agpMajor < 9) { + kotlinOptions { + jvmTarget = project.ext.javaVersion + } + } + sourceSets { main.java.srcDirs += "src/main/kotlin" test.java.srcDirs += "src/test/kotlin" @@ -77,11 +88,6 @@ android { implementation 'org.reactivestreams:reactive-streams:1.0.4' } - kotlinOptions { - jvmTarget = JavaVersion.VERSION_17 - } - } -apply from: file("./user-agent.gradle") -apply plugin: 'org.jetbrains.kotlin.android' \ No newline at end of file +apply from: file("./user-agent.gradle") \ No newline at end of file diff --git a/packages/firebase_analytics/firebase_analytics/android/build.gradle b/packages/firebase_analytics/firebase_analytics/android/build.gradle index 41533c10483d..183ea4605c04 100755 --- a/packages/firebase_analytics/firebase_analytics/android/build.gradle +++ b/packages/firebase_analytics/firebase_analytics/android/build.gradle @@ -25,7 +25,12 @@ rootProject.allprojects { } apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' + +// AGP 9+ has built-in Kotlin support; older versions need the plugin explicitly. +def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0] as int +if (agpMajor < 9) { + apply plugin: 'kotlin-android' +} def firebaseCoreProject = findProject(':firebase_core') if (firebaseCoreProject == null) { @@ -53,8 +58,10 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } - kotlinOptions { - jvmTarget = JavaVersion.VERSION_17 + if (agpMajor < 9) { + kotlinOptions { + jvmTarget = project.ext.javaVersion + } } compileOptions { diff --git a/packages/firebase_database/firebase_database/android/build.gradle b/packages/firebase_database/firebase_database/android/build.gradle index f49c3bf4250f..4826a7d16f7a 100755 --- a/packages/firebase_database/firebase_database/android/build.gradle +++ b/packages/firebase_database/firebase_database/android/build.gradle @@ -2,9 +2,14 @@ group 'io.flutter.plugins.firebase.database' version '1.0-SNAPSHOT' apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' apply from: file("local-config.gradle") +// AGP 9+ has built-in Kotlin support; older versions need the plugin explicitly. +def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0] as int +if (agpMajor < 9) { + apply plugin: 'kotlin-android' +} + buildscript { repositories { google() @@ -50,8 +55,10 @@ android { targetCompatibility project.ext.javaVersion } - kotlinOptions { - jvmTarget = project.ext.javaVersion + if (agpMajor < 9) { + kotlinOptions { + jvmTarget = project.ext.javaVersion + } } sourceSets { diff --git a/packages/firebase_performance/firebase_performance/android/build.gradle b/packages/firebase_performance/firebase_performance/android/build.gradle index 682805cda15f..6d38c5620117 100644 --- a/packages/firebase_performance/firebase_performance/android/build.gradle +++ b/packages/firebase_performance/firebase_performance/android/build.gradle @@ -3,7 +3,12 @@ version '1.0-SNAPSHOT' apply plugin: 'com.android.library' apply from: file("local-config.gradle") -apply plugin: 'kotlin-android' + +// AGP 9+ has built-in Kotlin support; older versions need the plugin explicitly. +def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0] as int +if (agpMajor < 9) { + apply plugin: 'kotlin-android' +} buildscript { ext.kotlin_version = "1.8.22" @@ -55,15 +60,17 @@ android { targetCompatibility project.ext.javaVersion } + if (agpMajor < 9) { + kotlinOptions { + jvmTarget = project.ext.javaVersion + } + } + sourceSets { main.java.srcDirs += "src/main/kotlin" test.java.srcDirs += "src/test/kotlin" } - kotlinOptions { - jvmTarget = project.ext.javaVersion - } - buildFeatures { buildConfig true } diff --git a/packages/firebase_remote_config/firebase_remote_config/android/build.gradle b/packages/firebase_remote_config/firebase_remote_config/android/build.gradle index 41fc6f44f3d2..a03f8e444446 100644 --- a/packages/firebase_remote_config/firebase_remote_config/android/build.gradle +++ b/packages/firebase_remote_config/firebase_remote_config/android/build.gradle @@ -19,7 +19,11 @@ rootProject.allprojects { } } -apply plugin: 'kotlin-android' +// AGP 9+ has built-in Kotlin support; older versions need the plugin explicitly. +def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0] as int +if (agpMajor < 9) { + apply plugin: 'kotlin-android' +} def firebaseCoreProject = findProject(':firebase_core') if (firebaseCoreProject == null) { @@ -47,8 +51,10 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } - kotlinOptions { - jvmTarget = JavaVersion.VERSION_17 + if (agpMajor < 9) { + kotlinOptions { + jvmTarget = project.ext.javaVersion + } } compileOptions { diff --git a/packages/firebase_storage/firebase_storage/android/build.gradle b/packages/firebase_storage/firebase_storage/android/build.gradle index 7ef42fddf333..c9e5b5e5940d 100755 --- a/packages/firebase_storage/firebase_storage/android/build.gradle +++ b/packages/firebase_storage/firebase_storage/android/build.gradle @@ -44,7 +44,11 @@ def getRootProjectExtOrCoreProperty(name, firebaseCoreProject) { return rootProject.ext.get('FlutterFire').get(name) } -apply plugin: 'kotlin-android' +// AGP 9+ has built-in Kotlin support; older versions need the plugin explicitly. +def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0] as int +if (agpMajor < 9) { + apply plugin: 'kotlin-android' +} android { // Conditional for compatibility with AGP <4.2. @@ -59,8 +63,10 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } - kotlinOptions { - jvmTarget = project.ext.javaVersion + if (agpMajor < 9) { + kotlinOptions { + jvmTarget = project.ext.javaVersion + } } compileOptions { From 5b602105faf9f64ac977a4266de5ee10785330bd Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Tue, 3 Mar 2026 15:26:43 +0100 Subject: [PATCH 43/72] fix(messaging,ios): fix an issue where the scene initializer could be called twice in latest Flutter versions (#18051) * fix(messaging,ios): fix an issue where the scene initializer could be called twice in latest Flutter versions * format --- .../FLTFirebaseMessagingPlugin.m | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/firebase_messaging/firebase_messaging/ios/firebase_messaging/Sources/firebase_messaging/FLTFirebaseMessagingPlugin.m b/packages/firebase_messaging/firebase_messaging/ios/firebase_messaging/Sources/firebase_messaging/FLTFirebaseMessagingPlugin.m index 76879a85c526..2dd3f536edcb 100644 --- a/packages/firebase_messaging/firebase_messaging/ios/firebase_messaging/Sources/firebase_messaging/FLTFirebaseMessagingPlugin.m +++ b/packages/firebase_messaging/firebase_messaging/ios/firebase_messaging/Sources/firebase_messaging/FLTFirebaseMessagingPlugin.m @@ -43,6 +43,9 @@ @implementation FLTFirebaseMessagingPlugin { // Track if scene delegate connected (for iOS 13+ scene delegate support) BOOL _sceneDidConnect; + // Guard against calling setupNotificationHandling twice + BOOL _notificationHandlingSetup; + #ifdef __FF_NOTIFICATIONS_SUPPORTED_PLATFORM API_AVAILABLE(ios(10), macosx(10.14)) __weak id _originalNotificationCenterDelegate; @@ -63,6 +66,7 @@ - (instancetype)initWithFlutterMethodChannel:(FlutterMethodChannel *)channel if (self) { _initialNotificationGathered = NO; _sceneDidConnect = NO; + _notificationHandlingSetup = NO; _channel = channel; _registrar = registrar; // Application @@ -222,6 +226,32 @@ - (void)messaging:(nonnull FIRMessaging *)messaging - (void)setupNotificationHandlingWithRemoteNotification: (nullable NSDictionary *)remoteNotification { + // If notification handling was already set up (e.g. from + // application_onDidFinishLaunchingNotification) and we're called again (e.g. from + // scene:willConnectToSession:), only process the notification but skip delegate/swizzler + // re-registration to avoid _originalNotificationCenterDelegate being set to self, which causes + // infinite recursion in didReceiveNotificationResponse. See #18037. + if (_notificationHandlingSetup) { + if (remoteNotification != nil) { + _initialNotification = + [FLTFirebaseMessagingPlugin remoteMessageUserInfoToDict:remoteNotification]; + _initialNotificationID = remoteNotification[@"gcm.message_id"]; + _initialNotificationGathered = YES; + [self initialNotificationCallback]; + } else if (_sceneDidConnect && !_initialNotificationGathered) { + // Scene connected with no notification — delay to allow didReceiveRemoteNotification + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + if (!self->_initialNotificationGathered) { + self->_initialNotificationGathered = YES; + [self initialNotificationCallback]; + } + }); + } + return; + } + _notificationHandlingSetup = YES; + if (remoteNotification != nil) { // If remoteNotification exists, it is the notification that opened the app. _initialNotification = From 5db577116139d469bcdf38dd58f69c1e5f61c87e Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Tue, 3 Mar 2026 15:26:49 +0100 Subject: [PATCH 44/72] fix(remote-config, ios): fix hot reload issue (#18062) --- .../firebase_remote_config/example/lib/home_page.dart | 4 ++-- .../firebase_remote_config/FirebaseRemoteConfigPlugin.swift | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/firebase_remote_config/firebase_remote_config/example/lib/home_page.dart b/packages/firebase_remote_config/firebase_remote_config/example/lib/home_page.dart index a47174ac6269..dacdfdc8c61a 100644 --- a/packages/firebase_remote_config/firebase_remote_config/example/lib/home_page.dart +++ b/packages/firebase_remote_config/firebase_remote_config/example/lib/home_page.dart @@ -176,8 +176,8 @@ class _ButtonAndTextState extends State<_ButtonAndText> { padding: const EdgeInsets.all(8), child: Row( children: [ - Text(_text ?? widget.defaultText), - const Spacer(), + Expanded(child: Text(_text ?? widget.defaultText)), + const SizedBox(width: 8), ElevatedButton( onPressed: () async { final result = await widget.onPressed(); diff --git a/packages/firebase_remote_config/firebase_remote_config/ios/firebase_remote_config/Sources/firebase_remote_config/FirebaseRemoteConfigPlugin.swift b/packages/firebase_remote_config/firebase_remote_config/ios/firebase_remote_config/Sources/firebase_remote_config/FirebaseRemoteConfigPlugin.swift index db00d033998e..10a80253147c 100644 --- a/packages/firebase_remote_config/firebase_remote_config/ios/firebase_remote_config/Sources/firebase_remote_config/FirebaseRemoteConfigPlugin.swift +++ b/packages/firebase_remote_config/firebase_remote_config/ios/firebase_remote_config/Sources/firebase_remote_config/FirebaseRemoteConfigPlugin.swift @@ -60,6 +60,10 @@ public class FirebaseRemoteConfigPlugin: NSObject, FlutterPlugin, FlutterStreamH } public func didReinitializeFirebaseCore(_ completion: @escaping () -> Void) { + for listener in listenersMap.values { + listener.remove() + } + listenersMap.removeAll() completion() } From e1a93a0501d580c93f055c8edbe625534730bab0 Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Tue, 3 Mar 2026 15:27:03 +0100 Subject: [PATCH 45/72] fix(firestore, web): fix an issue where DocumentReference couldn't be read properly in web (#18058) --- .../integration_test/document_reference_e2e.dart | 13 +++++++++++++ .../lib/src/utils/codec_utility.dart | 7 +++++-- .../lib/src/utils/encode_utility.dart | 12 ++++++++---- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/example/integration_test/document_reference_e2e.dart b/packages/cloud_firestore/cloud_firestore/example/integration_test/document_reference_e2e.dart index 5a39ff84d445..5e570f983331 100644 --- a/packages/cloud_firestore/cloud_firestore/example/integration_test/document_reference_e2e.dart +++ b/packages/cloud_firestore/cloud_firestore/example/integration_test/document_reference_e2e.dart @@ -456,6 +456,19 @@ void runDocumentReferenceTests() { expect(data['infinity'], equals(double.infinity)); expect(data['negative_infinity'], equals(double.negativeInfinity)); }); + + test('sets data with DocumentReference as map key', () async { + DocumentReference> document = + await initializeTest('document-set-ref-key'); + DocumentReference> refKey = + FirebaseFirestore.instance.doc('foo/bar'); + await document.set({ + 'myMap': {refKey: 42.0}, + }); + DocumentSnapshot> snapshot = await document.get(); + final myMap = snapshot.data()!['myMap'] as Map; + expect(myMap[refKey.path], equals(42.0)); + }); }); group('DocumentReference.update()', () { diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/utils/codec_utility.dart b/packages/cloud_firestore/cloud_firestore/lib/src/utils/codec_utility.dart index 2c704fe861c6..2e305ce16393 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/utils/codec_utility.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/utils/codec_utility.dart @@ -17,8 +17,11 @@ class _CodecUtility { if (data == null) { return null; } - Map output = Map.from(data); - output.updateAll((_, value) => valueEncode(value)); + final output = {}; + data.forEach((key, value) { + final stringKey = key is DocumentReference ? key.path : key as String; + output[stringKey] = valueEncode(value); + }); return output; } diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/utils/encode_utility.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/utils/encode_utility.dart index defa2f1fb702..ca5f5bf5ea4b 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/utils/encode_utility.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/utils/encode_utility.dart @@ -18,8 +18,12 @@ class EncodeUtility { if (data == null) { return null; } - Map output = Map.from(data); - output.updateAll((key, value) => valueEncode(value)); + final output = {}; + data.forEach((key, value) { + final stringKey = + key is DocumentReferencePlatform ? key.path : key as String; + output[stringKey] = valueEncode(value); + }); return output; } @@ -126,8 +130,8 @@ class EncodeUtility { return firestore_interop.BytesJsImpl.fromUint8Array(value.bytes.toJS); } else if (value is DocumentReferenceWeb) { return value.firestoreWeb.doc(value.path); - } else if (value is Map) { - return encodeMapData(value); + } else if (value is Map) { + return encodeMapData(value.cast()); } else if (value is List) { return encodeArrayData(value); } else if (value is Iterable) { From b853386e987d686eab4b8fd9b8dad14eda97479c Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Tue, 3 Mar 2026 16:04:52 +0100 Subject: [PATCH 46/72] feat(messaging, web): add support for debug tokens on Web (#18057) * feat(messaging, web): add support for debug tokens on Web * format * fix analyze --- .../firebase_app_check/example/lib/main.dart | 4 ++- .../lib/firebase_app_check.dart | 3 +- .../lib/src/web_providers.dart | 16 ++++++++++ .../lib/firebase_app_check_web.dart | 32 +++++++++++++------ .../lib/src/interop/app_check.dart | 19 ++++++++++- 5 files changed, 62 insertions(+), 12 deletions(-) diff --git a/packages/firebase_app_check/firebase_app_check/example/lib/main.dart b/packages/firebase_app_check/firebase_app_check/example/lib/main.dart index 1fbb5c9b7745..7a1e778d35a0 100644 --- a/packages/firebase_app_check/firebase_app_check/example/lib/main.dart +++ b/packages/firebase_app_check/firebase_app_check/example/lib/main.dart @@ -23,7 +23,9 @@ Future main() async { await FirebaseAppCheck.instance // Your personal reCaptcha public key goes here: .activate( - providerWeb: ReCaptchaV3Provider(kWebRecaptchaSiteKey), + providerWeb: kDebugMode + ? WebDebugProvider() + : ReCaptchaV3Provider(kWebRecaptchaSiteKey), providerAndroid: const AndroidDebugProvider(), providerApple: const AppleDebugProvider(), ); diff --git a/packages/firebase_app_check/firebase_app_check/lib/firebase_app_check.dart b/packages/firebase_app_check/firebase_app_check/lib/firebase_app_check.dart index df0e9641fe25..c5f5ae1ad633 100644 --- a/packages/firebase_app_check/firebase_app_check/lib/firebase_app_check.dart +++ b/packages/firebase_app_check/firebase_app_check/lib/firebase_app_check.dart @@ -20,7 +20,8 @@ export 'package:firebase_app_check_platform_interface/firebase_app_check_platfor AppleAppAttestProvider, AppleAppAttestWithDeviceCheckFallbackProvider, ReCaptchaEnterpriseProvider, - ReCaptchaV3Provider; + ReCaptchaV3Provider, + WebDebugProvider; export 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart' show FirebaseException; diff --git a/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/web_providers.dart b/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/web_providers.dart index 8715a4248f78..55d5ab576c7e 100644 --- a/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/web_providers.dart +++ b/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/web_providers.dart @@ -15,3 +15,19 @@ class ReCaptchaV3Provider extends WebProvider { class ReCaptchaEnterpriseProvider extends WebProvider { ReCaptchaEnterpriseProvider(String siteKey) : super(siteKey); } + +/// Debug provider for Web. +/// +/// Sets `self.FIREBASE_APPCHECK_DEBUG_TOKEN` before initializing App Check. +/// If [debugToken] is provided, that token is used. Otherwise the Firebase JS +/// SDK auto-generates one and prints it to the browser console — you then +/// register that token in the Firebase Console. +/// +/// See documentation: https://firebase.google.com/docs/app-check/web/debug-provider +class WebDebugProvider extends WebProvider { + /// Creates a web debug provider with an optional debug token. + WebDebugProvider({this.debugToken}) : super(''); + + /// The debug token for this provider. + final String? debugToken; +} diff --git a/packages/firebase_app_check/firebase_app_check_web/lib/firebase_app_check_web.dart b/packages/firebase_app_check/firebase_app_check_web/lib/firebase_app_check_web.dart index b9011976f765..6393edc01587 100644 --- a/packages/firebase_app_check/firebase_app_check_web/lib/firebase_app_check_web.dart +++ b/packages/firebase_app_check/firebase_app_check_web/lib/firebase_app_check_web.dart @@ -22,6 +22,7 @@ class FirebaseAppCheckWeb extends FirebaseAppCheckPlatform { static const String _libraryName = 'flutter-fire-app-check'; static const recaptchaTypeV3 = 'recaptcha-v3'; static const recaptchaTypeEnterprise = 'enterprise'; + static const recaptchaTypeDebug = 'debug'; static Map> _tokenChangesListeners = {}; /// Stub initializer to allow the [registerWith] to create an instance without @@ -56,14 +57,22 @@ class FirebaseAppCheckWeb extends FirebaseAppCheckPlatform { .getItem(_sessionKeyRecaptchaSiteKey(firebaseApp.name)); } - if (recaptchaType != null && recaptchaSiteKey != null) { + if (recaptchaType != null) { final WebProvider provider; - if (recaptchaType == recaptchaTypeV3) { - provider = ReCaptchaV3Provider(recaptchaSiteKey); - } else if (recaptchaType == recaptchaTypeEnterprise) { - provider = ReCaptchaEnterpriseProvider(recaptchaSiteKey); + if (recaptchaType == recaptchaTypeDebug) { + final debugToken = + recaptchaSiteKey?.isNotEmpty ?? false ? recaptchaSiteKey : null; + provider = WebDebugProvider(debugToken: debugToken); + } else if (recaptchaSiteKey != null) { + if (recaptchaType == recaptchaTypeV3) { + provider = ReCaptchaV3Provider(recaptchaSiteKey); + } else if (recaptchaType == recaptchaTypeEnterprise) { + provider = ReCaptchaEnterpriseProvider(recaptchaSiteKey); + } else { + throw Exception('Invalid recaptcha type: $recaptchaType'); + } } else { - throw Exception('Invalid recaptcha type: $recaptchaType'); + return; } await instance.activate(webProvider: provider); } @@ -127,7 +136,9 @@ class FirebaseAppCheckWeb extends FirebaseAppCheckPlatform { // save the recaptcha type and site key for future startups if (webProvider != null) { final String recaptchaType; - if (webProvider is ReCaptchaV3Provider) { + if (webProvider is WebDebugProvider) { + recaptchaType = recaptchaTypeDebug; + } else if (webProvider is ReCaptchaV3Provider) { recaptchaType = recaptchaTypeV3; } else if (webProvider is ReCaptchaEnterpriseProvider) { recaptchaType = recaptchaTypeEnterprise; @@ -136,8 +147,11 @@ class FirebaseAppCheckWeb extends FirebaseAppCheckPlatform { } web.window.localStorage .setItem(_sessionKeyRecaptchaType(app.name), recaptchaType); - web.window.localStorage - .setItem(_sessionKeyRecaptchaSiteKey(app.name), webProvider.siteKey); + web.window.localStorage.setItem( + _sessionKeyRecaptchaSiteKey(app.name), + webProvider is WebDebugProvider + ? webProvider.debugToken ?? '' + : webProvider.siteKey); } // activate API no longer exists, recaptcha key has to be passed on initialization of app-check instance. diff --git a/packages/firebase_app_check/firebase_app_check_web/lib/src/interop/app_check.dart b/packages/firebase_app_check/firebase_app_check_web/lib/src/interop/app_check.dart index 63f261ca8fa6..c35a5d1d8d16 100644 --- a/packages/firebase_app_check/firebase_app_check_web/lib/src/interop/app_check.dart +++ b/packages/firebase_app_check/firebase_app_check_web/lib/src/interop/app_check.dart @@ -18,7 +18,24 @@ export 'app_check_interop.dart'; AppCheck? getAppCheckInstance([App? app, WebProvider? provider]) { late app_check_interop.ReCaptchaProvider jsProvider; - if (provider is ReCaptchaV3Provider) { + if (provider is WebDebugProvider) { + // Set the debug token global before initializing App Check. + // The Firebase JS SDK reads this and creates a DebugProvider internally. + if (provider.debugToken != null) { + globalContext.setProperty( + 'FIREBASE_APPCHECK_DEBUG_TOKEN'.toJS, + provider.debugToken!.toJS, + ); + } else { + globalContext.setProperty( + 'FIREBASE_APPCHECK_DEBUG_TOKEN'.toJS, + true.toJS, + ); + } + // A provider is still required by initializeAppCheck, but the debug + // token global overrides it. + jsProvider = app_check_interop.ReCaptchaV3Provider('debug'.toJS); + } else if (provider is ReCaptchaV3Provider) { jsProvider = app_check_interop.ReCaptchaV3Provider(provider.siteKey.toJS); } else if (provider is ReCaptchaEnterpriseProvider) { jsProvider = From dfab6191d62a419e3bf10809251b73b1986e911b Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Tue, 3 Mar 2026 15:50:34 +0000 Subject: [PATCH 47/72] feat: implement Firestore pipeline support for web --- .../lib/src/interop/firestore.dart | 107 +++++++++++++ .../lib/src/interop/firestore_interop.dart | 86 ++++++++++- .../lib/src/pipeline_builder_web.dart | 141 ++++++++++++++++++ .../lib/src/pipeline_web.dart | 106 +++++++++++++ .../lib/src/firebase_core_web.dart | 8 + 5 files changed, 443 insertions(+), 5 deletions(-) create mode 100644 packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_builder_web.dart create mode 100644 packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_web.dart 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 d64c5f98cfeb..dff1828ce0a8 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 @@ -1075,3 +1075,110 @@ class AggregateQuerySnapshot } } } + +// ============================================================================= +// Pipeline (global execute + snapshot/result wrappers) +// ============================================================================= + +/// Single result in a pipeline snapshot (document + data). +class PipelineResult + extends JsObjectWrapper { + static final _expando = Expando(); + + late final DocumentReference _ref; + late final Map? _data; + late final DateTime? _createTime; + late final DateTime? _updateTime; + + static PipelineResult getInstance( + firestore_interop.PipelineResultJsImpl jsObject) { + return _expando[jsObject] ??= PipelineResult._fromJsObject(jsObject); + } + + PipelineResult._fromJsObject(firestore_interop.PipelineResultJsImpl jsObject) + : _ref = DocumentReference.getInstance(jsObject.ref), + _data = _dataFromResult(jsObject), + _createTime = _timestampToDateTime(jsObject.createTime), + _updateTime = _timestampToDateTime(jsObject.updateTime), + super.fromJsObject(jsObject); + + static Map? _dataFromResult( + firestore_interop.PipelineResultJsImpl jsResult) { + final d = jsResult.data(); + if (d == null) return null; + final parsed = dartify(d); + return parsed != null + ? Map.from(parsed as Map) + : null; + } + + static DateTime? _timestampToDateTime(dynamic value) { + if (value == null) return null; + final d = dartify(value); + if (d == null) return null; + if (d is DateTime) return d; + if (d is Timestamp) return d.toDate(); + if (d is int) return DateTime.fromMillisecondsSinceEpoch(d); + return null; + } + + DocumentReference get ref => _ref; + Map? get data => _data; + DateTime? get createTime => _createTime; + DateTime? get updateTime => _updateTime; +} + +/// Snapshot of pipeline execution results. +class PipelineSnapshot + extends JsObjectWrapper { + static final _expando = Expando(); + + late final List _results; + late final DateTime? _executionTime; + + static PipelineSnapshot getInstance( + firestore_interop.PipelineSnapshotJsImpl jsObject) { + return _expando[jsObject] ??= PipelineSnapshot._fromJsObject(jsObject); + } + + PipelineSnapshot._fromJsObject( + firestore_interop.PipelineSnapshotJsImpl jsObject) + : _results = jsObject.results.toDart + .cast() + .map(PipelineResult.getInstance) + .toList(), + _executionTime = _executionTimeFromJs(jsObject.executionTime), + super.fromJsObject(jsObject); + + static DateTime? _executionTimeFromJs(dynamic value) { + if (value == null) return null; + final d = dartify(value); + if (d == null) return null; + if (d is DateTime) return d; + if (d is int) return DateTime.fromMillisecondsSinceEpoch(d); + return null; + } + + List get results => _results; + DateTime? get executionTime => _executionTime; +} + +/// Wraps a JS pipeline; use [execute] to run it via the global execute function. +class Pipeline extends JsObjectWrapper { + static final _expando = Expando(); + + static Pipeline getInstance(firestore_interop.PipelineJsImpl jsObject) { + return _expando[jsObject] ??= Pipeline._fromJsObject(jsObject); + } + + Pipeline._fromJsObject(firestore_interop.PipelineJsImpl jsObject) + : super.fromJsObject(jsObject); + + /// Runs this pipeline using the global JS SDK execute function. + Future execute() async { + final snapshot = + await firestore_interop.pipelines.execute(jsObject as JSAny).toDart; + return PipelineSnapshot.getInstance( + snapshot! as firestore_interop.PipelineSnapshotJsImpl); + } +} 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 9d9fa0fdadf4..40134584255b 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 @@ -335,13 +335,20 @@ external JSObject get and; @staticInterop external WriteBatchJsImpl writeBatch(FirestoreJsImpl firestore); -@JS('Firestore') -@staticInterop -abstract class FirestoreJsImpl {} - -extension FirestoreJsImplExtension on FirestoreJsImpl { +extension type FirestoreJsImpl._(JSObject _) implements JSObject { external AppJsImpl get app; external JSString get type; + + /// Returns the pipeline source for building and executing pipelines. + external JSAny pipeline(); +} + +@JS() +@staticInterop +external PipelinesJsImpl get pipelines; + +extension type PipelinesJsImpl._(JSObject _) implements JSObject { + external JSPromise execute(JSAny pipeline); } extension type WriteBatchJsImpl._(JSObject _) implements JSObject { @@ -1007,3 +1014,72 @@ extension type AggregateQuerySnapshotJsImpl._(JSObject _) implements JSObject { @JS() @staticInterop abstract class PersistentCacheIndexManager {} + +/// Entry point for defining the data source of a Firestore Pipeline. +/// Use .collection(), .collectionGroup(), .database(), or .documents(). +extension type PipelineSourceJsImpl._(JSObject _) implements JSObject { + /// Returns all documents from the entire collection (can be nested). + external PipelineJsImpl collection(JSString collectionPath); + + /// Returns all documents from a collection ID regardless of parent. + external PipelineJsImpl collectionGroup(JSString collectionId); + + /// Returns all documents from the entire database. + external PipelineJsImpl database(); + + /// Sets the pipeline source to the given document paths or references. + external PipelineJsImpl documents(JSArray docs); +} + +/// Pipeline returned by PipelineSource methods; chain stages and call execute(). +/// See: https://firebase.google.com/docs/reference/js/firestore_pipelines.pipeline +extension type PipelineJsImpl._(JSObject _) implements JSObject { + external JSPromise execute( + [PipelineExecuteOptions? options]); + external PipelineJsImpl limit(JSNumber limit); + external PipelineJsImpl offset(JSNumber offset); + external PipelineJsImpl where(JSAny condition); + external PipelineJsImpl sort(JSAny orderingOrOptions); + external PipelineJsImpl addFields(JSAny fieldOrOptions); + external PipelineJsImpl select(JSAny selectionOrOptions); + external PipelineJsImpl distinct(JSAny groupOrOptions); + external PipelineJsImpl aggregate(JSAny accumulatorOrOptions); + external PipelineJsImpl sample(JSAny documentsOrOptions); + external PipelineJsImpl unnest(JSAny selectableOrOptions); + external PipelineJsImpl removeFields(JSAny fieldOrOptions); + external PipelineJsImpl replaceWith(JSAny fieldNameOrOptions); + external PipelineJsImpl findNearest(JSAny options); + external PipelineJsImpl union(JSAny otherOrOptions); + external PipelineJsImpl rawStage(JSString name, JSArray params, + [JSAny? options]); +} + +/// Options for pipeline execution (e.g. index mode). +@anonymous +@JS() +@staticInterop +abstract class PipelineExecuteOptions { + external factory PipelineExecuteOptions({JSString? indexMode}); +} + +extension PipelineExecuteOptionsExtension on PipelineExecuteOptions { + external JSString? get indexMode; + external set indexMode(JSString? v); +} + +/// Snapshot of pipeline execution results. +extension type PipelineSnapshotJsImpl._(JSObject _) implements JSObject { + /// Array of [PipelineResultJsImpl]. + external JSArray get results; + + /// Execution time (if provided by SDK). + external JSAny? get executionTime; +} + +/// Single result in a pipeline snapshot (document + data). +extension type PipelineResultJsImpl._(JSObject _) implements JSObject { + external DocumentReferenceJsImpl get ref; + external JSObject? data(); + external JSAny? get createTime; + external JSAny? get updateTime; +} diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_builder_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_builder_web.dart new file mode 100644 index 000000000000..0ff091765824 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_builder_web.dart @@ -0,0 +1,141 @@ +// ignore_for_file: require_trailing_commas +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:js_interop'; + +import 'package:cloud_firestore_web/src/interop/firestore_interop.dart' + as interop; +import 'package:cloud_firestore_web/src/pipeline_expression_converter_web.dart'; + +/// Builds a JS Pipeline from serialized [stages] and returns it ready to execute. +/// Keeps [executePipeline] thin: build → execute → convert. +interop.PipelineJsImpl buildPipelineFromStages( + interop.FirestoreJsImpl jsFirestore, + List> stages, +) { + if (stages.isEmpty) { + throw ArgumentError('Pipeline must have at least one stage (source).'); + } + print(jsFirestore); + final source = jsFirestore.pipeline(); + print('source: $source'); + final first = stages.first; + final stageName = first['stage'] as String?; + + // Build source stage + interop.PipelineJsImpl pipeline = _applySourceStage( + source as interop.PipelineSourceJsImpl, jsFirestore, stageName, first); + + final converter = PipelineExpressionConverterWeb(jsFirestore); + + // Apply remaining stages + for (var i = 1; i < stages.length; i++) { + pipeline = _applyStage(pipeline, stages[i], converter, jsFirestore); + } + return pipeline; +} + +interop.PipelineJsImpl _applySourceStage( + interop.PipelineSourceJsImpl source, + interop.FirestoreJsImpl jsFirestore, + String? stageName, + Map first, +) { + final args = first['args']; + switch (stageName) { + case 'collection': + final path = (args is Map ? args['path'] as String? : null) ?? ''; + return source.collection(path.toJS); + case 'collection_group': + final path = (args is Map ? args['path'] as String? : null) ?? ''; + return source.collectionGroup(path.toJS); + case 'database': + return source.database(); + case 'documents': + final docsRaw = first['args']; + final docs = docsRaw is List + ? docsRaw + : (args is Map + ? args['documents'] as List? ?? + args['paths'] as List? + : null) ?? + []; + final paths = docs + .map((e) => (e is Map ? e['path'] as String? : e?.toString()) ?? '') + .where((s) => s.isNotEmpty) + .toList(); + final refs = + paths.map((p) => interop.doc(jsFirestore as JSAny, p.toJS)).toList(); + return source.documents(refs.toJS); + default: + throw UnsupportedError( + 'Pipeline source stage "$stageName" is not supported on web.', + ); + } +} + +interop.PipelineJsImpl _applyStage( + interop.PipelineJsImpl pipeline, + Map stage, + PipelineExpressionConverterWeb converter, + interop.FirestoreJsImpl jsFirestore, +) { + final name = stage['stage'] as String?; + final args = stage['args']; + final map = args is Map ? args : {}; + + switch (name) { + case 'limit': + final limit = map['limit'] as int? ?? 0; + return pipeline.limit(limit.toJS); + case 'offset': + final offset = map['offset'] as int? ?? 0; + return pipeline.offset(offset.toJS); + case 'where': + final expression = map['expression']; + if (expression == null) return pipeline; + return pipeline.where( + converter.toBooleanExpression(expression as Map)); + case 'sort': + final orderings = map['orderings'] as List?; + if (orderings == null || orderings.isEmpty) return pipeline; + return pipeline.sort(converter.toSortOptions(orderings)); + case 'add_fields': + final expressions = map['expressions'] as List?; + if (expressions == null || expressions.isEmpty) return pipeline; + return pipeline.addFields(converter.toAddFieldsOptions(expressions)); + case 'select': + final expressions = map['expressions'] as List?; + if (expressions == null || expressions.isEmpty) return pipeline; + return pipeline.select(converter.toSelectOptions(expressions)); + case 'distinct': + final expressions = map['expressions'] as List?; + if (expressions == null || expressions.isEmpty) return pipeline; + return pipeline.distinct(converter.toDistinctOptions(expressions)); + case 'aggregate': + return pipeline.aggregate(converter.toAggregateOptions(map)); + case 'sample': + return pipeline.sample(converter.toSampleOptions(args)); + case 'unnest': + return pipeline.unnest(converter.toUnnestOptions(map)); + case 'remove_fields': + final fieldPaths = map['field_paths'] as List?; + if (fieldPaths == null || fieldPaths.isEmpty) return pipeline; + return pipeline.removeFields(converter.toRemoveFieldsOptions(fieldPaths)); + case 'replace_with': + final expression = map['expression']; + if (expression == null) return pipeline; + return pipeline.replaceWith( + converter.toReplaceWithOptions(expression as Map)); + case 'find_nearest': + return pipeline.findNearest(converter.toFindNearestOptions(map)); + case 'union': + // Union requires another Pipeline; not yet supported from serialized stages. + return pipeline; + default: + // Ignore unknown stages + return pipeline; + } +} diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_web.dart new file mode 100644 index 000000000000..ba7ffe09751f --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_web.dart @@ -0,0 +1,106 @@ +// ignore_for_file: require_trailing_commas +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:js_interop'; + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:cloud_firestore_web/src/interop/utils/utils.dart'; + +import 'document_reference_web.dart'; +import 'interop/firestore.dart' as firestore_interop; +import 'interop/firestore_interop.dart' as interop; + +/// Web implementation of [PipelinePlatform]. +class PipelineWeb extends PipelinePlatform { + final firestore_interop.Firestore _firestoreWeb; + + PipelineWeb( + FirebaseFirestorePlatform firestore, + this._firestoreWeb, + List>? stages, + ) : super(firestore, stages); + + @override + PipelinePlatform addStage(Map serializedStage) { + return PipelineWeb( + firestore, + _firestoreWeb, + [...stages, serializedStage], + ); + } + + @override + Future execute({ + Map? options, + }) async { + return firestore.executePipeline(stages, options: options); + } +} + +/// Web implementation of [PipelineSnapshotPlatform]. +class PipelineSnapshotWeb extends PipelineSnapshotPlatform { + PipelineSnapshotWeb(this._results, this._executionTime) : super(); + + final List _results; + final DateTime _executionTime; + + @override + List get results => _results; + + @override + DateTime get executionTime => _executionTime; +} + +/// Web implementation of [PipelineResultPlatform]. +class PipelineResultWeb extends PipelineResultPlatform { + PipelineResultWeb( + FirebaseFirestorePlatform firestore, + firestore_interop.Firestore firestoreWeb, + interop.PipelineResultJsImpl jsResult, + ) : _document = DocumentReferenceWeb( + firestore, + firestoreWeb, + jsResult.ref.path.toDart, + ), + _createTime = _timestampToDateTime(jsResult.createTime), + _updateTime = _timestampToDateTime(jsResult.updateTime), + _data = _dataFromResult(jsResult), + super(); + + final DocumentReferencePlatform _document; + final DateTime? _createTime; + final DateTime? _updateTime; + final Map? _data; + + static Map? _dataFromResult( + interop.PipelineResultJsImpl jsResult) { + final d = jsResult.data(); + return d != null + ? Map.from(dartify(d) as Map) + : null; + } + + static DateTime? _timestampToDateTime(JSAny? value) { + if (value == null) return null; + final d = dartify(value); + if (d == null) return null; + if (d is DateTime) return d; + if (d is Timestamp) return d.toDate(); + if (d is int) return DateTime.fromMillisecondsSinceEpoch(d); + return null; + } + + @override + DocumentReferencePlatform get document => _document; + + @override + DateTime? get createTime => _createTime; + + @override + DateTime? get updateTime => _updateTime; + + @override + Map? get data => _data; +} diff --git a/packages/firebase_core/firebase_core_web/lib/src/firebase_core_web.dart b/packages/firebase_core/firebase_core_web/lib/src/firebase_core_web.dart index b70f503d9ff3..5ec94d43b010 100644 --- a/packages/firebase_core/firebase_core_web/lib/src/firebase_core_web.dart +++ b/packages/firebase_core/firebase_core_web/lib/src/firebase_core_web.dart @@ -213,6 +213,14 @@ class FirebaseCoreWeb extends FirebasePlatform { if (ignored.contains(service.override ?? service.name)) { return Future.value(); } + final firestoreServiceName = 'firestore'; + + if (service.name == firestoreServiceName) { + return injectSrcScript( + 'https://www.gstatic.com/firebasejs/$version/firebase-firestore-pipelines.js', + 'firebase_$firestoreServiceName', + ); + } return injectSrcScript( 'https://www.gstatic.com/firebasejs/$version/firebase-${service.name}.js', From 6e6f65468c07045e1c21b1d7970234b2dfc16b3d Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Tue, 3 Mar 2026 17:01:03 +0100 Subject: [PATCH 48/72] fix(auth,ios): fix crash that could happen when reloading currentUser informations (#18065) * fix(auth,ios): fix crash that could happen when reloading currentUser informations * format --- .../firebase_auth/FLTFirebaseAuthPlugin.m | 13 ++--- .../Sources/firebase_auth/PigeonParser.m | 7 ++- .../firebase_auth_user_e2e_test.dart | 56 +++++++++++++++++++ 3 files changed, 66 insertions(+), 10 deletions(-) diff --git a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/FLTFirebaseAuthPlugin.m b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/FLTFirebaseAuthPlugin.m index ee84d102644d..22a26ae98a97 100644 --- a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/FLTFirebaseAuthPlugin.m +++ b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/FLTFirebaseAuthPlugin.m @@ -1947,7 +1947,7 @@ - (void)reloadApp:(nonnull AuthPigeonFirebaseApp *)app if (error != nil) { completion(nil, [FLTFirebaseAuthPlugin convertToFlutterError:error]); } else { - completion([PigeonParser getPigeonDetails:auth.currentUser], nil); + completion([PigeonParser getPigeonDetails:currentUser], nil); } }]; } @@ -2023,7 +2023,7 @@ - (void)updateEmailApp:(nonnull AuthPigeonFirebaseApp *)app if (reloadError != nil) { completion(nil, [FLTFirebaseAuthPlugin convertToFlutterError:reloadError]); } else { - completion([PigeonParser getPigeonDetails:auth.currentUser], nil); + completion([PigeonParser getPigeonDetails:currentUser], nil); } }]; } @@ -2053,7 +2053,7 @@ - (void)updatePasswordApp:(nonnull AuthPigeonFirebaseApp *)app if (reloadError != nil) { completion(nil, [FLTFirebaseAuthPlugin convertToFlutterError:reloadError]); } else { - completion([PigeonParser getPigeonDetails:auth.currentUser], nil); + completion([PigeonParser getPigeonDetails:currentUser], nil); } }]; } @@ -2109,9 +2109,8 @@ - (void)updatePhoneNumberApp:(nonnull AuthPigeonFirebaseApp *)app reloadError]); } else { completion( - [PigeonParser - getPigeonDetails: - auth.currentUser], + [PigeonParser getPigeonDetails: + currentUser], nil); } }]; @@ -2164,7 +2163,7 @@ - (void)updateProfileApp:(nonnull AuthPigeonFirebaseApp *)app if (reloadError != nil) { completion(nil, [FLTFirebaseAuthPlugin convertToFlutterError:reloadError]); } else { - completion([PigeonParser getPigeonDetails:auth.currentUser], nil); + completion([PigeonParser getPigeonDetails:currentUser], nil); } }]; } diff --git a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/PigeonParser.m b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/PigeonParser.m index 3386570909a4..f8ef1b77493c 100644 --- a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/PigeonParser.m +++ b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/PigeonParser.m @@ -32,12 +32,12 @@ + (PigeonUserDetails *)getPigeonDetails:(nonnull FIRUser *)user { } + (PigeonUserInfo *)getPigeonUserInfo:(nonnull FIRUser *)user { + NSString *photoUrlString = user.photoURL.absoluteString; return [PigeonUserInfo makeWithUid:user.uid email:user.email displayName:user.displayName - photoUrl:(user.photoURL.absoluteString.length > 0) ? user.photoURL.absoluteString - : nil + photoUrl:(photoUrlString.length > 0) ? photoUrlString : nil phoneNumber:user.phoneNumber isAnonymous:user.isAnonymous isEmailVerified:user.emailVerified @@ -54,6 +54,7 @@ + (PigeonUserInfo *)getPigeonUserInfo:(nonnull FIRUser *)user { [NSMutableArray arrayWithCapacity:providerData.count]; for (id userInfo in providerData) { + NSString *photoUrlStr = userInfo.photoURL.absoluteString; NSDictionary *dataDict = @{ @"providerId" : userInfo.providerID, // Can be null on emulator @@ -61,7 +62,7 @@ + (PigeonUserInfo *)getPigeonUserInfo:(nonnull FIRUser *)user { @"displayName" : userInfo.displayName ?: [NSNull null], @"email" : userInfo.email ?: [NSNull null], @"phoneNumber" : userInfo.phoneNumber ?: [NSNull null], - @"photoURL" : userInfo.photoURL.absoluteString ?: [NSNull null], + @"photoURL" : photoUrlStr ?: [NSNull null], // isAnonymous is always false on in a providerData object (the user is not anonymous) @"isAnonymous" : @NO, // isEmailVerified is always true on in a providerData object (the email is verified by the diff --git a/tests/integration_test/firebase_auth/firebase_auth_user_e2e_test.dart b/tests/integration_test/firebase_auth/firebase_auth_user_e2e_test.dart index 16c42b7cf353..7f3cd8d9ff74 100644 --- a/tests/integration_test/firebase_auth/firebase_auth_user_e2e_test.dart +++ b/tests/integration_test/firebase_auth/firebase_auth_user_e2e_test.dart @@ -490,6 +490,62 @@ void main() { } expect(FirebaseAuth.instance.currentUser, isNull); }); + + test( + 'should preserve photoURL after reload', + () async { + await FirebaseAuth.instance.createUserWithEmailAndPassword( + email: email, + password: testPassword, + ); + await FirebaseAuth.instance.currentUser!.updatePhotoURL( + 'http://photo.url/test.jpg', + ); + await FirebaseAuth.instance.currentUser!.reload(); + + expect( + FirebaseAuth.instance.currentUser!.photoURL, + 'http://photo.url/test.jpg', + ); + + // Reload again to exercise the PigeonParser path + // with a non-null photoURL + await FirebaseAuth.instance.currentUser!.reload(); + + expect( + FirebaseAuth.instance.currentUser!.photoURL, + 'http://photo.url/test.jpg', + ); + expect( + FirebaseAuth.instance.currentUser!.displayName, + isNull, + ); + }, + skip: kIsWeb || + defaultTargetPlatform == TargetPlatform.macOS || + defaultTargetPlatform == TargetPlatform.windows, + ); + + test( + 'should handle reload when photoURL is null', + () async { + await FirebaseAuth.instance.createUserWithEmailAndPassword( + email: email, + password: testPassword, + ); + + // User created without photoURL — reload should not crash + await FirebaseAuth.instance.currentUser!.reload(); + + expect( + FirebaseAuth.instance.currentUser!.photoURL, + isNull, + ); + }, + skip: kIsWeb || + defaultTargetPlatform == TargetPlatform.macOS || + defaultTargetPlatform == TargetPlatform.windows, + ); }); group( From 91d35e68cc848fb09aae8a83626828cbb5230d92 Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Tue, 3 Mar 2026 17:39:34 +0100 Subject: [PATCH 49/72] docs(auth,android): add documentation about android:taskAffinity when using OAuth signing (#18068) --- docs/auth/federated-auth.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/auth/federated-auth.md b/docs/auth/federated-auth.md index 943505bdc4ce..69fbf9112591 100644 --- a/docs/auth/federated-auth.md +++ b/docs/auth/federated-auth.md @@ -12,6 +12,13 @@ Both native platforms and web support creating a credential which can then be pa or `linkWithCredential` methods. Alternatively on web platforms, you can trigger the authentication process via a popup or redirect. +Note: On Android, `signInWithProvider` opens a Chrome Custom Tab for the OAuth flow. If your +`AndroidManifest.xml` contains `android:taskAffinity=""` (Flutter's default template), the Custom Tab +will close when the user switches apps (e.g. to open a password manager), and returning will give a +`web-context-already-presented` error. To fix this, remove `android:taskAffinity=""` from your +`AndroidManifest.xml`. +{: .callout .callout-warning} + ## Google Most configuration is already setup when using Google Sign-In with Firebase, however you need to ensure your machine's From 3afd41019bf931b95ae039394fc866528ff13f96 Mon Sep 17 00:00:00 2001 From: David Bebawy Date: Wed, 4 Mar 2026 04:57:27 -0500 Subject: [PATCH 50/72] fix(core): bump Firebase C++ SDK to 13.5.0 (CMake deprecation fix) (#18071) * fix(core): bump Firebase C++ SDK to 13.5.0 to resolve CMake deprecation warning Resolves #12849. The bundled Firebase C++ SDK 13.4.0 sets cmake_minimum_required(VERSION 3.1), which triggers a deprecation warning on CMake 3.25+ and a hard error on CMake 4.x. Firebase C++ SDK 13.5.0 bumps this to VERSION 3.22, fixing the warning and restoring compatibility with modern CMake. * chore: add David Bebawy to AUTHORS * Revert "chore: add David Bebawy to AUTHORS" This reverts commit 8f93138444169fd62ac68a659e5c095f69905e6d. --- packages/firebase_core/firebase_core/windows/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/firebase_core/firebase_core/windows/CMakeLists.txt b/packages/firebase_core/firebase_core/windows/CMakeLists.txt index 1df3eeb43f59..cbc06ecebdbd 100644 --- a/packages/firebase_core/firebase_core/windows/CMakeLists.txt +++ b/packages/firebase_core/firebase_core/windows/CMakeLists.txt @@ -4,7 +4,7 @@ # customers of the plugin. cmake_minimum_required(VERSION 3.14) -set(FIREBASE_SDK_VERSION "13.4.0") +set(FIREBASE_SDK_VERSION "13.5.0") if (EXISTS $ENV{FIREBASE_CPP_SDK_DIR}/include/firebase/version.h) file(READ "$ENV{FIREBASE_CPP_SDK_DIR}/include/firebase/version.h" existing_version) From 5eed50c15dd29ab97934a4bd0919378f61c46f9e Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Wed, 4 Mar 2026 11:09:01 +0100 Subject: [PATCH 51/72] fix(functions,web): fix a crash that could happen with the Int64 type (#18066) --- .../lib/src/https_callable.dart | 3 +- .../test/https_callable_test.dart | 41 +++++++++++++++++++ .../cloud_functions_e2e_test.dart | 22 ++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/packages/cloud_functions/cloud_functions/lib/src/https_callable.dart b/packages/cloud_functions/cloud_functions/lib/src/https_callable.dart index 264e6b93e7bb..cac96c0c3374 100644 --- a/packages/cloud_functions/cloud_functions/lib/src/https_callable.dart +++ b/packages/cloud_functions/cloud_functions/lib/src/https_callable.dart @@ -79,7 +79,8 @@ class HttpsCallable { dynamic _updateRawDataToList(dynamic value) { if (value is Uint8List || value is Int32List || - value is Int64List || + // Int64List is not supported by dart2js, skip the check on web. + (!kIsWeb && value is Int64List) || value is Float32List || value is Float64List) { return value.toList(); diff --git a/packages/cloud_functions/cloud_functions/test/https_callable_test.dart b/packages/cloud_functions/cloud_functions/test/https_callable_test.dart index 87a21f8a61c6..8a9224ad470a 100644 --- a/packages/cloud_functions/cloud_functions/test/https_callable_test.dart +++ b/packages/cloud_functions/cloud_functions/test/https_callable_test.dart @@ -3,6 +3,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:typed_data'; + import 'package:cloud_functions/cloud_functions.dart'; import 'package:cloud_functions_platform_interface/cloud_functions_platform_interface.dart'; import 'package:firebase_core/firebase_core.dart'; @@ -95,6 +97,45 @@ void main() { ); }); + test('converts typed data lists in map values to regular lists', + () async { + final result = await httpsCallable!.call({ + 'bytes': Uint8List.fromList([1, 2, 3]), + 'ints': Int32List.fromList([4, 5, 6]), + 'floats': Float32List.fromList([1.0, 2.0]), + 'doubles': Float64List.fromList([3.0, 4.0]), + }); + final data = result.data as Map; + expect(data['bytes'], isA>()); + expect(data['bytes'], isNot(isA())); + expect(data['bytes'], equals([1, 2, 3])); + expect(data['ints'], isA>()); + expect(data['ints'], isNot(isA())); + expect(data['floats'], isA>()); + expect(data['floats'], isNot(isA())); + expect(data['doubles'], isA>()); + expect(data['doubles'], isNot(isA())); + }); + + test('converts typed data lists passed as direct parameters', () async { + final result = await httpsCallable!.call(Uint8List.fromList([7, 8, 9])); + expect(result.data, isA()); + expect(result.data, isNot(isA())); + expect(result.data, equals([7, 8, 9])); + }); + + test('converts typed data lists inside list parameters', () async { + final result = await httpsCallable!.call([ + Uint8List.fromList([1, 2]), + Int32List.fromList([3, 4]), + ]); + final data = result.data as List; + expect(data[0], isA>()); + expect(data[0], isNot(isA())); + expect(data[1], isA>()); + expect(data[1], isNot(isA())); + }); + test('parameter validation throws if any other type of data is passed', () async { expect(() { diff --git a/tests/integration_test/cloud_functions/cloud_functions_e2e_test.dart b/tests/integration_test/cloud_functions/cloud_functions_e2e_test.dart index 375def3359bc..05b448a961bc 100644 --- a/tests/integration_test/cloud_functions/cloud_functions_e2e_test.dart +++ b/tests/integration_test/cloud_functions/cloud_functions_e2e_test.dart @@ -113,6 +113,28 @@ void main() { skip: kIsWeb, ); + test( + 'accepts raw data as arguments on web (excluding Int64List)', + () async { + HttpsCallableResult result = await callable({ + 'type': 'rawData', + 'list': Uint8List(100), + 'int': Int32List(39), + 'float': Float32List(23), + 'double': Float64List(1001), + }); + final data = result.data; + expect(data['list'], isA()); + expect(data['int'], isA()); + expect(data['float'], isA()); + expect(data['double'], isA()); + }, + // This test is the web counterpart of the above test, + // verifying that typed data serialization works on dart2js + // without triggering "Int64 accessor not supported by dart2js". + skip: !kIsWeb, + ); + test( '[HttpsCallableResult.data] should return Map type for returned objects', () async { From 397ba523df968e8deb92e679f54ea837f28b23e3 Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Wed, 4 Mar 2026 11:09:44 +0100 Subject: [PATCH 52/72] feat(firestore,web): add webPersistentTabManager settings support (#18067) * feat(firestore,web): add webPersistentTabManager settings support * fix --- .../integration_test/settings_e2e.dart | 39 ++++++++ .../cloud_firestore/lib/cloud_firestore.dart | 3 + .../cloud_firestore/lib/src/firestore.dart | 1 + .../lib/src/settings.dart | 94 ++++++++++++++++++- .../test/settings_test.dart | 58 ++++++++++++ .../lib/cloud_firestore_web.dart | 27 +++++- .../lib/src/interop/firestore_interop.dart | 19 ++-- 7 files changed, 230 insertions(+), 11 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/example/integration_test/settings_e2e.dart b/packages/cloud_firestore/cloud_firestore/example/integration_test/settings_e2e.dart index c6d0014d0270..10de2f3bef55 100644 --- a/packages/cloud_firestore/cloud_firestore/example/integration_test/settings_e2e.dart +++ b/packages/cloud_firestore/cloud_firestore/example/integration_test/settings_e2e.dart @@ -40,6 +40,45 @@ void runSettingsTest() { settings.webExperimentalLongPollingOptions, ); }); + + test('can apply WebPersistentMultipleTabManager setting', () async { + const settings = Settings( + persistenceEnabled: true, + webPersistentTabManager: WebPersistentMultipleTabManager(), + ); + + firestore.settings = settings; + + expect( + firestore.settings.webPersistentTabManager, + isA(), + ); + }); + + test('can apply WebPersistentSingleTabManager setting', () async { + const settings = Settings( + persistenceEnabled: true, + webPersistentTabManager: + WebPersistentSingleTabManager(forceOwnership: true), + ); + + firestore.settings = settings; + + final tabManager = firestore.settings.webPersistentTabManager; + expect(tabManager, isA()); + expect( + (tabManager! as WebPersistentSingleTabManager).forceOwnership, + true, + ); + }); + + test('webPersistentTabManager defaults to null', () async { + const settings = Settings( + persistenceEnabled: true, + ); + + expect(settings.webPersistentTabManager, isNull); + }); }, ); } diff --git a/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart b/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart index 6efbb19345ce..8ec159100581 100755 --- a/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart @@ -28,6 +28,9 @@ export 'package:cloud_firestore_platform_interface/cloud_firestore_platform_inte PersistenceSettings, Settings, WebExperimentalLongPollingOptions, + WebPersistentTabManager, + WebPersistentMultipleTabManager, + WebPersistentSingleTabManager, IndexField, Index, FieldOverrides, diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart b/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart index 4f735629aee1..3e11563bb332 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart @@ -282,6 +282,7 @@ class FirebaseFirestore extends FirebasePluginPlatform { settings.webExperimentalAutoDetectLongPolling, webExperimentalLongPollingOptions: settings.webExperimentalLongPollingOptions, + webPersistentTabManager: settings.webPersistentTabManager, ); } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/settings.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/settings.dart index ab5d3c993272..eba93af2ebea 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/settings.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/settings.dart @@ -20,6 +20,7 @@ class Settings { this.webExperimentalAutoDetectLongPolling, this.webExperimentalLongPollingOptions, this.ignoreUndefinedProperties = false, + this.webPersistentTabManager, }); /// Constant used to indicate the LRU garbage collection should be disabled. @@ -75,6 +76,18 @@ class Settings { /// Otherwise, these options have no effect. final WebExperimentalLongPollingOptions? webExperimentalLongPollingOptions; + /// Configures how multiple browser tabs are managed when using persistent + /// cache on web. + /// + /// When `null` (the default), the SDK uses single-tab mode. Set to + /// [WebPersistentMultipleTabManager] for multi-tab synchronization, or + /// [WebPersistentSingleTabManager] with [WebPersistentSingleTabManager.forceOwnership] + /// for Web Worker support. + /// + /// This setting only applies to Flutter Web with [persistenceEnabled] set + /// to `true`. It is ignored on other platforms. + final WebPersistentTabManager? webPersistentTabManager; + /// Returns the settings as a [Map] Map get asMap { return { @@ -88,6 +101,7 @@ class Settings { 'webExperimentalLongPollingOptions': webExperimentalLongPollingOptions?.asMap, if (kIsWeb) 'ignoreUndefinedProperties': ignoreUndefinedProperties, + if (kIsWeb) 'webPersistentTabManager': webPersistentTabManager, }; } @@ -100,6 +114,7 @@ class Settings { bool? webExperimentalAutoDetectLongPolling, bool? ignoreUndefinedProperties, WebExperimentalLongPollingOptions? webExperimentalLongPollingOptions, + WebPersistentTabManager? webPersistentTabManager, }) { assert( cacheSizeBytes == null || @@ -122,6 +137,8 @@ class Settings { this.webExperimentalLongPollingOptions, ignoreUndefinedProperties: ignoreUndefinedProperties ?? this.ignoreUndefinedProperties, + webPersistentTabManager: + webPersistentTabManager ?? this.webPersistentTabManager, ); } @@ -139,7 +156,8 @@ class Settings { webExperimentalAutoDetectLongPolling && other.webExperimentalLongPollingOptions == webExperimentalLongPollingOptions && - other.ignoreUndefinedProperties == ignoreUndefinedProperties; + other.ignoreUndefinedProperties == ignoreUndefinedProperties && + other.webPersistentTabManager == webPersistentTabManager; @override int get hashCode => Object.hash( @@ -152,12 +170,86 @@ class Settings { webExperimentalAutoDetectLongPolling, webExperimentalLongPollingOptions, ignoreUndefinedProperties, + webPersistentTabManager, ); @override String toString() => 'Settings($asMap)'; } +/// Configures how multiple browser tabs are managed by the Firestore SDK +/// when using persistent cache on web. +/// +/// This setting only applies to Flutter Web with [Settings.persistenceEnabled] +/// set to `true`. It is ignored on other platforms. +/// +/// See also: +/// - [WebPersistentMultipleTabManager] for multi-tab synchronization +/// - [WebPersistentSingleTabManager] for single-tab mode with optional +/// force ownership (Web Workers) +sealed class WebPersistentTabManager { + const WebPersistentTabManager(); +} + +/// Enables multi-tab synchronization for Firestore’s persistent cache. +/// +/// The SDK will synchronize queries and mutations across all open browser +/// tabs that use the same Firestore instance. +/// +/// Example: +/// ```dart +/// FirebaseFirestore.instance.settings = const Settings( +/// persistenceEnabled: true, +/// webPersistentTabManager: WebPersistentMultipleTabManager(), +/// ); +/// ``` +@immutable +class WebPersistentMultipleTabManager extends WebPersistentTabManager { + const WebPersistentMultipleTabManager(); + + @override + bool operator ==(Object other) => + other is WebPersistentMultipleTabManager && + other.runtimeType == runtimeType; + + @override + int get hashCode => runtimeType.hashCode; +} + +/// Configures the Firestore SDK to operate in single-tab mode. +/// +/// When [forceOwnership] is `true`, this tab forcibly acquires the +/// IndexedDB lock, which is useful for Web Workers but will cause other +/// tabs using persistence to fail. +/// +/// Example: +/// ```dart +/// FirebaseFirestore.instance.settings = const Settings( +/// persistenceEnabled: true, +/// webPersistentTabManager: WebPersistentSingleTabManager(forceOwnership: true), +/// ); +/// ``` +@immutable +class WebPersistentSingleTabManager extends WebPersistentTabManager { + const WebPersistentSingleTabManager({this.forceOwnership = false}); + + /// Whether to force-enable persistent (IndexedDB) cache for this tab. + /// + /// This cannot be used with multi-tab synchronization and is primarily + /// intended for use with Web Workers. Setting this to `true` will enable + /// IndexedDB, but cause other tabs using IndexedDB cache to fail. + final bool forceOwnership; + + @override + bool operator ==(Object other) => + other is WebPersistentSingleTabManager && + other.runtimeType == runtimeType && + other.forceOwnership == forceOwnership; + + @override + int get hashCode => Object.hash(runtimeType, forceOwnership); +} + /// Options that configure the SDK’s underlying network transport (WebChannel) when long-polling is used. @immutable class WebExperimentalLongPollingOptions { diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/test/settings_test.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/test/settings_test.dart index dfecd20fae20..a035780479f5 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/test/settings_test.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/test/settings_test.dart @@ -20,6 +20,7 @@ void main() { webExperimentalLongPollingOptions: WebExperimentalLongPollingOptions( timeoutDuration: Duration(seconds: 4), ), + webPersistentTabManager: WebPersistentMultipleTabManager(), ), equals( const Settings( @@ -33,6 +34,7 @@ void main() { WebExperimentalLongPollingOptions( timeoutDuration: Duration(seconds: 4), ), + webPersistentTabManager: WebPersistentMultipleTabManager(), ), ), ); @@ -111,6 +113,62 @@ void main() { test('CACHE_SIZE_UNLIMITED returns -1', () { expect(Settings.CACHE_SIZE_UNLIMITED, equals(-1)); }); + + test('WebPersistentTabManager equality', () { + expect( + const WebPersistentMultipleTabManager(), + equals(const WebPersistentMultipleTabManager()), + ); + + expect( + const WebPersistentSingleTabManager(), + equals(const WebPersistentSingleTabManager()), + ); + + expect( + const WebPersistentSingleTabManager(forceOwnership: true), + equals(const WebPersistentSingleTabManager(forceOwnership: true)), + ); + + expect( + const WebPersistentSingleTabManager(forceOwnership: true), + isNot(equals(const WebPersistentSingleTabManager())), + ); + + expect( + const WebPersistentMultipleTabManager(), + isNot(equals(const WebPersistentSingleTabManager())), + ); + }); + + test('Settings with different webPersistentTabManager are not equal', () { + expect( + const Settings( + persistenceEnabled: true, + webPersistentTabManager: WebPersistentMultipleTabManager(), + ), + isNot(equals( + const Settings( + persistenceEnabled: true, + webPersistentTabManager: WebPersistentSingleTabManager(), + ), + )), + ); + }); + + test('copyWith preserves webPersistentTabManager', () { + const settings = Settings( + persistenceEnabled: true, + webPersistentTabManager: WebPersistentMultipleTabManager(), + ); + + final copied = settings.copyWith(host: 'localhost'); + + expect(copied.webPersistentTabManager, + isA()); + expect(copied.host, 'localhost'); + expect(copied.persistenceEnabled, true); + }); }); } diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/cloud_firestore_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/cloud_firestore_web.dart index bb7ddaa47d04..ff68541be65a 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/cloud_firestore_web.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/cloud_firestore_web.dart @@ -145,6 +145,7 @@ class FirebaseFirestoreWeb extends FirebaseFirestorePlatform { sslEnabled: firestoreSettings.sslEnabled, cacheSizeBytes: firestoreSettings.cacheSizeBytes, ignoreUndefinedProperties: firestoreSettings.ignoreUndefinedProperties, + webPersistentTabManager: firestoreSettings.webPersistentTabManager, ); // Union type MemoryLocalCache | PersistentLocalCache dynamic localCache; @@ -152,10 +153,28 @@ class FirebaseFirestoreWeb extends FirebaseFirestorePlatform { if (persistenceEnabled == null || persistenceEnabled == false) { localCache = firestore_interop.memoryLocalCache(null); } else { - localCache = firestore_interop - .persistentLocalCache(firestore_interop.PersistentCacheSettings( - cacheSizeBytes: firestoreSettings.cacheSizeBytes?.toJS, - )); + final tabManagerSetting = firestoreSettings.webPersistentTabManager; + final firestore_interop.PersistentCacheSettings cacheSettings; + if (tabManagerSetting is WebPersistentMultipleTabManager) { + cacheSettings = firestore_interop.PersistentCacheSettings( + cacheSizeBytes: firestoreSettings.cacheSizeBytes?.toJS, + tabManager: firestore_interop.persistentMultipleTabManager(), + ); + } else if (tabManagerSetting is WebPersistentSingleTabManager) { + cacheSettings = firestore_interop.PersistentCacheSettings( + cacheSizeBytes: firestoreSettings.cacheSizeBytes?.toJS, + tabManager: firestore_interop.persistentSingleTabManager( + firestore_interop.PersistentSingleTabManagerSettings( + forceOwnership: tabManagerSetting.forceOwnership.toJS, + ), + ), + ); + } else { + cacheSettings = firestore_interop.PersistentCacheSettings( + cacheSizeBytes: firestoreSettings.cacheSizeBytes?.toJS, + ); + } + localCache = firestore_interop.persistentLocalCache(cacheSettings); } if (firestoreSettings.host != null && firestoreSettings.sslEnabled != null) { 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 9d9fa0fdadf4..85cc51fbffde 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 @@ -234,9 +234,7 @@ external PersistentSingleTabManager persistentSingleTabManager( @JS() @staticInterop -external PersistentMultipleTabManager persistentMultipleTabManager( - PersistentSingleTabManagerSettings? settings, -); +external PersistentMultipleTabManager persistentMultipleTabManager(); @JS() @staticInterop @@ -825,11 +823,20 @@ extension PersistentCacheSettingsExtension on PersistentCacheSettings { external set tabManager(JSObject v); } -/// An settings object to configure an PersistentLocalCache instance. +/// Settings to configure a PersistentSingleTabManager instance. /// /// See: . -extension type PersistentSingleTabManagerSettings._(JSObject _) - implements JSObject { +@anonymous +@JS() +@staticInterop +abstract class PersistentSingleTabManagerSettings { + external factory PersistentSingleTabManagerSettings({ + JSBoolean? forceOwnership, + }); +} + +extension PersistentSingleTabManagerSettingsExtension + on PersistentSingleTabManagerSettings { /// Whether to force-enable persistent (IndexedDB) cache for the client. /// This cannot be used with multi-tab synchronization and is primarily /// intended for use with Web Workers. From 8c30132909dd486a49e21b897b0688a3a35cb881 Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Wed, 4 Mar 2026 11:10:03 +0100 Subject: [PATCH 53/72] docs(auth,ios): update Apple Sign In docs to showcase adding scopes (#18075) --- docs/auth/federated-auth.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/auth/federated-auth.md b/docs/auth/federated-auth.md index 69fbf9112591..3811c94f348a 100644 --- a/docs/auth/federated-auth.md +++ b/docs/auth/federated-auth.md @@ -215,11 +215,18 @@ For further information, see this [issue](https://github.com/firebase/flutterfir and [enable Apple as a sign-in provider](/docs/auth/web/apple#enable-apple-as-a-sign-in-provider). +To have Apple present the full first-time sign-in UI (including the "Share/Hide email" option), +you must request the `email` and `name` scopes: +{: .callout .callout-info} + ```dart import 'package:firebase_auth/firebase_auth.dart'; Future signInWithApple() async { final appleProvider = AppleAuthProvider(); + appleProvider.addScope('email'); + appleProvider.addScope('name'); + if (kIsWeb) { await FirebaseAuth.instance.signInWithPopup(appleProvider); } else { @@ -266,6 +273,8 @@ import 'package:firebase_auth/firebase_auth.dart'; Future signInWithApple() async { final appleProvider = AppleAuthProvider(); + appleProvider.addScope('email'); + appleProvider.addScope('name'); UserCredential userCredential = await FirebaseAuth.instance.signInWithPopup(appleProvider); // Keep the authorization code returned from Apple platforms From 49a56c26444c3edc92368a33c948356ebb5261f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 11:53:12 +0100 Subject: [PATCH 54/72] chore(deps): bump fast-xml-parser (#18063) Bumps [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) from 5.3.6 to 5.4.2. - [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases) - [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md) - [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v5.3.6...v5.4.2) --- updated-dependencies: - dependency-name: fast-xml-parser dependency-version: 5.4.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../scripts/functions/package-lock.json | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/.github/workflows/scripts/functions/package-lock.json b/.github/workflows/scripts/functions/package-lock.json index d0e6b9f3c645..b0e9cf375ef2 100644 --- a/.github/workflows/scripts/functions/package-lock.json +++ b/.github/workflows/scripts/functions/package-lock.json @@ -986,10 +986,22 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "optional": true }, + "node_modules/fast-xml-builder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.0.0.tgz", + "integrity": "sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "optional": true + }, "node_modules/fast-xml-parser": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.6.tgz", - "integrity": "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.2.tgz", + "integrity": "sha512-pw/6pIl4k0CSpElPEJhDppLzaixDEuWui2CUQQBH/ECDf7+y6YwA4Gf7Tyb0Rfe4DIMuZipYj4AEL0nACKglvQ==", "funding": [ { "type": "github", @@ -998,6 +1010,7 @@ ], "optional": true, "dependencies": { + "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { @@ -3204,12 +3217,19 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "optional": true }, + "fast-xml-builder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.0.0.tgz", + "integrity": "sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==", + "optional": true + }, "fast-xml-parser": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.6.tgz", - "integrity": "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.2.tgz", + "integrity": "sha512-pw/6pIl4k0CSpElPEJhDppLzaixDEuWui2CUQQBH/ECDf7+y6YwA4Gf7Tyb0Rfe4DIMuZipYj4AEL0nACKglvQ==", "optional": true, "requires": { + "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" } }, From 80c6cff2836ef102c716d1e54eda8114b8ee629b Mon Sep 17 00:00:00 2001 From: KeeganDC <97861347+KeeganDC@users.noreply.github.com> Date: Thu, 5 Mar 2026 00:02:29 +1300 Subject: [PATCH 55/72] fix(analytics, iOS): Update hashedEmailAddress handling to use hex string conversion (#18060) --- .../Sources/firebase_analytics/FirebaseAnalyticsPlugin.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/Sources/firebase_analytics/FirebaseAnalyticsPlugin.swift b/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/Sources/firebase_analytics/FirebaseAnalyticsPlugin.swift index 80c7b5c43668..9fde6c52ec13 100644 --- a/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/Sources/firebase_analytics/FirebaseAnalyticsPlugin.swift +++ b/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/Sources/firebase_analytics/FirebaseAnalyticsPlugin.swift @@ -132,7 +132,7 @@ public class FirebaseAnalyticsPlugin: NSObject, FLTFirebasePluginProtocol, Flutt Analytics.initiateOnDeviceConversionMeasurement(phoneNumber: phoneNumber) } if let hashedEmailAddress = arguments["hashedEmailAddress"] as? String, - let data = hashedEmailAddress.data(using: .utf8) { + let data = hexStringToData(hashedEmailAddress) { Analytics.initiateOnDeviceConversionMeasurement(hashedEmailAddress: data) } if let hashedPhoneNumber = arguments["hashedPhoneNumber"] as? String, From ea1f309a33075fc06c082819f0653976c6d5214b Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Wed, 4 Mar 2026 15:34:58 +0100 Subject: [PATCH 56/72] fix(remote_config,windows): release mode wasn't linking properly for windows (#18073) --- .github/workflows/windows.yaml | 2 ++ packages/firebase_core/firebase_core/windows/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 025ae1cdfe5f..ff82836a8a18 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -53,6 +53,8 @@ jobs: - name: "Install Tools" run: | npm install -g firebase-tools + - name: "Build Windows (Release)" + run: cd tests && flutter build windows --release - name: Start Firebase Emulator and run tests run: cd ./.github/workflows/scripts && firebase emulators:exec --project flutterfire-e2e-tests "cd ../../../tests && flutter test .\integration_test\e2e_test.dart -d windows" diff --git a/packages/firebase_core/firebase_core/windows/CMakeLists.txt b/packages/firebase_core/firebase_core/windows/CMakeLists.txt index cbc06ecebdbd..b5d1fce438e1 100644 --- a/packages/firebase_core/firebase_core/windows/CMakeLists.txt +++ b/packages/firebase_core/firebase_core/windows/CMakeLists.txt @@ -122,7 +122,7 @@ add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL) target_include_directories(${PLUGIN_NAME} INTERFACE "${FIREBASE_CPP_SDK_DIR}/include") -set(FIREBASE_RELEASE_PATH_LIBS firebase_app firebase_auth firebase_storage firebase_firestore) +set(FIREBASE_RELEASE_PATH_LIBS firebase_app firebase_auth firebase_remote_config firebase_storage firebase_firestore) foreach(firebase_lib IN ITEMS ${FIREBASE_RELEASE_PATH_LIBS}) get_target_property(firebase_lib_path ${firebase_lib} IMPORTED_LOCATION) string(REPLACE "Debug" "Release" firebase_lib_release_path ${firebase_lib_path}) From 9061ea88440183ef8fb17cbab638c2c08b669dbc Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Wed, 4 Mar 2026 16:14:26 +0000 Subject: [PATCH 57/72] feat: enhance Firestore pipeline support for web --- .../lib/pipeline_snapshot.dart | 5 +- .../cloud_firestore/lib/src/pipeline.dart | 7 +- .../method_channel_pipeline_snapshot.dart | 16 +- .../platform_interface_pipeline_snapshot.dart | 4 +- .../lib/cloud_firestore_web.dart | 29 ++ .../lib/src/interop/firestore.dart | 23 +- .../lib/src/interop/firestore_interop.dart | 73 ++- .../lib/src/pipeline_builder_web.dart | 4 +- .../pipeline_expression_converter_web.dart | 444 ++++++++++++++++++ .../lib/src/pipeline_web.dart | 16 +- 10 files changed, 587 insertions(+), 34 deletions(-) create mode 100644 packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_converter_web.dart diff --git a/packages/cloud_firestore/cloud_firestore/lib/pipeline_snapshot.dart b/packages/cloud_firestore/cloud_firestore/lib/pipeline_snapshot.dart index ac1f287acb18..f2adc19a19cb 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/pipeline_snapshot.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/pipeline_snapshot.dart @@ -6,13 +6,14 @@ part of 'cloud_firestore.dart'; /// Result of executing a pipeline class PipelineResult { - final DocumentReference> document; + /// The document reference, or null for aggregate-only results (no document row). + final DocumentReference>? document; final DateTime? createTime; final DateTime? updateTime; final Map? _data; PipelineResult({ - required this.document, + this.document, this.createTime, this.updateTime, Map? data, diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart index 3565b835bbc9..bbf7bbfe0fc9 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart @@ -40,10 +40,9 @@ class Pipeline { ) { final results = platformSnapshot.results.map((platformResult) { return PipelineResult( - document: _JsonDocumentReference( - _firestore, - platformResult.document, - ), + document: platformResult.document != null + ? _JsonDocumentReference(_firestore, platformResult.document!) + : null, createTime: platformResult.createTime, updateTime: platformResult.updateTime, data: platformResult.data, diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart index 2566fd79cf10..c5f351ef88ed 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart @@ -47,7 +47,7 @@ class MethodChannelPipelineSnapshot extends PipelineSnapshotPlatform { /// An implementation of [PipelineResultPlatform] that uses [MethodChannel] to /// communicate with Firebase plugins. class MethodChannelPipelineResult extends PipelineResultPlatform { - final DocumentReferencePlatform _document; + final DocumentReferencePlatform? _document; final DateTime? _createTime; final DateTime? _updateTime; final Map? _data; @@ -59,16 +59,18 @@ class MethodChannelPipelineResult extends PipelineResultPlatform { this._createTime, this._updateTime, Map? data, - ) : _document = MethodChannelDocumentReference( - firestore, - documentPath ?? '', - pigeonApp, - ), + ) : _document = (documentPath != null && documentPath.isNotEmpty) + ? MethodChannelDocumentReference( + firestore, + documentPath, + pigeonApp, + ) + : null, _data = data, super(); @override - DocumentReferencePlatform get document => _document; + DocumentReferencePlatform? get document => _document; @override DateTime? get createTime => _createTime; diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline_snapshot.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline_snapshot.dart index d4760207a80d..1946ce740712 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline_snapshot.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline_snapshot.dart @@ -29,8 +29,8 @@ abstract class PipelineResultPlatform extends PlatformInterface { static final Object _token = Object(); - /// The document reference - DocumentReferencePlatform get document; + /// The document reference. Null for aggregate-only results (no document row). + DocumentReferencePlatform? get document; /// The creation time of the document DateTime? get createTime; diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/cloud_firestore_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/cloud_firestore_web.dart index bb7ddaa47d04..b477a4eb1f49 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/cloud_firestore_web.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/cloud_firestore_web.dart @@ -21,6 +21,9 @@ import 'src/collection_reference_web.dart'; import 'src/document_reference_web.dart'; import 'src/field_value_factory_web.dart'; import 'src/interop/firestore.dart' as firestore_interop; +import 'src/interop/firestore_interop.dart' as firestore_interop_js; +import 'src/pipeline_builder_web.dart'; +import 'src/pipeline_web.dart'; import 'src/query_web.dart'; import 'src/transaction_web.dart'; import 'src/write_batch_web.dart'; @@ -252,4 +255,30 @@ class FirebaseFirestoreWeb extends FirebaseFirestorePlatform { } _delegate.setLoggingEnabled(value); } + + @override + PipelinePlatform pipeline(List> initialStages) { + return PipelineWeb(this, _delegate, initialStages); + } + + @override + Future executePipeline( + List> stages, { + Map? options, + }) async { + return convertWebExceptions(() async { + final jsFirestore = _delegate.jsObject; + final jsPipeline = buildPipelineFromStages(jsFirestore, stages); + final dartPipeline = firestore_interop.Pipeline.getInstance(jsPipeline); + final snapshot = await dartPipeline.execute(); + + final results = snapshot.results + .map((r) => PipelineResultWeb(this, _delegate, r.jsObject)) + .toList(); + + final executionTime = snapshot.executionTime ?? DateTime.now(); + + return PipelineSnapshotWeb(results, executionTime); + }); + } } 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 dff1828ce0a8..0b4db4328439 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 @@ -1085,7 +1085,7 @@ class PipelineResult extends JsObjectWrapper { static final _expando = Expando(); - late final DocumentReference _ref; + late final DocumentReference? _ref; late final Map? _data; late final DateTime? _createTime; late final DateTime? _updateTime; @@ -1096,7 +1096,7 @@ class PipelineResult } PipelineResult._fromJsObject(firestore_interop.PipelineResultJsImpl jsObject) - : _ref = DocumentReference.getInstance(jsObject.ref), + : _ref = jsObject.ref != null ? DocumentReference.getInstance(jsObject.ref!) : null, _data = _dataFromResult(jsObject), _createTime = _timestampToDateTime(jsObject.createTime), _updateTime = _timestampToDateTime(jsObject.updateTime), @@ -1122,7 +1122,7 @@ class PipelineResult return null; } - DocumentReference get ref => _ref; + DocumentReference? get ref => _ref; Map? get data => _data; DateTime? get createTime => _createTime; DateTime? get updateTime => _updateTime; @@ -1138,15 +1138,24 @@ class PipelineSnapshot static PipelineSnapshot getInstance( firestore_interop.PipelineSnapshotJsImpl jsObject) { + print('PipelineSnapshot.getInstance: jsObject = $jsObject'); + // Bypass Expando to test if key type causes the error: + // return PipelineSnapshot._fromJsObject(jsObject); return _expando[jsObject] ??= PipelineSnapshot._fromJsObject(jsObject); } + static List _buildResults( + firestore_interop.PipelineSnapshotJsImpl jsObject) { + final rawResults = jsObject.results.toDart; + return rawResults + .cast() + .map(PipelineResult.getInstance) + .toList(); + } + PipelineSnapshot._fromJsObject( firestore_interop.PipelineSnapshotJsImpl jsObject) - : _results = jsObject.results.toDart - .cast() - .map(PipelineResult.getInstance) - .toList(), + : _results = _buildResults(jsObject), _executionTime = _executionTimeFromJs(jsObject.executionTime), super.fromJsObject(jsObject); 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 40134584255b..c6e30774a1bf 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 @@ -347,8 +347,77 @@ extension type FirestoreJsImpl._(JSObject _) implements JSObject { @staticInterop external PipelinesJsImpl get pipelines; +/// Pipeline expression API — mirrors the Firebase JS SDK pipelines module. +/// Use these to build expressions for where(), sort(), addFields(), aggregate(), etc. extension type PipelinesJsImpl._(JSObject _) implements JSObject { external JSPromise execute(JSAny pipeline); + + // --- Expression builders --- + external JSAny field(JSString path); + external JSAny constant(JSAny value); + + // --- Boolean / comparison --- + external JSAny equal(JSAny left, JSAny right); + external JSAny notEqual(JSAny left, JSAny right); + external JSAny greaterThan(JSAny left, JSAny right); + external JSAny greaterThanOrEqual(JSAny left, JSAny right); + external JSAny lessThan(JSAny left, JSAny right); + external JSAny lessThanOrEqual(JSAny left, JSAny right); + external JSAny and(JSAny a, JSAny b); + external JSAny or(JSAny a, JSAny b); + external JSAny not(JSAny expr); + + // --- Existence / type checks --- + external JSAny exists(JSAny expr); + external JSAny isAbsent(JSAny expr); + external JSAny isError(JSAny expr); + + // --- Array --- + external JSAny arrayContains(JSAny array, JSAny element); + + // --- Ordering (for sort stage) --- + external JSAny ascending(JSAny expr); + external JSAny descending(JSAny expr); + + // --- Aggregates --- + external AggregateFunctionJsImpl sum(JSAny expr); + external AggregateFunctionJsImpl average(JSAny expr); + external AggregateFunctionJsImpl count(JSAny expr); + external AggregateFunctionJsImpl countDistinct(JSAny expr); + external AggregateFunctionJsImpl minimum(JSAny expr); + external AggregateFunctionJsImpl maximum(JSAny expr); + external AggregateFunctionJsImpl countAll(); + + // --- Aliased (for select/addFields/aggregate output names) --- + external JSAny aliased(JSAny expr, JSString alias); +} + +/// Aggregate function (result of sum(), average(), count(), etc. on pipelines). +/// Has .as(alias) to create an aliased aggregate for accumulators. +extension type AggregateFunctionJsImpl._(JSObject _) implements JSObject { + @JS('as') + external JSAny asAlias(JSString alias); +} + +/// Aliased aggregate for use in aggregate() stage accumulators. +/// Mirrors Firebase JS SDK: constructor(aggregate, alias, _methodName?). +@JS('AliasedAggregate') +@staticInterop +abstract class AliasedAggregateJsImpl { + external factory AliasedAggregateJsImpl( + JSAny aggregate, + JSString alias, [ + JSString? methodName, + ]); +} + +/// Options for the aggregate() pipeline stage. +/// Mirrors Firebase JS SDK AggregateStageOptions: { accumulators, groups? }. +extension type AggregateStageOptionsJsImpl._(JSObject _) implements JSObject { + AggregateStageOptionsJsImpl() : this._(JSObject.new()); + + external set accumulators(JSAny value); + external set groups(JSAny value); } extension type WriteBatchJsImpl._(JSObject _) implements JSObject { @@ -1043,7 +1112,7 @@ extension type PipelineJsImpl._(JSObject _) implements JSObject { external PipelineJsImpl addFields(JSAny fieldOrOptions); external PipelineJsImpl select(JSAny selectionOrOptions); external PipelineJsImpl distinct(JSAny groupOrOptions); - external PipelineJsImpl aggregate(JSAny accumulatorOrOptions); + external PipelineJsImpl aggregate(AggregateStageOptionsJsImpl options); external PipelineJsImpl sample(JSAny documentsOrOptions); external PipelineJsImpl unnest(JSAny selectableOrOptions); external PipelineJsImpl removeFields(JSAny fieldOrOptions); @@ -1078,7 +1147,7 @@ extension type PipelineSnapshotJsImpl._(JSObject _) implements JSObject { /// Single result in a pipeline snapshot (document + data). extension type PipelineResultJsImpl._(JSObject _) implements JSObject { - external DocumentReferenceJsImpl get ref; + external DocumentReferenceJsImpl? get ref; external JSObject? data(); external JSAny? get createTime; external JSAny? get updateTime; diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_builder_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_builder_web.dart index 0ff091765824..42d97d8f50bd 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_builder_web.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_builder_web.dart @@ -18,9 +18,7 @@ interop.PipelineJsImpl buildPipelineFromStages( if (stages.isEmpty) { throw ArgumentError('Pipeline must have at least one stage (source).'); } - print(jsFirestore); final source = jsFirestore.pipeline(); - print('source: $source'); final first = stages.first; final stageName = first['stage'] as String?; @@ -28,7 +26,7 @@ interop.PipelineJsImpl buildPipelineFromStages( interop.PipelineJsImpl pipeline = _applySourceStage( source as interop.PipelineSourceJsImpl, jsFirestore, stageName, first); - final converter = PipelineExpressionConverterWeb(jsFirestore); + final converter = PipelineExpressionConverterWeb(interop.pipelines); // Apply remaining stages for (var i = 1; i < stages.length; i++) { diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_converter_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_converter_web.dart new file mode 100644 index 000000000000..98912313f909 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_converter_web.dart @@ -0,0 +1,444 @@ +// ignore_for_file: require_trailing_commas +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:js_interop'; +import 'dart:js_util' show setProperty; + +import 'package:cloud_firestore_web/src/interop/firestore_interop.dart' + as interop; +import 'package:cloud_firestore_web/src/interop/utils/utils.dart'; + +/// Converts Dart serialized pipeline expressions/stage args into JS pipeline +/// types by calling the pipelines interop API (field, constant, equal, and, +/// ascending, etc.) that mirrors the Firebase JS SDK. +class PipelineExpressionConverterWeb { + PipelineExpressionConverterWeb(this._pipelines); + + final interop.PipelinesJsImpl _pipelines; + + /// Converts a serialized expression map to a JS Expression/BooleanExpression. + JSAny? toExpression(Map map) { + final name = map['name'] as String?; + final args = map['args']; + final argsMap = args is Map ? args : {}; + + switch (name) { + case 'field': + final path = (argsMap['field'] as String?) ?? ''; + return _pipelines.field(path.toJS); + case 'constant': + case 'null': + final value = argsMap['value']; + final jsValue = jsify(value); + if (jsValue == null) { + throw UnsupportedError( + 'constant(null) is not supported on web; use a non-null value.', + ); + } + return _pipelines.constant(jsValue); + case 'equal': + final left = argsMap['left']; + final right = argsMap['right']; + if (left == null || right == null) return null; + final leftJs = toExpression(left as Map); + final rightJs = toExpression(right as Map); + if (leftJs == null || rightJs == null) return null; + return _pipelines.equal(leftJs, rightJs); + case 'not_equal': + final left = argsMap['left']; + final right = argsMap['right']; + if (left == null || right == null) return null; + final leftJs = toExpression(left as Map); + final rightJs = toExpression(right as Map); + if (leftJs == null || rightJs == null) return null; + return _pipelines.notEqual(leftJs, rightJs); + case 'greater_than': + final left = argsMap['left']; + final right = argsMap['right']; + if (left == null || right == null) return null; + final leftJs = toExpression(left as Map); + final rightJs = toExpression(right as Map); + if (leftJs == null || rightJs == null) return null; + return _pipelines.greaterThan(leftJs, rightJs); + case 'greater_than_or_equal': + final left = argsMap['left']; + final right = argsMap['right']; + if (left == null || right == null) return null; + final leftJs = toExpression(left as Map); + final rightJs = toExpression(right as Map); + if (leftJs == null || rightJs == null) return null; + return _pipelines.greaterThanOrEqual(leftJs, rightJs); + case 'less_than': + final left = argsMap['left']; + final right = argsMap['right']; + if (left == null || right == null) return null; + final leftJs = toExpression(left as Map); + final rightJs = toExpression(right as Map); + if (leftJs == null || rightJs == null) return null; + return _pipelines.lessThan(leftJs, rightJs); + case 'less_than_or_equal': + final left = argsMap['left']; + final right = argsMap['right']; + if (left == null || right == null) return null; + final leftJs = toExpression(left as Map); + final rightJs = toExpression(right as Map); + if (leftJs == null || rightJs == null) return null; + return _pipelines.lessThanOrEqual(leftJs, rightJs); + case 'filter': + final operator = argsMap['operator'] as String?; + final expressions = argsMap['expressions'] as List?; + if (expressions == null || expressions.isEmpty) return null; + final jsList = expressions + .map((e) => toExpression(e as Map)) + .whereType() + .toList(); + if (jsList.isEmpty) return null; + if (jsList.length == 1) return jsList.single; + JSAny result = jsList[0]; + for (var i = 1; i < jsList.length; i++) { + result = operator == 'and' + ? _pipelines.and(result, jsList[i]) + : _pipelines.or(result, jsList[i]); + } + return result; + case 'not': + final expression = argsMap['expression']; + if (expression == null) return null; + final exprJs = toExpression(expression as Map); + return exprJs != null ? _pipelines.not(exprJs) : null; + case 'exists': + final expression = argsMap['expression'] ?? argsMap['array']; + if (expression == null) return null; + final exprJs = toExpression(expression as Map); + return exprJs != null ? _pipelines.exists(exprJs) : null; + case 'is_absent': + final expression = argsMap['expression'] ?? argsMap['array']; + if (expression == null) return null; + final exprJs = toExpression(expression as Map); + return exprJs != null ? _pipelines.isAbsent(exprJs) : null; + case 'is_error': + final expression = argsMap['expression'] ?? argsMap['array']; + if (expression == null) return null; + final exprJs = toExpression(expression as Map); + return exprJs != null ? _pipelines.isError(exprJs) : null; + case 'array_contains': + final array = argsMap['array']; + final element = argsMap['element']; + if (array == null || element == null) return null; + final arrayJs = toExpression(array as Map); + final elementJs = toExpression(element as Map); + if (arrayJs == null || elementJs == null) return null; + return _pipelines.arrayContains(arrayJs, elementJs); + default: + return null; + } + } + + /// Converts a serialized boolean expression to JS BooleanExpression for where(). + JSAny toBooleanExpression(Map map) { + final js = toExpression(map); + if (js == null) { + throw UnsupportedError( + 'Pipeline where() on web requires the Firebase JS pipeline expression API ' + '(e.g. field, constant, equal, and, or from firebase/firestore/pipelines). ' + 'Ensure the pipelines module is loaded.', + ); + } + return js; + } + + /// Converts orderings list to JS SortStageOptions. Each item: { expression, order_direction }. + JSAny toSortOptions(List orderings) { + final list = []; + for (final o in orderings) { + final m = o is Map ? o : {}; + final expr = m['expression']; + final dir = m['order_direction'] as String?; + if (expr == null) continue; + final exprJs = toExpression(expr as Map); + if (exprJs == null) continue; + final ordering = (dir == 'desc') + ? _pipelines.descending(exprJs) + : _pipelines.ascending(exprJs); + list.add(ordering); + } + if (list.isEmpty) { + throw UnsupportedError( + 'Pipeline sort() on web requires the Firebase JS pipeline expression API ' + '(ascending, descending). Ensure the pipelines module is loaded.', + ); + } + final obj = JSObject.new; + setProperty(obj, 'orderings', list.toJS); + return obj as JSAny; + } + + /// Converts add_fields expressions (Selectable[]) to JS AddFieldsStageOptions. + JSAny toAddFieldsOptions(List expressions) { + final list = []; + for (final e in expressions) { + final sel = + toSelectable(e is Map ? e : {}); + if (sel != null) list.add(sel); + } + if (list.isEmpty) { + throw UnsupportedError( + 'Pipeline addFields() on web requires the Firebase JS pipeline expression API.', + ); + } + final obj = JSObject.new; + setProperty(obj, 'fields', list.toJS); + return obj as JSAny; + } + + /// Converts a single expression map to a JS Selectable (field or aliased). + JSAny? toSelectable(Map map) { + final name = map['name'] as String?; + final args = map['args']; + final argsMap = args is Map ? args : {}; + if (name == 'field') { + final path = (argsMap['field'] as String?) ?? ''; + return _pipelines.field(path.toJS); + } + if (name == 'alias') { + final alias = argsMap['alias'] as String?; + final expression = argsMap['expression']; + if (alias == null || expression == null) return null; + final exprJs = toExpression(expression as Map); + if (exprJs == null) return null; + return _pipelines.aliased(exprJs, alias.toJS); + } + final exprJs = toExpression(map); + return exprJs; + } + + /// Converts select stage expressions to JS SelectStageOptions. + JSAny toSelectOptions(List expressions) { + final list = []; + for (final e in expressions) { + final sel = + toSelectable(e is Map ? e : {}); + if (sel != null) list.add(sel); + } + if (list.isEmpty) { + throw UnsupportedError( + 'Pipeline select() on web requires the Firebase JS pipeline expression API.', + ); + } + final obj = JSObject.new; + setProperty(obj, 'selections', list.toJS); + return obj as JSAny; + } + + /// Converts distinct stage groups to JS DistinctStageOptions. + JSAny toDistinctOptions(List expressions) { + final list = []; + for (final e in expressions) { + final sel = + toSelectable(e is Map ? e : {}); + if (sel != null) list.add(sel); + } + if (list.isEmpty) { + throw UnsupportedError( + 'Pipeline distinct() on web requires the Firebase JS pipeline expression API.', + ); + } + final obj = JSObject.new; + setProperty(obj, 'groups', list.toJS); + return obj as JSAny; + } + + /// Converts aggregate stage args to JS AggregateStageOptions. + /// Input shape: { aggregate_functions: [...] } or { aggregate_stage: { aggregate_functions: [...] } } or { accumulators: [...] }. + /// Each list item is an aliased aggregate: { name: 'alias', args: { alias: String, aggregate_function: { name, args?: { expression } } } }. + interop.AggregateStageOptionsJsImpl toAggregateOptions(Map map) { + final aggregateFunctions = _getAggregateFunctionsList(map); + if (aggregateFunctions.isEmpty) { + throw UnsupportedError( + 'Pipeline aggregate() on web requires aggregate_functions.', + ); + } + + final accumulators = []; + for (final item in aggregateFunctions) { + final aliased = _asStringKeyMap(item); + if (aliased == null) continue; + + final args = _asStringKeyMap(aliased['args']) ?? {}; + final alias = args['alias'] ?? aliased['alias']; + if (alias is! String) continue; + + final aggregateFn = _asStringKeyMap(args['aggregate_function']) ?? + _asStringKeyMap(args['aggregate']) ?? + _asStringKeyMap(aliased['aggregate_function']); + if (aggregateFn == null) continue; + + final name = aggregateFn['name'] as String?; + if (name == null) continue; + + final fnArgs = _asStringKeyMap(aggregateFn['args']) ?? {}; + final expressionMap = _asStringKeyMap(fnArgs['expression']) ?? + _asStringKeyMap(aggregateFn['expression']); + + JSAny? exprJs; + if (name != 'count_all') { + if (expressionMap == null) continue; + exprJs = toExpression(expressionMap); + if (exprJs == null) continue; + } + + final fn = _buildAggregateFunction(name, exprJs); + if (fn == null) continue; + + accumulators.add(fn.asAlias(alias.toJS)); + } + + if (accumulators.isEmpty) { + throw UnsupportedError( + 'Pipeline aggregate() on web requires the Firebase JS pipeline expression API ' + '(sum, average, count, etc.).', + ); + } + + final options = interop.AggregateStageOptionsJsImpl(); + options.accumulators = accumulators.toJS; + return options; + } + + /// Returns the aggregate_functions list from [map] (top-level, under aggregate_stage, or as accumulators). + static List _getAggregateFunctionsList(Map map) { + final list = map['aggregate_functions']; + if (list is List && list.isNotEmpty) return list; + + final stage = _asStringKeyMap(map['aggregate_stage']); + final fromStage = stage?['aggregate_functions']; + if (fromStage is List && fromStage.isNotEmpty) return fromStage; + + final accumulators = map['accumulators']; + if (accumulators is List && accumulators.isNotEmpty) return accumulators; + + return []; + } + + static Map? _asStringKeyMap(dynamic value) => + value is Map ? value : null; + + /// Builds one aggregate function (sum, average, count_all, etc.) from serialized [name] and optional [exprJs]. + interop.AggregateFunctionJsImpl? _buildAggregateFunction(String name, JSAny? exprJs) { + switch (name) { + case 'count_all': + return _pipelines.countAll(); + case 'sum': + return exprJs != null ? _pipelines.sum(exprJs) : null; + case 'average': + return exprJs != null ? _pipelines.average(exprJs) : null; + case 'count': + return exprJs != null ? _pipelines.count(exprJs) : null; + case 'count_distinct': + return exprJs != null ? _pipelines.countDistinct(exprJs) : null; + case 'minimum': + return exprJs != null ? _pipelines.minimum(exprJs) : null; + case 'maximum': + return exprJs != null ? _pipelines.maximum(exprJs) : null; + default: + return null; + } + } + + /// Converts sample stage args to JS (number or SampleStageOptions). + /// Dart PipelineSample.toMap() returns { type: 'size', value: n } or { type: 'percentage', value: p }. + JSAny toSampleOptions(dynamic args) { + if (args is num) return args.toInt().toJS; + if (args is Map) { + final type = args['type'] as String?; + final value = args['value']; + if (type == 'size' && value != null) return (value as num).toInt().toJS; + final n = args['documents'] as int? ?? args['count'] as int?; + if (n != null) return n.toJS; + return (args['documents'] as num?)?.toInt().toJS ?? 0.toJS; + } + return 0.toJS; + } + + /// Converts unnest stage args to JS UnnestStageOptions. + /// Dart uses 'expression' (Selectable/Expression toMap). + JSAny toUnnestOptions(Map map) { + final selectable = map['selectable'] ?? map['expression'] ?? map['field']; + final indexField = map['index_field'] as String?; + if (selectable == null) { + throw UnsupportedError( + 'Pipeline unnest() on web requires selectable or field.', + ); + } + final sel = selectable is Map + ? toSelectable(selectable) + : toExpression({ + 'name': 'field', + 'args': {'field': selectable.toString()} + }); + if (sel == null) { + throw UnsupportedError( + 'Pipeline unnest() on web requires the Firebase JS pipeline expression API.', + ); + } + final obj = JSObject.new; + setProperty(obj, 'selectable', sel); + if (indexField != null) setProperty(obj, 'indexField', indexField.toJS); + return obj as JSAny; + } + + /// Converts remove_fields field paths to JS RemoveFieldsStageOptions. + JSAny toRemoveFieldsOptions(List fieldPaths) { + final paths = []; + for (final e in fieldPaths) { + final s = e is String + ? e + : (e is Map ? e['field'] ?? e['path'] : null)?.toString(); + if (s != null) paths.add(s.toJS); + } + if (paths.isEmpty) { + throw UnsupportedError( + 'Pipeline removeFields() on web requires at least one field path.', + ); + } + final obj = JSObject.new; + setProperty(obj, 'fields', paths.toJS); + return obj as JSAny; + } + + /// Converts replace_with expression to JS ReplaceWithStageOptions. + JSAny toReplaceWithOptions(Map expression) { + final exprJs = toExpression(expression); + if (exprJs == null) { + throw UnsupportedError( + 'Pipeline replaceWith() on web requires the Firebase JS pipeline expression API.', + ); + } + final obj = JSObject.new; + setProperty(obj, 'expression', exprJs); + return obj as JSAny; + } + + /// Converts find_nearest args to JS FindNearestStageOptions. + JSAny toFindNearestOptions(Map map) { + final vectorField = + (map['vector_field'] as String?) ?? (map['field'] as String?); + final vectorValue = map['vector_value'] as List?; + final distanceMeasure = (map['distance_measure'] as String?) ?? 'cosine'; + final limit = map['limit'] as int?; + if (vectorField == null || vectorValue == null) { + throw UnsupportedError( + 'Pipeline findNearest() on web requires vector_field and vector_value.', + ); + } + final obj = JSObject.new; + setProperty(obj, 'vectorField', vectorField.toJS); + setProperty(obj, 'vectorValue', + jsify(vectorValue.map((e) => (e as num).toDouble()).toList())); + setProperty(obj, 'distanceMeasure', distanceMeasure.toJS); + if (limit != null) setProperty(obj, 'limit', limit.toJS); + return obj as JSAny; + } +} diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_web.dart index ba7ffe09751f..a633f017ae93 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_web.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_web.dart @@ -59,17 +59,19 @@ class PipelineResultWeb extends PipelineResultPlatform { FirebaseFirestorePlatform firestore, firestore_interop.Firestore firestoreWeb, interop.PipelineResultJsImpl jsResult, - ) : _document = DocumentReferenceWeb( - firestore, - firestoreWeb, - jsResult.ref.path.toDart, - ), + ) : _document = jsResult.ref != null + ? DocumentReferenceWeb( + firestore, + firestoreWeb, + jsResult.ref!.path.toDart, + ) + : null, _createTime = _timestampToDateTime(jsResult.createTime), _updateTime = _timestampToDateTime(jsResult.updateTime), _data = _dataFromResult(jsResult), super(); - final DocumentReferencePlatform _document; + final DocumentReferencePlatform? _document; final DateTime? _createTime; final DateTime? _updateTime; final Map? _data; @@ -93,7 +95,7 @@ class PipelineResultWeb extends PipelineResultPlatform { } @override - DocumentReferencePlatform get document => _document; + DocumentReferencePlatform? get document => _document; @override DateTime? get createTime => _createTime; From 904249ebc67b14115aebe619b2874b0fd325a3ce Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Wed, 4 Mar 2026 17:53:19 +0100 Subject: [PATCH 58/72] fix(fdc,web): add WASM support and improve CI (#18074) * fix(fdc,web): add WASM support and improve CI * permissions clean * clean --- .github/workflows/e2e_tests_fdc.yaml | 82 +++++++++++++++++++ .../example/web/wasm_index.html | 14 ++++ .../lib/src/firebase_data_connect.dart | 1 + 3 files changed, 97 insertions(+) create mode 100644 packages/firebase_data_connect/firebase_data_connect/example/web/wasm_index.html diff --git a/.github/workflows/e2e_tests_fdc.yaml b/.github/workflows/e2e_tests_fdc.yaml index f746c59e87d3..998ae091362d 100644 --- a/.github/workflows/e2e_tests_fdc.yaml +++ b/.github/workflows/e2e_tests_fdc.yaml @@ -21,6 +21,9 @@ on: - '**/example/**' - '**.md' +permissions: + contents: read + jobs: android: runs-on: ubuntu-latest @@ -333,3 +336,82 @@ jobs: key: ${{ steps.firebase-emulator-cache.outputs.cache-primary-key }} # Must match the restore path exactly path: ~/.cache/firebase/emulators + + web-wasm: + runs-on: macos-latest + timeout-minutes: 15 + strategy: + fail-fast: false + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a + name: Install Node.js 20 + with: + node-version: '20' + - uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b + with: + distribution: 'temurin' + java-version: '21' + - name: Setup PostgreSQL for Linux/macOS/Windows + uses: ikalnytskyi/action-setup-postgres@v7 + - uses: subosito/flutter-action@f2c4f6686ca8e8d6e6d0f28410eeef506ed66aff + with: + channel: 'stable' + cache: true + cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" + pub-cache-key: "flutter-pub-:os:-:channel:-:version:-:arch:-:hash:" + - uses: bluefireteam/melos-action@c7dcb921b23cc520cace360b95d02b37bf09cdaa + with: + run-bootstrap: false + melos-version: '5.3.0' + - name: 'Bootstrap package' + run: melos bootstrap --scope "firebase_data_connect*" + - name: 'Install Tools' + run: | + sudo npm i -g firebase-tools + echo "FIREBASE_TOOLS_VERSION=$(npm firebase --version)" >> $GITHUB_ENV + - name: Firebase Emulator Cache + id: firebase-emulator-cache + uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 + continue-on-error: true + with: + # The firebase emulators are pure javascript and java, OS-independent + enableCrossOsArchive: true + # Must match the save path exactly + path: ~/.cache/firebase/emulators + key: firebase-emulators-v3-${{ env.FIREBASE_TOOLS_VERSION }} + restore-keys: firebase-emulators-v3 + - name: Start Firebase Emulator + run: | + cd ./packages/firebase_data_connect/firebase_data_connect/example + unset PGSERVICEFILE + firebase experiments:enable dataconnect + ./start-firebase-emulator.sh + - name: 'E2E Tests' + working-directory: 'packages/firebase_data_connect/firebase_data_connect/example' + # Web devices are not supported for the `flutter test` command yet. As a + # workaround we can use the `flutter drive` command. Tracking issue: + # https://github.com/flutter/flutter/issues/66264 + run: | + chromedriver --port=4444 --trace-buffer-size=100000 & + mv ./web/wasm_index.html ./web/index.html + flutter drive --target=./integration_test/e2e_test.dart --driver=./test_driver/integration_test.dart -d chrome --wasm --dart-define=CI=true | tee output.log + # We have to check the output for failed tests matching the string "[E]" + output=$( + + + + Flutter web app + + + + + + diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/firebase_data_connect.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/firebase_data_connect.dart index 1684e88ed8b8..d5813f94dc89 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/firebase_data_connect.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/firebase_data_connect.dart @@ -22,6 +22,7 @@ import 'package:flutter/foundation.dart'; import './network/transport_library.dart' if (dart.library.io) './network/grpc_library.dart' + if (dart.library.js_interop) './network/rest_library.dart' if (dart.library.html) './network/rest_library.dart'; import 'cache/cache_data_types.dart'; From 00f23b4acaa579ec651163a802adabb00ba1877c Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Wed, 4 Mar 2026 23:46:47 +0000 Subject: [PATCH 59/72] fix --- .../lib/src/interop/firestore.dart | 4 +- .../lib/src/interop/firestore_interop.dart | 31 +++- .../lib/src/pipeline_builder_web.dart | 14 +- .../pipeline_expression_converter_web.dart | 166 ++++++++---------- .../lib/src/pipeline_web.dart | 12 +- 5 files changed, 117 insertions(+), 110 deletions(-) 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 0b4db4328439..163ed535cba9 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 @@ -1096,7 +1096,9 @@ class PipelineResult } PipelineResult._fromJsObject(firestore_interop.PipelineResultJsImpl jsObject) - : _ref = jsObject.ref != null ? DocumentReference.getInstance(jsObject.ref!) : null, + : _ref = jsObject.ref != null + ? DocumentReference.getInstance(jsObject.ref!) + : null, _data = _dataFromResult(jsObject), _createTime = _timestampToDateTime(jsObject.createTime), _updateTime = _timestampToDateTime(jsObject.updateTime), 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 c6e30774a1bf..ef196733057e 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 @@ -353,8 +353,8 @@ extension type PipelinesJsImpl._(JSObject _) implements JSObject { external JSPromise execute(JSAny pipeline); // --- Expression builders --- - external JSAny field(JSString path); - external JSAny constant(JSAny value); + external ExpressionJsImpl field(JSString path); + external ExpressionJsImpl constant(JSAny value); // --- Boolean / comparison --- external JSAny equal(JSAny left, JSAny right); @@ -399,6 +399,27 @@ extension type AggregateFunctionJsImpl._(JSObject _) implements JSObject { external JSAny asAlias(JSString alias); } +extension type ExpressionJsImpl._(JSObject _) implements JSObject { + @JS('as') + external JSAny asAlias(JSString alias); + + external ExpressionJsImpl add(JSAny right); + external ExpressionJsImpl subtract(JSAny right); + external ExpressionJsImpl multiply(JSAny right); + external ExpressionJsImpl divide(JSAny right); + external ExpressionJsImpl modulo(JSAny right); + external ExpressionJsImpl length(); + external ExpressionJsImpl concat(JSAny right); + external ExpressionJsImpl toLowerCase(); + external ExpressionJsImpl toUpperCase(); + external ExpressionJsImpl trim(); +} + +extension type SelectableJsImpl._(JSObject _) implements JSObject { + @JS('as') + external JSAny asAlias(JSString alias); +} + /// Aliased aggregate for use in aggregate() stage accumulators. /// Mirrors Firebase JS SDK: constructor(aggregate, alias, _methodName?). @JS('AliasedAggregate') @@ -420,6 +441,12 @@ extension type AggregateStageOptionsJsImpl._(JSObject _) implements JSObject { external set groups(JSAny value); } +extension type AddFieldsOptionsJsImpl._(JSObject _) implements JSObject { + AddFieldsOptionsJsImpl() : this._(JSObject.new()); + + external set fields(JSAny value); +} + extension type WriteBatchJsImpl._(JSObject _) implements JSObject { external JSPromise commit(); diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_builder_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_builder_web.dart index 42d97d8f50bd..7c98351b28fb 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_builder_web.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_builder_web.dart @@ -86,16 +86,22 @@ interop.PipelineJsImpl _applyStage( switch (name) { case 'limit': - final limit = map['limit'] as int? ?? 0; + final limit = map['limit'] as int; return pipeline.limit(limit.toJS); case 'offset': - final offset = map['offset'] as int? ?? 0; + final offset = map['offset'] as int; return pipeline.offset(offset.toJS); case 'where': final expression = map['expression']; if (expression == null) return pipeline; - return pipeline.where( - converter.toBooleanExpression(expression as Map)); + final condition = + converter.toBooleanExpression(expression as Map); + if (condition == null) { + throw UnsupportedError( + 'Pipeline where() on web: could not parse the condition expression.', + ); + } + return pipeline.where(condition); case 'sort': final orderings = map['orderings'] as List?; if (orderings == null || orderings.isEmpty) return pipeline; diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_converter_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_converter_web.dart index 98912313f909..cfa3c8ce0ee7 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_converter_web.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_converter_web.dart @@ -18,8 +18,9 @@ class PipelineExpressionConverterWeb { final interop.PipelinesJsImpl _pipelines; - /// Converts a serialized expression map to a JS Expression/BooleanExpression. - JSAny? toExpression(Map map) { + /// Converts a serialized value expression to JS Expression (field, constant only). + /// For boolean expressions (equal, not_equal, and, or, etc.) use [toBooleanExpression]. + interop.ExpressionJsImpl toExpression(Map map) { final name = map['name'] as String?; final args = map['args']; final argsMap = args is Map ? args : {}; @@ -28,73 +29,72 @@ class PipelineExpressionConverterWeb { case 'field': final path = (argsMap['field'] as String?) ?? ''; return _pipelines.field(path.toJS); + case 'add': + final leftJsExpr = + toExpression(argsMap['left'] as Map); + final rightJsExpr = + toExpression(argsMap['right'] as Map); + return leftJsExpr.add(rightJsExpr); case 'constant': case 'null': final value = argsMap['value']; - final jsValue = jsify(value); - if (jsValue == null) { + if (value == null) { throw UnsupportedError( 'constant(null) is not supported on web; use a non-null value.', ); } - return _pipelines.constant(jsValue); + return _pipelines.constant(jsify(value)!); + // return _pipelines.constant(value.toJS); + default: + return throw UnsupportedError( + 'Unsupported expression: $name', + ); + } + } + + /// Converts a serialized boolean expression to JS BooleanExpression for where(). + /// Handles equal, not_equal, comparisons, and, or, not, exists, is_absent, is_error, array_contains. + /// Sub-expressions (e.g. left/right of equal) can be value expressions or nested boolean expressions. + /// Returns null if [map] is not a recognized boolean or value expression. + JSAny? toBooleanExpression(Map map) { + final name = map['name'] as String?; + final args = map['args']; + final argsMap = args is Map ? args : {}; + + JSAny leftJs(Map left) => toExpression(left); + JSAny rightJs(Map right) => toExpression(right); + + switch (name) { case 'equal': - final left = argsMap['left']; - final right = argsMap['right']; - if (left == null || right == null) return null; - final leftJs = toExpression(left as Map); - final rightJs = toExpression(right as Map); - if (leftJs == null || rightJs == null) return null; - return _pipelines.equal(leftJs, rightJs); + return _pipelines.equal(leftJs(argsMap['left'] as Map), + rightJs(argsMap['right'] as Map)); case 'not_equal': - final left = argsMap['left']; - final right = argsMap['right']; - if (left == null || right == null) return null; - final leftJs = toExpression(left as Map); - final rightJs = toExpression(right as Map); - if (leftJs == null || rightJs == null) return null; - return _pipelines.notEqual(leftJs, rightJs); + return _pipelines.notEqual( + leftJs(argsMap['left'] as Map), + rightJs(argsMap['right'] as Map)); case 'greater_than': - final left = argsMap['left']; - final right = argsMap['right']; - if (left == null || right == null) return null; - final leftJs = toExpression(left as Map); - final rightJs = toExpression(right as Map); - if (leftJs == null || rightJs == null) return null; - return _pipelines.greaterThan(leftJs, rightJs); + return _pipelines.greaterThan( + leftJs(argsMap['left'] as Map), + rightJs(argsMap['right'] as Map)); case 'greater_than_or_equal': - final left = argsMap['left']; - final right = argsMap['right']; - if (left == null || right == null) return null; - final leftJs = toExpression(left as Map); - final rightJs = toExpression(right as Map); - if (leftJs == null || rightJs == null) return null; - return _pipelines.greaterThanOrEqual(leftJs, rightJs); + return _pipelines.greaterThanOrEqual( + leftJs(argsMap['left'] as Map), + rightJs(argsMap['right'] as Map)); case 'less_than': - final left = argsMap['left']; - final right = argsMap['right']; - if (left == null || right == null) return null; - final leftJs = toExpression(left as Map); - final rightJs = toExpression(right as Map); - if (leftJs == null || rightJs == null) return null; - return _pipelines.lessThan(leftJs, rightJs); + return _pipelines.lessThan( + leftJs(argsMap['left'] as Map), + rightJs(argsMap['right'] as Map)); case 'less_than_or_equal': - final left = argsMap['left']; - final right = argsMap['right']; - if (left == null || right == null) return null; - final leftJs = toExpression(left as Map); - final rightJs = toExpression(right as Map); - if (leftJs == null || rightJs == null) return null; - return _pipelines.lessThanOrEqual(leftJs, rightJs); + return _pipelines.lessThanOrEqual( + leftJs(argsMap['left'] as Map), + rightJs(argsMap['right'] as Map)); case 'filter': final operator = argsMap['operator'] as String?; final expressions = argsMap['expressions'] as List?; if (expressions == null || expressions.isEmpty) return null; final jsList = expressions .map((e) => toExpression(e as Map)) - .whereType() .toList(); - if (jsList.isEmpty) return null; if (jsList.length == 1) return jsList.single; JSAny result = jsList[0]; for (var i = 1; i < jsList.length; i++) { @@ -104,51 +104,26 @@ class PipelineExpressionConverterWeb { } return result; case 'not': - final expression = argsMap['expression']; - if (expression == null) return null; - final exprJs = toExpression(expression as Map); - return exprJs != null ? _pipelines.not(exprJs) : null; + return _pipelines + .not(leftJs(argsMap['expression'] as Map)); case 'exists': - final expression = argsMap['expression'] ?? argsMap['array']; - if (expression == null) return null; - final exprJs = toExpression(expression as Map); - return exprJs != null ? _pipelines.exists(exprJs) : null; + return _pipelines + .exists(leftJs(argsMap['expression'] as Map)); case 'is_absent': - final expression = argsMap['expression'] ?? argsMap['array']; - if (expression == null) return null; - final exprJs = toExpression(expression as Map); - return exprJs != null ? _pipelines.isAbsent(exprJs) : null; + return _pipelines + .isAbsent(leftJs(argsMap['expression'] as Map)); case 'is_error': - final expression = argsMap['expression'] ?? argsMap['array']; - if (expression == null) return null; - final exprJs = toExpression(expression as Map); - return exprJs != null ? _pipelines.isError(exprJs) : null; + return _pipelines + .isError(leftJs(argsMap['expression'] as Map)); case 'array_contains': - final array = argsMap['array']; - final element = argsMap['element']; - if (array == null || element == null) return null; - final arrayJs = toExpression(array as Map); - final elementJs = toExpression(element as Map); - if (arrayJs == null || elementJs == null) return null; - return _pipelines.arrayContains(arrayJs, elementJs); + return _pipelines.arrayContains( + leftJs(argsMap['array'] as Map), + rightJs(argsMap['element'] as Map)); default: return null; } } - /// Converts a serialized boolean expression to JS BooleanExpression for where(). - JSAny toBooleanExpression(Map map) { - final js = toExpression(map); - if (js == null) { - throw UnsupportedError( - 'Pipeline where() on web requires the Firebase JS pipeline expression API ' - '(e.g. field, constant, equal, and, or from firebase/firestore/pipelines). ' - 'Ensure the pipelines module is loaded.', - ); - } - return js; - } - /// Converts orderings list to JS SortStageOptions. Each item: { expression, order_direction }. JSAny toSortOptions(List orderings) { final list = []; @@ -158,7 +133,6 @@ class PipelineExpressionConverterWeb { final dir = m['order_direction'] as String?; if (expr == null) continue; final exprJs = toExpression(expr as Map); - if (exprJs == null) continue; final ordering = (dir == 'desc') ? _pipelines.descending(exprJs) : _pipelines.ascending(exprJs); @@ -179,18 +153,13 @@ class PipelineExpressionConverterWeb { JSAny toAddFieldsOptions(List expressions) { final list = []; for (final e in expressions) { + print('e: $e'); final sel = toSelectable(e is Map ? e : {}); if (sel != null) list.add(sel); } - if (list.isEmpty) { - throw UnsupportedError( - 'Pipeline addFields() on web requires the Firebase JS pipeline expression API.', - ); - } - final obj = JSObject.new; - setProperty(obj, 'fields', list.toJS); - return obj as JSAny; + + return interop.AddFieldsOptionsJsImpl()..fields = list.toJS; } /// Converts a single expression map to a JS Selectable (field or aliased). @@ -208,7 +177,8 @@ class PipelineExpressionConverterWeb { if (alias == null || expression == null) return null; final exprJs = toExpression(expression as Map); if (exprJs == null) return null; - return _pipelines.aliased(exprJs, alias.toJS); + // return _pipelines.aliased(exprJs, alias.toJS); + return exprJs.asAlias(alias.toJS); } final exprJs = toExpression(map); return exprJs; @@ -253,7 +223,8 @@ class PipelineExpressionConverterWeb { /// Converts aggregate stage args to JS AggregateStageOptions. /// Input shape: { aggregate_functions: [...] } or { aggregate_stage: { aggregate_functions: [...] } } or { accumulators: [...] }. /// Each list item is an aliased aggregate: { name: 'alias', args: { alias: String, aggregate_function: { name, args?: { expression } } } }. - interop.AggregateStageOptionsJsImpl toAggregateOptions(Map map) { + interop.AggregateStageOptionsJsImpl toAggregateOptions( + Map map) { final aggregateFunctions = _getAggregateFunctionsList(map); if (aggregateFunctions.isEmpty) { throw UnsupportedError( @@ -326,7 +297,8 @@ class PipelineExpressionConverterWeb { value is Map ? value : null; /// Builds one aggregate function (sum, average, count_all, etc.) from serialized [name] and optional [exprJs]. - interop.AggregateFunctionJsImpl? _buildAggregateFunction(String name, JSAny? exprJs) { + interop.AggregateFunctionJsImpl? _buildAggregateFunction( + String name, JSAny? exprJs) { switch (name) { case 'count_all': return _pipelines.countAll(); diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_web.dart index a633f017ae93..4361b186befc 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_web.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_web.dart @@ -60,12 +60,12 @@ class PipelineResultWeb extends PipelineResultPlatform { firestore_interop.Firestore firestoreWeb, interop.PipelineResultJsImpl jsResult, ) : _document = jsResult.ref != null - ? DocumentReferenceWeb( - firestore, - firestoreWeb, - jsResult.ref!.path.toDart, - ) - : null, + ? DocumentReferenceWeb( + firestore, + firestoreWeb, + jsResult.ref!.path.toDart, + ) + : null, _createTime = _timestampToDateTime(jsResult.createTime), _updateTime = _timestampToDateTime(jsResult.updateTime), _data = _dataFromResult(jsResult), From a1fad454a7a613c6376ddbce6fbd0d8832688d80 Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Thu, 5 Mar 2026 09:39:58 +0100 Subject: [PATCH 60/72] fix(storage,web): contentType inference for web (#18078) * fix(storage,web): contentType inference for web * format --- .../lib/firebase_storage.dart | 4 +- .../firebase_storage/lib/src/reference.dart | 27 +++- .../firebase_storage/pubspec.yaml | 1 + .../firebase_storage/test/reference_test.dart | 125 ++++++++++++++++++ .../firebase_storage/reference_e2e.dart | 40 ++++++ 5 files changed, 192 insertions(+), 5 deletions(-) diff --git a/packages/firebase_storage/firebase_storage/lib/firebase_storage.dart b/packages/firebase_storage/firebase_storage/lib/firebase_storage.dart index e51e882d8b2e..9a19065a691d 100755 --- a/packages/firebase_storage/firebase_storage/lib/firebase_storage.dart +++ b/packages/firebase_storage/firebase_storage/lib/firebase_storage.dart @@ -8,9 +8,6 @@ library firebase_storage; import 'dart:async'; import 'dart:convert' show utf8, base64; import 'dart:io' show File; -// TODO(Lyokone): remove once we bump Flutter SDK min version to 3.3 -// ignore: unnecessary_import -import 'dart:typed_data' show Uint8List; // import 'package:flutter/foundation.dart'; import 'package:firebase_core/firebase_core.dart'; @@ -18,6 +15,7 @@ import 'package:firebase_core_platform_interface/firebase_core_platform_interfac show FirebasePluginPlatform; import 'package:firebase_storage_platform_interface/firebase_storage_platform_interface.dart'; import 'package:flutter/foundation.dart'; +import 'package:mime/mime.dart'; import 'src/utils.dart'; diff --git a/packages/firebase_storage/firebase_storage/lib/src/reference.dart b/packages/firebase_storage/firebase_storage/lib/src/reference.dart index 5b3dbe555385..e5c59e20cf83 100644 --- a/packages/firebase_storage/firebase_storage/lib/src/reference.dart +++ b/packages/firebase_storage/firebase_storage/lib/src/reference.dart @@ -103,13 +103,35 @@ class Reference { return _delegate.getData(maxSize); } + /// Infers the content type from the reference [name] if not already set. + SettableMetadata? _withInferredContentType(SettableMetadata? metadata) { + if (metadata?.contentType != null) return metadata; + + final inferred = lookupMimeType(name); + if (inferred == null) return metadata; + + if (metadata == null) { + return SettableMetadata(contentType: inferred); + } + + return SettableMetadata( + cacheControl: metadata.cacheControl, + contentDisposition: metadata.contentDisposition, + contentEncoding: metadata.contentEncoding, + contentLanguage: metadata.contentLanguage, + contentType: inferred, + customMetadata: metadata.customMetadata, + ); + } + /// Uploads data to this reference's location. /// /// Use this method to upload fixed sized data as a [Uint8List]. /// /// Optionally, you can also set metadata onto the uploaded object. UploadTask putData(Uint8List data, [SettableMetadata? metadata]) { - return UploadTask._(storage, _delegate.putData(data, metadata)); + return UploadTask._( + storage, _delegate.putData(data, _withInferredContentType(metadata))); } /// Upload a [Blob]. Note; this is only supported on web platforms. @@ -117,7 +139,8 @@ class Reference { /// Optionally, you can also set metadata onto the uploaded object. UploadTask putBlob(dynamic blob, [SettableMetadata? metadata]) { assert(blob != null); - return UploadTask._(storage, _delegate.putBlob(blob, metadata)); + return UploadTask._( + storage, _delegate.putBlob(blob, _withInferredContentType(metadata))); } /// Upload a [File] from the filesystem. The file must exist. diff --git a/packages/firebase_storage/firebase_storage/pubspec.yaml b/packages/firebase_storage/firebase_storage/pubspec.yaml index 56902a170449..0c08a4da4b65 100755 --- a/packages/firebase_storage/firebase_storage/pubspec.yaml +++ b/packages/firebase_storage/firebase_storage/pubspec.yaml @@ -25,6 +25,7 @@ dependencies: firebase_storage_web: ^3.11.3 flutter: sdk: flutter + mime: ^2.0.0 dev_dependencies: flutter_test: diff --git a/packages/firebase_storage/firebase_storage/test/reference_test.dart b/packages/firebase_storage/firebase_storage/test/reference_test.dart index 00b065cf9c6a..e38d3fed72bc 100644 --- a/packages/firebase_storage/firebase_storage/test/reference_test.dart +++ b/packages/firebase_storage/firebase_storage/test/reference_test.dart @@ -16,6 +16,7 @@ import 'package:mockito/mockito.dart'; import 'mock.dart'; MockReferencePlatform mockReference = MockReferencePlatform(); +MockReferencePlatform mockJpgReference = MockReferencePlatform(); MockListResultPlatform mockListResultPlatform = MockListResultPlatform(); MockUploadTaskPlatform mockUploadTaskPlatform = MockUploadTaskPlatform(); MockDownloadTaskPlatform mockDownloadTaskPlatform = MockDownloadTaskPlatform(); @@ -308,6 +309,130 @@ Future main() async { }); }); + group('putData() contentType inference', () { + late Reference jpgRef; + + setUp(() { + when(kMockStoragePlatform.ref(any)).thenReturn(mockJpgReference); + when(mockJpgReference.bucket).thenReturn(testBucket); + when(mockJpgReference.fullPath).thenReturn('foo/photo.jpg'); + when(mockJpgReference.name).thenReturn('photo.jpg'); + jpgRef = storage.ref('foo/photo.jpg'); + }); + + test('infers contentType from ref name when no metadata', () { + List list = utf8.encode('hello'); + Uint8List data = Uint8List.fromList(list); + when(mockJpgReference.putData(data, any)) + .thenReturn(mockUploadTaskPlatform); + + jpgRef.putData(data); + + final captured = verify(mockJpgReference.putData(data, captureAny)) + .captured + .single as SettableMetadata; + expect(captured.contentType, 'image/jpeg'); + }); + + test('infers contentType when metadata has no contentType', () { + List list = utf8.encode('hello'); + Uint8List data = Uint8List.fromList(list); + when(mockJpgReference.putData(data, any)) + .thenReturn(mockUploadTaskPlatform); + + jpgRef.putData(data, SettableMetadata(contentLanguage: 'en')); + + final captured = verify(mockJpgReference.putData(data, captureAny)) + .captured + .single as SettableMetadata; + expect(captured.contentType, 'image/jpeg'); + expect(captured.contentLanguage, 'en'); + }); + + test('preserves explicit contentType', () { + List list = utf8.encode('hello'); + Uint8List data = Uint8List.fromList(list); + when(mockJpgReference.putData(data, any)) + .thenReturn(mockUploadTaskPlatform); + + jpgRef.putData( + data, SettableMetadata(contentType: 'application/octet-stream')); + + final captured = verify(mockJpgReference.putData(data, captureAny)) + .captured + .single as SettableMetadata; + expect(captured.contentType, 'application/octet-stream'); + }); + + test('preserves customMetadata when inferring contentType', () { + List list = utf8.encode('hello'); + Uint8List data = Uint8List.fromList(list); + when(mockJpgReference.putData(data, any)) + .thenReturn(mockUploadTaskPlatform); + + jpgRef.putData( + data, SettableMetadata(customMetadata: {'activity': 'test'})); + + final captured = verify(mockJpgReference.putData(data, captureAny)) + .captured + .single as SettableMetadata; + expect(captured.contentType, 'image/jpeg'); + expect(captured.customMetadata, {'activity': 'test'}); + }); + + test('no inference when ref has no extension', () { + // Reset to the default mock with no extension + when(kMockStoragePlatform.ref(any)).thenReturn(mockReference); + when(mockReference.name).thenReturn(testName); + final noExtRef = storage.ref(); + + List list = utf8.encode('hello'); + Uint8List data = Uint8List.fromList(list); + when(mockReference.putData(data)).thenReturn(mockUploadTaskPlatform); + + noExtRef.putData(data); + + verify(mockReference.putData(data)); + }); + }); + + group('putBlob() contentType inference', () { + late Reference jpgRef; + + setUp(() { + when(kMockStoragePlatform.ref(any)).thenReturn(mockJpgReference); + when(mockJpgReference.bucket).thenReturn(testBucket); + when(mockJpgReference.fullPath).thenReturn('foo/photo.jpg'); + when(mockJpgReference.name).thenReturn('photo.jpg'); + jpgRef = storage.ref('foo/photo.jpg'); + }); + + test('infers contentType from ref name when no metadata', () { + when(mockJpgReference.putBlob(any, any)) + .thenReturn(mockUploadTaskPlatform); + + jpgRef.putBlob('blob-data'); + + final captured = verify(mockJpgReference.putBlob(any, captureAny)) + .captured + .single as SettableMetadata; + expect(captured.contentType, 'image/jpeg'); + }); + + test('preserves explicit contentType', () { + when(mockJpgReference.putBlob(any, any)) + .thenReturn(mockUploadTaskPlatform); + + jpgRef.putBlob( + 'blob-data', SettableMetadata(contentType: 'text/plain')); + + final captured = verify(mockJpgReference.putBlob(any, captureAny)) + .captured + .single as SettableMetadata; + expect(captured.contentType, 'text/plain'); + }); + }); + test('hashCode()', () { expect(testRef.hashCode, Object.hash(storage, testFullPath)); }); diff --git a/tests/integration_test/firebase_storage/reference_e2e.dart b/tests/integration_test/firebase_storage/reference_e2e.dart index 7f81f834ded8..f8477798d98e 100644 --- a/tests/integration_test/firebase_storage/reference_e2e.dart +++ b/tests/integration_test/firebase_storage/reference_e2e.dart @@ -307,6 +307,46 @@ void setupReferenceTests() { expect(complete.metadata?.contentType, 'application/json'); }, ); + + test( + 'infers contentType from .json ref path when no contentType set', + () async { + final Uint8List jsonData = + utf8.encode(jsonEncode({'key': 'value'})); + final Reference ref = + storage.ref('flutter-tests').child('flt-infer.json'); + final TaskSnapshot complete = await ref.putData(jsonData); + expect(complete.metadata?.contentType, 'application/json'); + }, + ); + + test( + 'infers contentType from .txt ref path and preserves customMetadata', + () async { + final Uint8List txtData = utf8.encode('hello world'); + final Reference ref = + storage.ref('flutter-tests').child('flt-infer.txt'); + final TaskSnapshot complete = await ref.putData( + txtData, + SettableMetadata( + customMetadata: {'activity': 'test'}, + ), + ); + expect(complete.metadata?.contentType, 'text/plain'); + expect(complete.metadata?.customMetadata?['activity'], 'test'); + }, + ); + + test( + 'infers contentType from .jpg ref path when no metadata provided', + () async { + final Uint8List imgData = Uint8List.fromList([0xFF, 0xD8, 0xFF]); + final Reference ref = + storage.ref('flutter-tests').child('flt-infer.jpg'); + final TaskSnapshot complete = await ref.putData(imgData); + expect(complete.metadata?.contentType, 'image/jpeg'); + }, + ); }, ); From 5bd8a756fdbaa2ebfc49ffc10077b856dd1bbed1 Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Thu, 5 Mar 2026 11:08:07 +0100 Subject: [PATCH 61/72] chore(messaging,web): improve the sample service worker (#18077) --- .../firebase-messaging-sw.ts | 16 + .../bundled-service-worker/package.json | 2 +- .../example/bundled-service-worker/yarn.lock | 663 +++++++++--------- .../example/web/firebase-messaging-sw.js | 12 + 4 files changed, 367 insertions(+), 326 deletions(-) diff --git a/packages/firebase_messaging/firebase_messaging/example/bundled-service-worker/firebase-messaging-sw.ts b/packages/firebase_messaging/firebase_messaging/example/bundled-service-worker/firebase-messaging-sw.ts index 8816259a0278..8e1d32b69e22 100644 --- a/packages/firebase_messaging/firebase_messaging/example/bundled-service-worker/firebase-messaging-sw.ts +++ b/packages/firebase_messaging/firebase_messaging/example/bundled-service-worker/firebase-messaging-sw.ts @@ -13,6 +13,22 @@ self.addEventListener('install', (event) => { console.log(event); }); +// Focus the existing app tab when a notification is clicked. +self.addEventListener('notificationclick', (event) => { + event.notification.close(); + event.waitUntil( + self.clients + .matchAll({ type: 'window', includeUncontrolled: true }) + .then((clientList) => { + for (const client of clientList) { + if (!client.focused) { + return client.focus(); + } + } + }) + ); +}); + const app = initializeApp({ apiKey: 'AIzaSyB7wZb2tO1-Fs6GbDADUSTs2Qs3w08Hovw', appId: '1:406099696497:web:87e25e51afe982cd3574d0', diff --git a/packages/firebase_messaging/firebase_messaging/example/bundled-service-worker/package.json b/packages/firebase_messaging/firebase_messaging/example/bundled-service-worker/package.json index a890537d619e..3f3d3ce3102d 100644 --- a/packages/firebase_messaging/firebase_messaging/example/bundled-service-worker/package.json +++ b/packages/firebase_messaging/firebase_messaging/example/bundled-service-worker/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "firebase": "10" + "firebase": "12" }, "devDependencies": { "esbuild": "^0.25.0" diff --git a/packages/firebase_messaging/firebase_messaging/example/bundled-service-worker/yarn.lock b/packages/firebase_messaging/firebase_messaging/example/bundled-service-worker/yarn.lock index c9ba8f2446c2..64d0ae8cf139 100644 --- a/packages/firebase_messaging/firebase_messaging/example/bundled-service-worker/yarn.lock +++ b/packages/firebase_messaging/firebase_messaging/example/bundled-service-worker/yarn.lock @@ -127,383 +127,396 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz#c8e119a30a7c8d60b9d2e22d2073722dde3b710b" integrity sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ== -"@fastify/busboy@^2.0.0": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" - integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== - -"@firebase/analytics-compat@0.2.7": - version "0.2.7" - resolved "https://registry.yarnpkg.com/@firebase/analytics-compat/-/analytics-compat-0.2.7.tgz#affad547d6db9c13424950df972019fb0e2ecaeb" - integrity sha512-17VCly4P0VFBDqaaal7m1nhyYQwsygtaTpSsnc51sFPRrr9XIYtnD8ficon9fneEGEoJQ2g7OtASvhwX9EbK8g== - dependencies: - "@firebase/analytics" "0.10.1" - "@firebase/analytics-types" "0.8.0" - "@firebase/component" "0.6.5" - "@firebase/util" "1.9.4" +"@firebase/ai@2.9.0": + version "2.9.0" + resolved "https://registry.npmjs.org/@firebase/ai/-/ai-2.9.0.tgz#9e6f3546eb688e31488f3e081702773300d609f1" + integrity sha512-NPvBBuvdGo9x3esnABAucFYmqbBmXvyTMimBq2PCuLZbdANZoHzGlx7vfzbwNDaEtCBq4RGGNMliLIv6bZ+PtA== + dependencies: + "@firebase/app-check-interop-types" "0.3.3" + "@firebase/component" "0.7.1" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.14.0" + tslib "^2.1.0" + +"@firebase/analytics-compat@0.2.26": + version "0.2.26" + resolved "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.26.tgz#2ec74dc4d41d075d38fab7670c33464803214f2f" + integrity sha512-0j2ruLOoVSwwcXAF53AMoniJKnkwiTjGVfic5LDzqiRkR13vb5j6TXMeix787zbLeQtN/m1883Yv1TxI0gItbA== + dependencies: + "@firebase/analytics" "0.10.20" + "@firebase/analytics-types" "0.8.3" + "@firebase/component" "0.7.1" + "@firebase/util" "1.14.0" tslib "^2.1.0" -"@firebase/analytics-types@0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.8.0.tgz#551e744a29adbc07f557306530a2ec86add6d410" - integrity sha512-iRP+QKI2+oz3UAh4nPEq14CsEjrjD6a5+fuypjScisAh9kXKFvdJOZJDwk7kikLvWVLGEs9+kIUS4LPQV7VZVw== +"@firebase/analytics-types@0.8.3": + version "0.8.3" + resolved "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.3.tgz#d08cd39a6209693ca2039ba7a81570dfa6c1518f" + integrity sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg== -"@firebase/analytics@0.10.1": - version "0.10.1" - resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.10.1.tgz#97d750020c5b3b41fd5191074c683a7a8c8900a5" - integrity sha512-5mnH1aQa99J5lZMJwTNzIoRc4yGXHf+fOn+EoEWhCDA3XGPweGHcylCbqq+G1wVJmfILL57fohDMa8ftMZ+44g== +"@firebase/analytics@0.10.20": + version "0.10.20" + resolved "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.20.tgz#ec3aaacaa157b979b6e2c12ac5a30e6484b19ddf" + integrity sha512-adGTNVUWH5q66tI/OQuKLSN6mamPpfYhj0radlH2xt+3eL6NFPtXoOs+ulvs+UsmK27vNFx5FjRDfWk+TyduHg== dependencies: - "@firebase/component" "0.6.5" - "@firebase/installations" "0.6.5" - "@firebase/logger" "0.4.0" - "@firebase/util" "1.9.4" + "@firebase/component" "0.7.1" + "@firebase/installations" "0.6.20" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.14.0" tslib "^2.1.0" -"@firebase/app-check-compat@0.3.9": - version "0.3.9" - resolved "https://registry.yarnpkg.com/@firebase/app-check-compat/-/app-check-compat-0.3.9.tgz#c67caa1cd5043fecab7f8ba1bc45ab047210ad83" - integrity sha512-7LxyupQ8XeEHRh72mO+tqm69kHT6KbWi2KtFMGedJ6tNbwzFzojcXESMKN8RpADXbYoQgY3loWMJjMx4r2Zt7w== +"@firebase/app-check-compat@0.4.1": + version "0.4.1" + resolved "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.4.1.tgz#2ff3f4b28fd4ee136e7ee12b99edac8cdc8cbbb1" + integrity sha512-yjSvSl5B1u4CirnxhzirN1uiTRCRfx+/qtfbyeyI+8Cx8Cw1RWAIO/OqytPSVwLYbJJ1vEC3EHfxazRaMoWKaA== dependencies: - "@firebase/app-check" "0.8.2" - "@firebase/app-check-types" "0.5.0" - "@firebase/component" "0.6.5" - "@firebase/logger" "0.4.0" - "@firebase/util" "1.9.4" + "@firebase/app-check" "0.11.1" + "@firebase/app-check-types" "0.5.3" + "@firebase/component" "0.7.1" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.14.0" tslib "^2.1.0" -"@firebase/app-check-interop-types@0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.0.tgz#b27ea1397cb80427f729e4bbf3a562f2052955c4" - integrity sha512-xAxHPZPIgFXnI+vb4sbBjZcde7ZluzPPaSK7Lx3/nmuVk4TjZvnL8ONnkd4ERQKL8WePQySU+pRcWkh8rDf5Sg== +"@firebase/app-check-interop-types@0.3.3": + version "0.3.3" + resolved "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz#ed9c4a4f48d1395ef378f007476db3940aa5351a" + integrity sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A== -"@firebase/app-check-types@0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@firebase/app-check-types/-/app-check-types-0.5.0.tgz#1b02826213d7ce6a1cf773c329b46ea1c67064f4" - integrity sha512-uwSUj32Mlubybw7tedRzR24RP8M8JUVR3NPiMk3/Z4bCmgEKTlQBwMXrehDAZ2wF+TsBq0SN1c6ema71U/JPyQ== +"@firebase/app-check-types@0.5.3": + version "0.5.3" + resolved "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.3.tgz#38ba954acf4bffe451581a32fffa20337f11d8e5" + integrity sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng== -"@firebase/app-check@0.8.2": - version "0.8.2" - resolved "https://registry.yarnpkg.com/@firebase/app-check/-/app-check-0.8.2.tgz#9ede3558cc7dc1ac8206a772ba692e67daf7e65e" - integrity sha512-A2B5+ldOguYAeqW1quFN5qNdruSNRrg4W59ag1Eq6QzxuHNIkrE+TrapfrW/z5NYFjCxAYqr/unVCgmk80Dwcg== +"@firebase/app-check@0.11.1": + version "0.11.1" + resolved "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.11.1.tgz#f327a2190b405eb566a93cd5c7eb8ebe7556032b" + integrity sha512-gmKfwQ2k8aUQlOyRshc+fOQLq0OwUmibIZvpuY1RDNu2ho0aTMlwxOuEiJeYOs7AxzhSx7gnXPFNsXCFbnvXUQ== dependencies: - "@firebase/component" "0.6.5" - "@firebase/logger" "0.4.0" - "@firebase/util" "1.9.4" + "@firebase/component" "0.7.1" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.14.0" tslib "^2.1.0" -"@firebase/app-compat@0.2.29": - version "0.2.29" - resolved "https://registry.yarnpkg.com/@firebase/app-compat/-/app-compat-0.2.29.tgz#d55a5800acaebc0a1a0ea33d548bb80dc711ec93" - integrity sha512-NqUdegXJfwphx9i/2bOE2CTZ55TC9bbDg+iwkxVShsPBJhD3CzQJkFhoDz4ccfbJaKZGsqjY3fisgX5kbDROnA== +"@firebase/app-compat@0.5.9": + version "0.5.9" + resolved "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.9.tgz#464efce323951283c6812893d251dddee15d61da" + integrity sha512-e5LzqjO69/N2z7XcJeuMzIp4wWnW696dQeaHAUpQvGk89gIWHAIvG6W+mA3UotGW6jBoqdppEJ9DnuwbcBByug== dependencies: - "@firebase/app" "0.9.29" - "@firebase/component" "0.6.5" - "@firebase/logger" "0.4.0" - "@firebase/util" "1.9.4" + "@firebase/app" "0.14.9" + "@firebase/component" "0.7.1" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.14.0" tslib "^2.1.0" -"@firebase/app-types@0.9.0": - version "0.9.0" - resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.9.0.tgz#35b5c568341e9e263b29b3d2ba0e9cfc9ec7f01e" - integrity sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q== +"@firebase/app-types@0.9.3": + version "0.9.3" + resolved "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz#8408219eae9b1fb74f86c24e7150a148460414ad" + integrity sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw== -"@firebase/app@0.9.29": - version "0.9.29" - resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.9.29.tgz#444280f0ddf1da4b2a974c86a6a8c6405d950fb7" - integrity sha512-HbKTjfmILklasIu/ij6zKnFf3SgLYXkBDVN7leJfVGmohl+zA7Ig+eXM1ZkT1pyBJ8FTYR+mlOJer/lNEnUCtw== +"@firebase/app@0.14.9": + version "0.14.9" + resolved "https://registry.npmjs.org/@firebase/app/-/app-0.14.9.tgz#b7f740904deee2889a3d6115736b16fdbdc853c7" + integrity sha512-3gtUX0e584MYkKBQMgSECMvE1Dwzg+eONefDQ0wxVSe5YMBsZwdN5pL7UapwWBlV8+i8QCztF9TP947tEjZAGA== dependencies: - "@firebase/component" "0.6.5" - "@firebase/logger" "0.4.0" - "@firebase/util" "1.9.4" + "@firebase/component" "0.7.1" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.14.0" idb "7.1.1" tslib "^2.1.0" -"@firebase/auth-compat@0.5.4": - version "0.5.4" - resolved "https://registry.yarnpkg.com/@firebase/auth-compat/-/auth-compat-0.5.4.tgz#a7ae705e5f85e786f280bae87fe06bda2d686d05" - integrity sha512-EtRVW9s0YsuJv3GnOGDoLUW3Pp9f3HcqWA2WK92E30Qa0FEVRwCSRLVQwn9td+SLVY3AP9gi/auC1q3osd4yCg== +"@firebase/auth-compat@0.6.3": + version "0.6.3" + resolved "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.6.3.tgz#8e085d98bd133081e7e7d37b7fb421b876694847" + integrity sha512-nHOkupcYuGVxI1AJJ/OBhLPaRokbP14Gq4nkkoVvf1yvuREEWqdnrYB/CdsSnPxHMAnn5wJIKngxBF9jNX7s/Q== dependencies: - "@firebase/auth" "1.6.2" - "@firebase/auth-types" "0.12.0" - "@firebase/component" "0.6.5" - "@firebase/util" "1.9.4" + "@firebase/auth" "1.12.1" + "@firebase/auth-types" "0.13.0" + "@firebase/component" "0.7.1" + "@firebase/util" "1.14.0" tslib "^2.1.0" - undici "5.28.3" - -"@firebase/auth-interop-types@0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz#78884f24fa539e34a06c03612c75f222fcc33742" - integrity sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg== - -"@firebase/auth-types@0.12.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.12.0.tgz#f28e1b68ac3b208ad02a15854c585be6da3e8e79" - integrity sha512-pPwaZt+SPOshK8xNoiQlK5XIrS97kFYc3Rc7xmy373QsOJ9MmqXxLaYssP5Kcds4wd2qK//amx/c+A8O2fVeZA== - -"@firebase/auth@1.6.2": - version "1.6.2" - resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-1.6.2.tgz#d8a9a622b8d4e8eb8c42ea544fcf647d0494651c" - integrity sha512-BFo/Nj1AAbKLbFiUyXCcnT/bSqMJicFOgdTAKzlXvCul7+eUE29vWmzd1g59O3iKAxvv3+fbQYjQVJpNTTHIyw== - dependencies: - "@firebase/component" "0.6.5" - "@firebase/logger" "0.4.0" - "@firebase/util" "1.9.4" + +"@firebase/auth-interop-types@0.2.4": + version "0.2.4" + resolved "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz#176a08686b0685596ff03d7879b7e4115af53de0" + integrity sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA== + +"@firebase/auth-types@0.13.0": + version "0.13.0" + resolved "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.0.tgz#ae6e0015e3bd4bfe18edd0942b48a0a118a098d9" + integrity sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg== + +"@firebase/auth@1.12.1": + version "1.12.1" + resolved "https://registry.npmjs.org/@firebase/auth/-/auth-1.12.1.tgz#5eb1c3bf99dfbe7025578a5f1439cc073a4183f0" + integrity sha512-nXKj7d5bMBlnq6XpcQQpmnSVwEeHBkoVbY/+Wk0P1ebLSICoH4XPtvKOFlXKfIHmcS84mLQ99fk3njlDGKSDtw== + dependencies: + "@firebase/component" "0.7.1" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.14.0" tslib "^2.1.0" - undici "5.28.3" -"@firebase/component@0.6.5": - version "0.6.5" - resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.6.5.tgz#8cc7334f2081d700f2769caaa8dae3ac4c1fe37e" - integrity sha512-2tVDk1ixi12sbDmmfITK8lxSjmcb73BMF6Qwc3U44hN/J1Fi1QY/Hnnb6klFlbB9/G16a3J3d4nXykye2EADTw== +"@firebase/component@0.7.1": + version "0.7.1" + resolved "https://registry.npmjs.org/@firebase/component/-/component-0.7.1.tgz#f16376146d77034ac5055834de25405e6c011491" + integrity sha512-mFzsm7CLHR60o08S23iLUY8m/i6kLpOK87wdEFPLhdlCahaxKmWOwSVGiWoENYSmFJJoDhrR3gKSCxz7ENdIww== dependencies: - "@firebase/util" "1.9.4" + "@firebase/util" "1.14.0" tslib "^2.1.0" -"@firebase/database-compat@1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@firebase/database-compat/-/database-compat-1.0.3.tgz#f7a255af6208d2d4d7af10ec2c9ecd9af4ff52d5" - integrity sha512-7tHEOcMbK5jJzHWyphPux4osogH/adWwncxdMxdBpB9g1DNIyY4dcz1oJdlkXGM/i/AjUBesZsd5CuwTRTBNTw== +"@firebase/data-connect@0.4.0": + version "0.4.0" + resolved "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.4.0.tgz#957d2e0ee602d7120b4c5dbcb8494f911b8a2e47" + integrity sha512-vLXM6WHNIR3VtEeYNUb/5GTsUOyl3Of4iWNZHBe1i9f88sYFnxybJNWVBjvJ7flhCyF8UdxGpzWcUnv6F5vGfg== dependencies: - "@firebase/component" "0.6.5" - "@firebase/database" "1.0.3" - "@firebase/database-types" "1.0.1" - "@firebase/logger" "0.4.0" - "@firebase/util" "1.9.4" + "@firebase/auth-interop-types" "0.2.4" + "@firebase/component" "0.7.1" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.14.0" + tslib "^2.1.0" + +"@firebase/database-compat@2.1.1": + version "2.1.1" + resolved "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.1.tgz#8ab656d2f6b53d1645b86fa846295db4734b9ac5" + integrity sha512-heAEVZ9Z8c8PnBUcmGh91JHX0cXcVa1yESW/xkLuwaX7idRFyLiN8sl73KXpR8ZArGoPXVQDanBnk6SQiekRCQ== + dependencies: + "@firebase/component" "0.7.1" + "@firebase/database" "1.1.1" + "@firebase/database-types" "1.0.17" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.14.0" tslib "^2.1.0" -"@firebase/database-types@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-1.0.1.tgz#1e7cd9fec03f6ca772c019d839cc72d9b2eda63c" - integrity sha512-Tmcmx5XgiI7UVF/4oGg2P3AOTfq3WKEPsm2yf+uXtN7uG/a4WTWhVMrXGYRY2ZUL1xPxv9V33wQRJ+CcrUhVXw== +"@firebase/database-types@1.0.17": + version "1.0.17" + resolved "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.17.tgz#6b7a14d81655e9ee5e87c26dc853c24d9737e4fe" + integrity sha512-4eWaM5fW3qEIHjGzfi3cf0Jpqi1xQsAdT6rSDE1RZPrWu8oGjgrq6ybMjobtyHQFgwGCykBm4YM89qDzc+uG/w== dependencies: - "@firebase/app-types" "0.9.0" - "@firebase/util" "1.9.4" + "@firebase/app-types" "0.9.3" + "@firebase/util" "1.14.0" -"@firebase/database@1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@firebase/database/-/database-1.0.3.tgz#88caee93188d28aca355236e9ad69f373f628804" - integrity sha512-9fjqLt9JzL46gw9+NRqsgQEMjgRwfd8XtzcKqG+UYyhVeFCdVRQ0Wp6Dw/dvYHnbH5vNEKzNv36dcB4p+PIAAA== +"@firebase/database@1.1.1": + version "1.1.1" + resolved "https://registry.npmjs.org/@firebase/database/-/database-1.1.1.tgz#591610b5087ffc25cc56486ad03749b09c887759" + integrity sha512-LwIXe8+mVHY5LBPulWECOOIEXDiatyECp/BOlu0gOhe+WOcKjWHROaCbLlkFTgHMY7RHr5MOxkLP/tltWAH3dA== dependencies: - "@firebase/app-check-interop-types" "0.3.0" - "@firebase/auth-interop-types" "0.2.1" - "@firebase/component" "0.6.5" - "@firebase/logger" "0.4.0" - "@firebase/util" "1.9.4" + "@firebase/app-check-interop-types" "0.3.3" + "@firebase/auth-interop-types" "0.2.4" + "@firebase/component" "0.7.1" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.14.0" faye-websocket "0.11.4" tslib "^2.1.0" -"@firebase/firestore-compat@0.3.27": - version "0.3.27" - resolved "https://registry.yarnpkg.com/@firebase/firestore-compat/-/firestore-compat-0.3.27.tgz#146024bf772f1b6aa65a7b9e17979d59c2fb5fe0" - integrity sha512-gY2q0fCDJvPg/IurZQbBM7MIVjxA1/LsvfgFOubUTrex5KTY9qm4/2V2R79eAs8Q+b4B8soDtlEjk6L8BW1Crw== +"@firebase/firestore-compat@0.4.6": + version "0.4.6" + resolved "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.6.tgz#30a20be30a72e80b0cfa32d5d693564daff6911a" + integrity sha512-NgVyR4hHHN2FvSNQOtbgBOuVsEdD/in30d9FKbEvvITiAChrBN2nBstmhfjI4EOTnHaP8zigwvkNYFI9yKGAkQ== dependencies: - "@firebase/component" "0.6.5" - "@firebase/firestore" "4.5.0" - "@firebase/firestore-types" "3.0.0" - "@firebase/util" "1.9.4" + "@firebase/component" "0.7.1" + "@firebase/firestore" "4.12.0" + "@firebase/firestore-types" "3.0.3" + "@firebase/util" "1.14.0" tslib "^2.1.0" -"@firebase/firestore-types@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-3.0.0.tgz#f3440d5a1cc2a722d361b24cefb62ca8b3577af3" - integrity sha512-Meg4cIezHo9zLamw0ymFYBD4SMjLb+ZXIbuN7T7ddXN6MGoICmOTq3/ltdCGoDCS2u+H1XJs2u/cYp75jsX9Qw== - -"@firebase/firestore@4.5.0": - version "4.5.0" - resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-4.5.0.tgz#f614495970d897b146c5d6cec17c213db0528497" - integrity sha512-rXS6v4HbsN6vZQlq2fLW1ZHb+J5SnS+8Zqb/McbKFIrGYjPUZo5CyO75mkgtlR1tCYAwCebaqoEWb6JHgZv/ww== - dependencies: - "@firebase/component" "0.6.5" - "@firebase/logger" "0.4.0" - "@firebase/util" "1.9.4" - "@firebase/webchannel-wrapper" "0.10.5" +"@firebase/firestore-types@3.0.3": + version "3.0.3" + resolved "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.3.tgz#7d0c3dd8850c0193d8f5ee0cc8f11961407742c1" + integrity sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q== + +"@firebase/firestore@4.12.0": + version "4.12.0" + resolved "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.12.0.tgz#3321155f66d70c749924c635bb1f0deb92254df3" + integrity sha512-PM47OyiiAAoAMB8kkq4Je14mTciaRoAPDd3ng3Ckqz9i2TX9D9LfxIRcNzP/OxzNV4uBKRq6lXoOggkJBQR3Gw== + dependencies: + "@firebase/component" "0.7.1" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.14.0" + "@firebase/webchannel-wrapper" "1.0.5" "@grpc/grpc-js" "~1.9.0" "@grpc/proto-loader" "^0.7.8" tslib "^2.1.0" - undici "5.28.3" -"@firebase/functions-compat@0.3.8": - version "0.3.8" - resolved "https://registry.yarnpkg.com/@firebase/functions-compat/-/functions-compat-0.3.8.tgz#a83a7ad2788db48483ccc86a80a12f0d824133da" - integrity sha512-VDHSw6UOu8RxfgAY/q8e+Jn+9Fh60Fc28yck0yfMsi2e0BiWgonIMWkFspFGGLgOJebTHl+hc+9v91rhzU6xlg== +"@firebase/functions-compat@0.4.2": + version "0.4.2" + resolved "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.4.2.tgz#5788b9d33a700164eefd0b4e455de87cd62d635c" + integrity sha512-YNxgnezvZDkqxqXa6cT7/oTeD4WXbxgIP7qZp4LFnathQv5o2omM6EoIhXiT9Ie5AoQDcIhG9Y3/dj+DFJGaGQ== dependencies: - "@firebase/component" "0.6.5" - "@firebase/functions" "0.11.2" - "@firebase/functions-types" "0.6.0" - "@firebase/util" "1.9.4" + "@firebase/component" "0.7.1" + "@firebase/functions" "0.13.2" + "@firebase/functions-types" "0.6.3" + "@firebase/util" "1.14.0" tslib "^2.1.0" -"@firebase/functions-types@0.6.0": - version "0.6.0" - resolved "https://registry.yarnpkg.com/@firebase/functions-types/-/functions-types-0.6.0.tgz#ccd7000dc6fc668f5acb4e6a6a042a877a555ef2" - integrity sha512-hfEw5VJtgWXIRf92ImLkgENqpL6IWpYaXVYiRkFY1jJ9+6tIhWM7IzzwbevwIIud/jaxKVdRzD7QBWfPmkwCYw== - -"@firebase/functions@0.11.2": - version "0.11.2" - resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.11.2.tgz#bcd10d7e7fa3cd185a6c3efe1776731b0222c14d" - integrity sha512-2NULTYOZbu0rXczwfYdqQH0w1FmmYrKjTy1YPQSHLCAkMBdfewoKmVm4Lyo2vRn0H9ZndciLY7NszKDFt9MKCQ== - dependencies: - "@firebase/app-check-interop-types" "0.3.0" - "@firebase/auth-interop-types" "0.2.1" - "@firebase/component" "0.6.5" - "@firebase/messaging-interop-types" "0.2.0" - "@firebase/util" "1.9.4" +"@firebase/functions-types@0.6.3": + version "0.6.3" + resolved "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.3.tgz#f5faf770248b13f45d256f614230da6a11bfb654" + integrity sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg== + +"@firebase/functions@0.13.2": + version "0.13.2" + resolved "https://registry.npmjs.org/@firebase/functions/-/functions-0.13.2.tgz#2e7936898afcdfa391e564e39049e0e908282420" + integrity sha512-tHduUD+DeokM3NB1QbHCvEMoL16e8Z8JSkmuVA4ROoJKPxHn8ibnecHPO2e3nVCJR1D9OjuKvxz4gksfq92/ZQ== + dependencies: + "@firebase/app-check-interop-types" "0.3.3" + "@firebase/auth-interop-types" "0.2.4" + "@firebase/component" "0.7.1" + "@firebase/messaging-interop-types" "0.2.3" + "@firebase/util" "1.14.0" tslib "^2.1.0" - undici "5.28.3" -"@firebase/installations-compat@0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@firebase/installations-compat/-/installations-compat-0.2.5.tgz#e23ff86dc5a4b856f5f3d3abafeda7362daa38c5" - integrity sha512-usvoIaog5CHEw082HXLrKAZ1qd4hIC3N/LDe2NqBgI3pkGE/7auLVM4Gn5gvyryp0x8z/IP1+d9fkGUj2OaGLQ== +"@firebase/installations-compat@0.2.20": + version "0.2.20" + resolved "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.20.tgz#f17bcd7623f1283937ac3192c3293dd68037fcdc" + integrity sha512-9C9pL/DIEGucmoPj8PlZTnztbX3nhNj5RTYVpUM7wQq/UlHywaYv99969JU/WHLvi9ptzIogXYS9d1eZ6XFe9g== dependencies: - "@firebase/component" "0.6.5" - "@firebase/installations" "0.6.5" - "@firebase/installations-types" "0.5.0" - "@firebase/util" "1.9.4" + "@firebase/component" "0.7.1" + "@firebase/installations" "0.6.20" + "@firebase/installations-types" "0.5.3" + "@firebase/util" "1.14.0" tslib "^2.1.0" -"@firebase/installations-types@0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@firebase/installations-types/-/installations-types-0.5.0.tgz#2adad64755cd33648519b573ec7ec30f21fb5354" - integrity sha512-9DP+RGfzoI2jH7gY4SlzqvZ+hr7gYzPODrbzVD82Y12kScZ6ZpRg/i3j6rleto8vTFC8n6Len4560FnV1w2IRg== +"@firebase/installations-types@0.5.3": + version "0.5.3" + resolved "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.3.tgz#cac8a14dd49f09174da9df8ae453f9b359c3ef2f" + integrity sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA== -"@firebase/installations@0.6.5": - version "0.6.5" - resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.6.5.tgz#1d6e0a581747bfaca54f11bf722e1f3da00dcc9c" - integrity sha512-0xxnQWw8rSRzu0ZOCkZaO+MJ0LkDAfwwTB2Z1SxRK6FAz5xkxD1ZUwM0WbCRni49PKubCrZYOJ6yg7tSjU7AKA== +"@firebase/installations@0.6.20": + version "0.6.20" + resolved "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.20.tgz#a019da0e71d5a0bb59b58e43a8edef0153368b94" + integrity sha512-LOzvR7XHPbhS0YB5ANXhqXB5qZlntPpwU/4KFwhSNpXNsGk/sBQ9g5hepi0y0/MfenJLe2v7t644iGOOElQaHQ== dependencies: - "@firebase/component" "0.6.5" - "@firebase/util" "1.9.4" + "@firebase/component" "0.7.1" + "@firebase/util" "1.14.0" idb "7.1.1" tslib "^2.1.0" -"@firebase/logger@0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@firebase/logger/-/logger-0.4.0.tgz#15ecc03c452525f9d47318ad9491b81d1810f113" - integrity sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA== +"@firebase/logger@0.5.0": + version "0.5.0" + resolved "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.0.tgz#a9e55b1c669a0983dc67127fa4a5964ce8ed5e1b" + integrity sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g== dependencies: tslib "^2.1.0" -"@firebase/messaging-compat@0.2.6": - version "0.2.6" - resolved "https://registry.yarnpkg.com/@firebase/messaging-compat/-/messaging-compat-0.2.6.tgz#ea89934bff5f048576dc1c4ce87e0c4c2141829b" - integrity sha512-Q2xC1s4L7Vpss7P7Gy6GuIS+xmJrf/vm9+gX76IK1Bo1TjoKwleCLHt1LHkPz5Rvqg5pTgzzI8qqPhBpZosFCg== +"@firebase/messaging-compat@0.2.24": + version "0.2.24" + resolved "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.24.tgz#9ea9bf0d88d605c382dd416e231203310da7b867" + integrity sha512-wXH8FrKbJvFuFe6v98TBhAtvgknxKIZtGM/wCVsfpOGmaAE80bD8tBxztl+uochjnFb9plihkd6mC4y7sZXSpA== dependencies: - "@firebase/component" "0.6.5" - "@firebase/messaging" "0.12.6" - "@firebase/util" "1.9.4" + "@firebase/component" "0.7.1" + "@firebase/messaging" "0.12.24" + "@firebase/util" "1.14.0" tslib "^2.1.0" -"@firebase/messaging-interop-types@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.0.tgz#6056f8904a696bf0f7fdcf5f2ca8f008e8f6b064" - integrity sha512-ujA8dcRuVeBixGR9CtegfpU4YmZf3Lt7QYkcj693FFannwNuZgfAYaTmbJ40dtjB81SAu6tbFPL9YLNT15KmOQ== +"@firebase/messaging-interop-types@0.2.3": + version "0.2.3" + resolved "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz#e647c9cd1beecfe6a6e82018a6eec37555e4da3e" + integrity sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q== -"@firebase/messaging@0.12.6": - version "0.12.6" - resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.12.6.tgz#ac7c59ed39a89e00990e3b6dfd7929e13dd77563" - integrity sha512-IORsPp9IPWq4j4yEhTOZ6GAGi3gQwGc+4yexmTAlya+qeBRSdRnJg2iIU/aj+tcKDQYr9RQuQPgHHOdFIx//vA== +"@firebase/messaging@0.12.24": + version "0.12.24" + resolved "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.24.tgz#ac586f68a038d8595ee8cbaea2a4b60e1886029a" + integrity sha512-UtKoubegAhHyehcB7iQjvQ8OVITThPbbWk3g2/2ze42PrQr6oe6OmCElYQkBrE5RDCeMTNucXejbdulrQ2XwVg== dependencies: - "@firebase/component" "0.6.5" - "@firebase/installations" "0.6.5" - "@firebase/messaging-interop-types" "0.2.0" - "@firebase/util" "1.9.4" + "@firebase/component" "0.7.1" + "@firebase/installations" "0.6.20" + "@firebase/messaging-interop-types" "0.2.3" + "@firebase/util" "1.14.0" idb "7.1.1" tslib "^2.1.0" -"@firebase/performance-compat@0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@firebase/performance-compat/-/performance-compat-0.2.5.tgz#9b827b1801fca19d8c379792326c076877ac5b91" - integrity sha512-jJwJkVyDcIMBaVGrZ6CRGs4m5FCZsWB5QCWYI3FdsHyIa9/TfteNDilxj9wGciF2naFIHDW7TgE69U5dAH9Ktg== +"@firebase/performance-compat@0.2.23": + version "0.2.23" + resolved "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.23.tgz#e4e440878c5be1e11e01d5fe28e5e1fe73d36857" + integrity sha512-c7qOAGBUAOpIuUlHu1axWcrCVtIYKPMhH0lMnoCDWnPwn1HcPuPUBVTWETbC7UWw71RMJF8DpirfWXzMWJQfgA== dependencies: - "@firebase/component" "0.6.5" - "@firebase/logger" "0.4.0" - "@firebase/performance" "0.6.5" - "@firebase/performance-types" "0.2.0" - "@firebase/util" "1.9.4" + "@firebase/component" "0.7.1" + "@firebase/logger" "0.5.0" + "@firebase/performance" "0.7.10" + "@firebase/performance-types" "0.2.3" + "@firebase/util" "1.14.0" tslib "^2.1.0" -"@firebase/performance-types@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.2.0.tgz#400685f7a3455970817136d9b48ce07a4b9562ff" - integrity sha512-kYrbr8e/CYr1KLrLYZZt2noNnf+pRwDq2KK9Au9jHrBMnb0/C9X9yWSXmZkFt4UIdsQknBq8uBB7fsybZdOBTA== +"@firebase/performance-types@0.2.3": + version "0.2.3" + resolved "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.3.tgz#5ce64e90fa20ab5561f8b62a305010cf9fab86fb" + integrity sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ== -"@firebase/performance@0.6.5": - version "0.6.5" - resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.6.5.tgz#5255fb18329719bc1fb2db29262e5ec15cbace06" - integrity sha512-OzAGcWhOqEFH9GdwUuY0oC5FSlnMejcnmSAhR+EjpI7exdDvixyLyCR4txjSHYNTbumrFBG+EP8GO11CNXRaJA== +"@firebase/performance@0.7.10": + version "0.7.10" + resolved "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.10.tgz#a282de63f064477a62cf0379c3374f3cc693ffa4" + integrity sha512-8nRFld+Ntzp5cLKzZuG9g+kBaSn8Ks9dmn87UQGNFDygbmR6ebd8WawauEXiJjMj1n70ypkvAOdE+lzeyfXtGA== dependencies: - "@firebase/component" "0.6.5" - "@firebase/installations" "0.6.5" - "@firebase/logger" "0.4.0" - "@firebase/util" "1.9.4" + "@firebase/component" "0.7.1" + "@firebase/installations" "0.6.20" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.14.0" tslib "^2.1.0" - -"@firebase/remote-config-compat@0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@firebase/remote-config-compat/-/remote-config-compat-0.2.5.tgz#b6850a45567db5372778668c796a8d49723413f3" - integrity sha512-ImkNnLuGrD/bylBHDJigSY6LMwRrwt37wQbsGZhWG4QQ6KLzHzSf0nnFRRFvkOZodEUE57Ib8l74d6Yn/6TDUQ== - dependencies: - "@firebase/component" "0.6.5" - "@firebase/logger" "0.4.0" - "@firebase/remote-config" "0.4.5" - "@firebase/remote-config-types" "0.3.0" - "@firebase/util" "1.9.4" + web-vitals "^4.2.4" + +"@firebase/remote-config-compat@0.2.22": + version "0.2.22" + resolved "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.22.tgz#5d34d4e856c8a9010e77be5fc2dc183657ade58c" + integrity sha512-uW/eNKKtRBot2gnCC5mnoy5Voo2wMzZuQ7dwqqGHU176fO9zFgMwKiRzk+aaC99NLrFk1KOmr0ZVheD+zdJmjQ== + dependencies: + "@firebase/component" "0.7.1" + "@firebase/logger" "0.5.0" + "@firebase/remote-config" "0.8.1" + "@firebase/remote-config-types" "0.5.0" + "@firebase/util" "1.14.0" tslib "^2.1.0" -"@firebase/remote-config-types@0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@firebase/remote-config-types/-/remote-config-types-0.3.0.tgz#689900dcdb3e5c059e8499b29db393e4e51314b4" - integrity sha512-RtEH4vdcbXZuZWRZbIRmQVBNsE7VDQpet2qFvq6vwKLBIQRQR5Kh58M4ok3A3US8Sr3rubYnaGqZSurCwI8uMA== - -"@firebase/remote-config@0.4.5": - version "0.4.5" - resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.4.5.tgz#1aae1a4639bb0dddc14671c10dcd92c8a83c58c1" - integrity sha512-rGLqc/4OmxrS39RA9kgwa6JmgWytQuMo+B8pFhmGp3d++x2Hf9j+MLQfhOLyyUo64fNw20J19mLXhrXvKHsjZQ== - dependencies: - "@firebase/component" "0.6.5" - "@firebase/installations" "0.6.5" - "@firebase/logger" "0.4.0" - "@firebase/util" "1.9.4" +"@firebase/remote-config-types@0.5.0": + version "0.5.0" + resolved "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.5.0.tgz#f0f503b32edda3384f5252f9900cd9613adbb99c" + integrity sha512-vI3bqLoF14L/GchtgayMiFpZJF+Ao3uR8WCde0XpYNkSokDpAKca2DxvcfeZv7lZUqkUwQPL2wD83d3vQ4vvrg== + +"@firebase/remote-config@0.8.1": + version "0.8.1" + resolved "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.8.1.tgz#47309f3e623d358652878935ac90c880b97ef118" + integrity sha512-L86TReBnPiiJOWd7k9iaiE9f7rHtMpjAoYN0fH2ey2ZRzsOChHV0s5sYf1+IIUYzplzsE46pjlmAUNkRRKwHSQ== + dependencies: + "@firebase/component" "0.7.1" + "@firebase/installations" "0.6.20" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.14.0" tslib "^2.1.0" -"@firebase/storage-compat@0.3.5": - version "0.3.5" - resolved "https://registry.yarnpkg.com/@firebase/storage-compat/-/storage-compat-0.3.5.tgz#4c55531dc5aa7d8b5f6c1ed4b5eeee09190072f1" - integrity sha512-5dJXfY5NxCF5NAk4dLvJqC+m6cgcf0Fr29nrMHwhwI34pBheQq2PdRZqALsqZCES9dnHTuFNlqGQDpLr+Ph4rw== +"@firebase/storage-compat@0.4.1": + version "0.4.1" + resolved "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.4.1.tgz#94c105a416f949fd1552ced075d2df613e761faa" + integrity sha512-bgl3FHHfXAmBgzIK/Fps6Xyv2HiAQlSTov07CBL+RGGhrC5YIk4lruS8JVIC+UkujRdYvnf8cpQFGn2RCilJ/A== dependencies: - "@firebase/component" "0.6.5" - "@firebase/storage" "0.12.2" - "@firebase/storage-types" "0.8.0" - "@firebase/util" "1.9.4" + "@firebase/component" "0.7.1" + "@firebase/storage" "0.14.1" + "@firebase/storage-types" "0.8.3" + "@firebase/util" "1.14.0" tslib "^2.1.0" -"@firebase/storage-types@0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@firebase/storage-types/-/storage-types-0.8.0.tgz#f1e40a5361d59240b6e84fac7fbbbb622bfaf707" - integrity sha512-isRHcGrTs9kITJC0AVehHfpraWFui39MPaU7Eo8QfWlqW7YPymBmRgjDrlOgFdURh6Cdeg07zmkLP5tzTKRSpg== +"@firebase/storage-types@0.8.3": + version "0.8.3" + resolved "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.3.tgz#2531ef593a3452fc12c59117195d6485c6632d3d" + integrity sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg== -"@firebase/storage@0.12.2": - version "0.12.2" - resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.12.2.tgz#73b1679fca74ec21a0f183beaa1b0b1a50f7e68b" - integrity sha512-MzanOBcxDx9oOwDaDPMuiYxd6CxcN1xZm+os5uNE3C1itbRKLhM9rzpODDKWzcbnHHFtXk3Q3lsK/d3Xa1WYYw== +"@firebase/storage@0.14.1": + version "0.14.1" + resolved "https://registry.npmjs.org/@firebase/storage/-/storage-0.14.1.tgz#2cdc6523bac9fd85bdd369c77e02a785866d4c02" + integrity sha512-uIpYgBBsv1vIET+5xV20XT7wwqV+H4GFp6PBzfmLUcEgguS4SWNFof56Z3uOC2lNDh0KDda1UflYq2VwD9Nefw== dependencies: - "@firebase/component" "0.6.5" - "@firebase/util" "1.9.4" + "@firebase/component" "0.7.1" + "@firebase/util" "1.14.0" tslib "^2.1.0" - undici "5.28.3" -"@firebase/util@1.9.4": - version "1.9.4" - resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.9.4.tgz#68eee380ab7e7828ec0d8684c46a1abed2d7e334" - integrity sha512-WLonYmS1FGHT97TsUmRN3qnTh5TeeoJp1Gg5fithzuAgdZOUtsYECfy7/noQ3llaguios8r5BuXSEiK82+UrxQ== +"@firebase/util@1.14.0": + version "1.14.0" + resolved "https://registry.npmjs.org/@firebase/util/-/util-1.14.0.tgz#e0a5998fc30a065fe5cba8bd7546ae8f095f3d3e" + integrity sha512-/gnejm7MKkVIXnSJGpc9L2CvvvzJvtDPeAEq5jAwgVlf/PeNxot+THx/bpD20wQ8uL5sz0xqgXy1nisOYMU+mw== dependencies: tslib "^2.1.0" -"@firebase/webchannel-wrapper@0.10.5": - version "0.10.5" - resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.10.5.tgz#cd9897680d0a2f1bce8d8c23a590e5874f4617c5" - integrity sha512-eSkJsnhBWv5kCTSU1tSUVl9mpFu+5NXXunZc83le8GMjMlsWwQArSc7cJJ4yl+aDFY0NGLi0AjZWMn1axOrkRg== +"@firebase/webchannel-wrapper@1.0.5": + version "1.0.5" + resolved "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.5.tgz#39cf5a600450cb42f1f0b507cc385459bf103b27" + integrity sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw== "@grpc/grpc-js@~1.9.0": version "1.9.15" @@ -662,37 +675,39 @@ faye-websocket@0.11.4: dependencies: websocket-driver ">=0.5.1" -firebase@10: - version "10.9.0" - resolved "https://registry.yarnpkg.com/firebase/-/firebase-10.9.0.tgz#748899beb0ed8e381864566c223c4208d2306091" - integrity sha512-R8rDU3mg2dq0uPOoZ5Nc3BeZTbXxBPJS8HcZLtnV0f5/YrmpNsHngzmMHRVB+91T+ViJGVL/42dV23gS9w9ccw== - dependencies: - "@firebase/analytics" "0.10.1" - "@firebase/analytics-compat" "0.2.7" - "@firebase/app" "0.9.29" - "@firebase/app-check" "0.8.2" - "@firebase/app-check-compat" "0.3.9" - "@firebase/app-compat" "0.2.29" - "@firebase/app-types" "0.9.0" - "@firebase/auth" "1.6.2" - "@firebase/auth-compat" "0.5.4" - "@firebase/database" "1.0.3" - "@firebase/database-compat" "1.0.3" - "@firebase/firestore" "4.5.0" - "@firebase/firestore-compat" "0.3.27" - "@firebase/functions" "0.11.2" - "@firebase/functions-compat" "0.3.8" - "@firebase/installations" "0.6.5" - "@firebase/installations-compat" "0.2.5" - "@firebase/messaging" "0.12.6" - "@firebase/messaging-compat" "0.2.6" - "@firebase/performance" "0.6.5" - "@firebase/performance-compat" "0.2.5" - "@firebase/remote-config" "0.4.5" - "@firebase/remote-config-compat" "0.2.5" - "@firebase/storage" "0.12.2" - "@firebase/storage-compat" "0.3.5" - "@firebase/util" "1.9.4" +firebase@12: + version "12.10.0" + resolved "https://registry.npmjs.org/firebase/-/firebase-12.10.0.tgz#2c000e889e8b423ce37399b6a0497cadfba890fe" + integrity sha512-tAjHnEirksqWpa+NKDUSUMjulOnsTcsPC1X1rQ+gwPtjlhJS572na91CwaBXQJHXharIrfj7sw/okDkXOsphjA== + dependencies: + "@firebase/ai" "2.9.0" + "@firebase/analytics" "0.10.20" + "@firebase/analytics-compat" "0.2.26" + "@firebase/app" "0.14.9" + "@firebase/app-check" "0.11.1" + "@firebase/app-check-compat" "0.4.1" + "@firebase/app-compat" "0.5.9" + "@firebase/app-types" "0.9.3" + "@firebase/auth" "1.12.1" + "@firebase/auth-compat" "0.6.3" + "@firebase/data-connect" "0.4.0" + "@firebase/database" "1.1.1" + "@firebase/database-compat" "2.1.1" + "@firebase/firestore" "4.12.0" + "@firebase/firestore-compat" "0.4.6" + "@firebase/functions" "0.13.2" + "@firebase/functions-compat" "0.4.2" + "@firebase/installations" "0.6.20" + "@firebase/installations-compat" "0.2.20" + "@firebase/messaging" "0.12.24" + "@firebase/messaging-compat" "0.2.24" + "@firebase/performance" "0.7.10" + "@firebase/performance-compat" "0.2.23" + "@firebase/remote-config" "0.8.1" + "@firebase/remote-config-compat" "0.2.22" + "@firebase/storage" "0.14.1" + "@firebase/storage-compat" "0.4.1" + "@firebase/util" "1.14.0" get-caller-file@^2.0.5: version "2.0.5" @@ -773,12 +788,10 @@ tslib@^2.1.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== -undici@5.28.3: - version "5.28.3" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.3.tgz#a731e0eff2c3fcfd41c1169a869062be222d1e5b" - integrity sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA== - dependencies: - "@fastify/busboy" "^2.0.0" +web-vitals@^4.2.4: + version "4.2.4" + resolved "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz#1d20bc8590a37769bd0902b289550936069184b7" + integrity sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw== websocket-driver@>=0.5.1: version "0.7.4" diff --git a/packages/firebase_messaging/firebase_messaging/example/web/firebase-messaging-sw.js b/packages/firebase_messaging/firebase_messaging/example/web/firebase-messaging-sw.js index 63cac871b4d9..9092bc9b88a7 100644 --- a/packages/firebase_messaging/firebase_messaging/example/web/firebase-messaging-sw.js +++ b/packages/firebase_messaging/firebase_messaging/example/web/firebase-messaging-sw.js @@ -1,3 +1,15 @@ +// ⚠️ WARNING: This file uses the legacy Firebase compat SDK loaded via importScripts. +// This approach is deprecated and not recommended for production use. +// +// Instead, use the bundled service worker with the modular Firebase JS SDK: +// See: ../bundled-service-worker/ +// +// To build: +// cd bundled-service-worker +// yarn install && yarn build +// +// This outputs a bundled firebase-messaging-sw.js into this directory. + importScripts("https://www.gstatic.com/firebasejs/9.10.0/firebase-app-compat.js"); importScripts("https://www.gstatic.com/firebasejs/9.10.0/firebase-messaging-compat.js"); From adef1872b523b77e2309f3d7400e5a5fdd95738c Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Thu, 5 Mar 2026 11:08:18 +0100 Subject: [PATCH 62/72] fix(firestore,windows): fix a crash happening when terminating the firestore instance (#18069) * fix(firestore,windows): fix a crash happening when terminating the firestore instance * fix test * fix cached instance in Dart * fix --- .../integration_test/instance_e2e.dart | 38 ++++++++++++++++++- .../windows/cloud_firestore_plugin.cpp | 4 +- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/example/integration_test/instance_e2e.dart b/packages/cloud_firestore/cloud_firestore/example/integration_test/instance_e2e.dart index fdde6a261eff..108cd2565cfe 100644 --- a/packages/cloud_firestore/cloud_firestore/example/integration_test/instance_e2e.dart +++ b/packages/cloud_firestore/cloud_firestore/example/integration_test/instance_e2e.dart @@ -148,7 +148,43 @@ void runInstanceTests() { await firestore.terminate(); await firestore.clearPersistence(); }, - skip: kIsWeb || defaultTargetPlatform == TargetPlatform.windows, + skip: kIsWeb, + ); + + test( + 'terminate() then use Firestore again', + () async { + // Regression test for https://github.com/firebase/flutterfire/issues/17781 + // On Windows, terminate() did not remove the instance from the native + // cache, so subsequent usage would crash with "The client has already + // been terminated". + final instance = FirebaseFirestore.instanceFor( + app: Firebase.app(), + databaseId: 'flutterfire-2', + ); + + instance.useFirestoreEmulator('localhost', 8080); + + // Use Firestore so it is fully initialized + await instance.collection('flutterfire-2').doc('terminate-test').set( + {'foo': 'bar'}, + ); + + await instance.terminate(); + await instance.clearPersistence(); + + // After terminate + clearPersistence, we should be able to use + // Firestore again without crashing. + await instance + .collection('flutterfire-2') + .doc('terminate-test') + .get(); + + // Clean up: terminate so the native instance cache is cleared + // for subsequent tests that may use the same databaseId. + await instance.terminate(); + }, + skip: kIsWeb, ); test( diff --git a/packages/cloud_firestore/cloud_firestore/windows/cloud_firestore_plugin.cpp b/packages/cloud_firestore/cloud_firestore/windows/cloud_firestore_plugin.cpp index 2e08cb71ef3e..342103df811a 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/cloud_firestore_plugin.cpp +++ b/packages/cloud_firestore/cloud_firestore/windows/cloud_firestore_plugin.cpp @@ -706,10 +706,12 @@ void CloudFirestorePlugin::EnableNetwork( void CloudFirestorePlugin::Terminate( const FirestorePigeonFirebaseApp& app, std::function reply)> result) { + std::string cacheKey = app.app_name() + "-" + app.database_u_r_l(); Firestore* firestore = GetFirestoreFromPigeon(app); firestore->Terminate().OnCompletion( - [result](const Future& completed_future) { + [result, cacheKey](const Future& completed_future) { if (completed_future.error() == firebase::firestore::kErrorOk) { + CloudFirestorePlugin::firestoreInstances_.erase(cacheKey); result(std::nullopt); } else { result(CloudFirestorePlugin::ParseError(completed_future)); From 26a92945572600be0ec212c49530489164a0c6d9 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Thu, 5 Mar 2026 15:36:10 +0000 Subject: [PATCH 63/72] chore: clean up and refactor --- .../utils/PipelineStageHandlers.java | 63 ++- .../cloud_firestore/FLTPipelineParser.m | 54 ++- .../cloud_firestore/lib/src/pipeline.dart | 4 +- .../lib/src/pipeline_aggregate.dart | 4 +- .../lib/src/pipeline_stage.dart | 4 +- .../lib/src/interop/firestore_interop.dart | 11 + .../lib/src/pipeline_builder_web.dart | 5 +- .../pipeline_expression_converter_web.dart | 449 ++++++++---------- 8 files changed, 298 insertions(+), 296 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java index 78ef835e3006..a1e5525813cb 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java @@ -59,6 +59,8 @@ Pipeline applyStage( return handleDistinct(pipeline, args); case "aggregate": return handleAggregate(pipeline, args); + case "aggregate_with_options": + return handleAggregateWithOptions(pipeline, args); case "unnest": return handleUnnest(pipeline, args); case "replace_with": @@ -218,45 +220,40 @@ private Pipeline handleDistinct(@NonNull Pipeline pipeline, @Nullable Map args) { - // Check if this is using aggregate_stage (new API) or aggregate_functions (legacy API) - if (args.containsKey("aggregate_stage")) { - // New API: aggregateStage with optional options - Map aggregateStageMap = (Map) args.get("aggregate_stage"); - AggregateStage aggregateStage = parsers.parseAggregateStage(aggregateStageMap); - - // Parse optional options - Map optionsMap = (Map) args.get("options"); - if (optionsMap != null && !optionsMap.isEmpty()) { - AggregateOptions options = parsers.parseAggregateOptions(optionsMap); - return pipeline.aggregate(aggregateStage, options); - } else { - return pipeline.aggregate(aggregateStage); - } - } else { - // Legacy API: aggregate_functions (varargs) - List> aggregateMaps = - (List>) args.get("aggregate_functions"); + List> aggregateMaps = + (List>) args.get("aggregate_functions"); + + if (aggregateMaps == null || aggregateMaps.isEmpty()) { + throw new IllegalArgumentException("'aggregate' requires at least one aggregate function"); + } + + AliasedAggregate firstAccumulator = parsers.parseAliasedAggregate(aggregateMaps.get(0)); + + if (aggregateMaps.size() == 1) { + return pipeline.aggregate(firstAccumulator); + } - if (aggregateMaps == null || aggregateMaps.isEmpty()) { - throw new IllegalArgumentException( - "'aggregate' requires at least one aggregate function or an aggregate_stage"); - } + AliasedAggregate[] additionalAccumulators = new AliasedAggregate[aggregateMaps.size() - 1]; + for (int i = 1; i < aggregateMaps.size(); i++) { + additionalAccumulators[i - 1] = parsers.parseAliasedAggregate(aggregateMaps.get(i)); + } - // Parse first aggregate function as AliasedAggregate - AliasedAggregate firstAccumulator = parsers.parseAliasedAggregate(aggregateMaps.get(0)); + return pipeline.aggregate(firstAccumulator, additionalAccumulators); + } - // Parse remaining aggregate functions as AliasedAggregate varargs - if (aggregateMaps.size() == 1) { - return pipeline.aggregate(firstAccumulator); - } + @SuppressWarnings("unchecked") + private Pipeline handleAggregateWithOptions( + @NonNull Pipeline pipeline, @Nullable Map args) { + Map aggregateStageMap = (Map) args.get("aggregate_stage"); - AliasedAggregate[] additionalAccumulators = new AliasedAggregate[aggregateMaps.size() - 1]; - for (int i = 1; i < aggregateMaps.size(); i++) { - additionalAccumulators[i - 1] = parsers.parseAliasedAggregate(aggregateMaps.get(i)); - } + AggregateStage aggregateStage = parsers.parseAggregateStage(aggregateStageMap); - return pipeline.aggregate(firstAccumulator, additionalAccumulators); + Map optionsMap = (Map) args.get("options"); + if (optionsMap != null && !optionsMap.isEmpty()) { + AggregateOptions options = parsers.parseAggregateOptions(optionsMap); + return pipeline.aggregate(aggregateStage, options); } + return pipeline.aggregate(aggregateStage); } private Pipeline handleUnnest(@NonNull Pipeline pipeline, @Nullable Map args) { diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m index 3018ce5820fa..bcee8288a06a 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m @@ -647,6 +647,8 @@ + (NSString *)keyForExpressionMap:(NSDictionary *)em error:(NSError **)error { } } else if ([stageName isEqualToString:@"aggregate"]) { stage = [self parseAggregateStageWithArgs:args exprParser:exprParser error:error]; + } else if ([stageName isEqualToString:@"aggregate_with_options"]) { + stage = [self parseAggregateStageWithOptionsArgs:args exprParser:exprParser error:error]; } else if ([stageName isEqualToString:@"unnest"]) { id exprMap = args[@"expression"]; if (![exprMap isKindOfClass:[NSDictionary class]]) { @@ -739,30 +741,48 @@ + (FIRAggregateFunctionBridge *)aggregateFunctionFromMap:(NSDictionary *)funcMap return [[FIRAggregateFunctionBridge alloc] initWithName:iosName Args:argsArray]; } -+ (FIRStageBridge *)parseAggregateStageWithArgs:(NSDictionary *)args - exprParser:(FLTPipelineExpressionParser *)exprParser - error:(NSError **)error { - NSError *parseErr = nil; - NSArray *accumulatorMaps = nil; - NSArray *groupMaps = nil; ++ (FIRStageBridge *)parseAggregateStageLegacyArgs:(NSDictionary *)args + exprParser:(FLTPipelineExpressionParser *)exprParser + error:(NSError **)error { + NSArray *accumulatorMaps = args[@"aggregate_functions"]; + if (![accumulatorMaps isKindOfClass:[NSArray class]] || accumulatorMaps.count == 0) { + if (error) *error = parseError(@"aggregate requires aggregate_functions"); + return nil; + } + return [self parseAggregateStageWithAccumulatorMaps:accumulatorMaps + groupMaps:nil + exprParser:exprParser + error:error]; +} - if (args[@"aggregate_stage"]) { - NSDictionary *stageMap = args[@"aggregate_stage"]; - if (![stageMap isKindOfClass:[NSDictionary class]]) { - if (error) *error = parseError(@"aggregate_stage must be a map"); - return nil; - } - accumulatorMaps = stageMap[@"accumulators"]; - groupMaps = stageMap[@"groups"]; ++ (FIRStageBridge *)parseAggregateStageWithOptionsArgs:(NSDictionary *)args + exprParser:(FLTPipelineExpressionParser *)exprParser + error:(NSError **)error { + NSDictionary *stageMap = args[@"aggregate_stage"]; + if (![stageMap isKindOfClass:[NSDictionary class]]) { + if (error) *error = parseError(@"aggregate_with_options requires aggregate_stage"); + return nil; } - if (!accumulatorMaps || ![accumulatorMaps isKindOfClass:[NSArray class]]) { - accumulatorMaps = args[@"aggregate_functions"]; + NSArray *accumulatorMaps = stageMap[@"accumulators"]; + if (![accumulatorMaps isKindOfClass:[NSArray class]] || accumulatorMaps.count == 0) { + accumulatorMaps = stageMap[@"aggregate_functions"]; } if (![accumulatorMaps isKindOfClass:[NSArray class]] || accumulatorMaps.count == 0) { - if (error) *error = parseError(@"aggregate requires accumulators or aggregate_functions"); + if (error) *error = parseError(@"aggregate_stage requires accumulators or aggregate_functions"); return nil; } + NSArray *groupMaps = stageMap[@"groups"]; + return [self parseAggregateStageWithAccumulatorMaps:accumulatorMaps + groupMaps:groupMaps + exprParser:exprParser + error:error]; +} ++ (FIRStageBridge *)parseAggregateStageWithAccumulatorMaps:(NSArray *)accumulatorMaps + groupMaps:(nullable NSArray *)groupMaps + exprParser:(FLTPipelineExpressionParser *)exprParser + error:(NSError **)error { + NSError *parseErr = nil; NSMutableDictionary *accumulators = [NSMutableDictionary dictionary]; for (id accMap in accumulatorMaps) { diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart index bbf7bbfe0fc9..dd8ee5148ea4 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart @@ -223,8 +223,8 @@ class Pipeline { /// options: AggregateOptions(), /// ); /// ``` - Pipeline aggregateStage( - AggregateStage aggregateStage, { + Pipeline aggregateWithOptions( + AggregateStageOptions aggregateStage, { AggregateOptions? options, }) { final stage = _AggregateStageWithOptions( diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart index c3ab5a96de41..175195dc7f08 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart @@ -171,11 +171,11 @@ class Maximum extends PipelineAggregateFunction { } /// Represents an aggregate stage with functions and optional grouping -class AggregateStage implements PipelineSerializable { +class AggregateStageOptions implements PipelineSerializable { final List accumulators; final List? groups; - AggregateStage({ + AggregateStageOptions({ required this.accumulators, this.groups, }); diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart index ab06a32dc51e..07060acdefca 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart @@ -129,13 +129,13 @@ final class _AggregateStage extends PipelineStage { /// Stage for aggregating data with options and grouping final class _AggregateStageWithOptions extends PipelineStage { - final AggregateStage aggregateStage; + final AggregateStageOptions aggregateStage; final AggregateOptions? options; _AggregateStageWithOptions(this.aggregateStage, this.options); @override - String get name => 'aggregate'; + String get name => 'aggregate_with_options'; @override Map toMap() { 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 ef196733057e..fdfd7d7ac648 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 @@ -407,6 +407,7 @@ extension type ExpressionJsImpl._(JSObject _) implements JSObject { external ExpressionJsImpl subtract(JSAny right); external ExpressionJsImpl multiply(JSAny right); external ExpressionJsImpl divide(JSAny right); + @JS('mod') external ExpressionJsImpl modulo(JSAny right); external ExpressionJsImpl length(); external ExpressionJsImpl concat(JSAny right); @@ -437,13 +438,23 @@ abstract class AliasedAggregateJsImpl { extension type AggregateStageOptionsJsImpl._(JSObject _) implements JSObject { AggregateStageOptionsJsImpl() : this._(JSObject.new()); + // ignore: avoid_setters_without_getters external set accumulators(JSAny value); + // ignore: avoid_setters_without_getters external set groups(JSAny value); } +extension type SelectStageOptionsJsImpl._(JSObject _) implements JSObject { + SelectStageOptionsJsImpl() : this._(JSObject.new()); + + // ignore: avoid_setters_without_getters + external set selections(JSArray value); +} + extension type AddFieldsOptionsJsImpl._(JSObject _) implements JSObject { AddFieldsOptionsJsImpl() : this._(JSObject.new()); + // ignore: avoid_setters_without_getters external set fields(JSAny value); } diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_builder_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_builder_web.dart index 7c98351b28fb..fdaf1cb7b7fc 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_builder_web.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_builder_web.dart @@ -119,7 +119,10 @@ interop.PipelineJsImpl _applyStage( if (expressions == null || expressions.isEmpty) return pipeline; return pipeline.distinct(converter.toDistinctOptions(expressions)); case 'aggregate': - return pipeline.aggregate(converter.toAggregateOptions(map)); + return pipeline.aggregate(converter.toAggregateOptionsFromFunctions(map)); + case 'aggregate_with_options': + return pipeline + .aggregate(converter.toAggregateOptionsFromStageAndOptions(map)); case 'sample': return pipeline.sample(converter.toSampleOptions(args)); case 'unnest': diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_converter_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_converter_web.dart index cfa3c8ce0ee7..81b50707bde0 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_converter_web.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_converter_web.dart @@ -18,125 +18,103 @@ class PipelineExpressionConverterWeb { final interop.PipelinesJsImpl _pipelines; - /// Converts a serialized value expression to JS Expression (field, constant only). - /// For boolean expressions (equal, not_equal, and, or, etc.) use [toBooleanExpression]. + static const _kName = 'name'; + static const _kArgs = 'args'; + static const _kLeft = 'left'; + static const _kRight = 'right'; + static const _kExpression = 'expression'; + static const _kField = 'field'; + static const _kAlias = 'alias'; + static const _kValue = 'value'; + + // ── Value expressions ───────────────────────────────────────────────────── + + /// Converts a serialized value expression to a JS Expression. interop.ExpressionJsImpl toExpression(Map map) { - final name = map['name'] as String?; - final args = map['args']; - final argsMap = args is Map ? args : {}; - + final name = map[_kName] as String?; + final argsMap = _argsOf(map); switch (name) { case 'field': - final path = (argsMap['field'] as String?) ?? ''; - return _pipelines.field(path.toJS); + return _pipelines.field(((argsMap[_kField] as String?) ?? '').toJS); case 'add': - final leftJsExpr = - toExpression(argsMap['left'] as Map); - final rightJsExpr = - toExpression(argsMap['right'] as Map); - return leftJsExpr.add(rightJsExpr); + return _binaryArithmetic(argsMap, (l, r) => l.add(r)); + case 'subtract': + return _binaryArithmetic(argsMap, (l, r) => l.subtract(r)); + case 'multiply': + return _binaryArithmetic(argsMap, (l, r) => l.multiply(r)); + case 'divide': + return _binaryArithmetic(argsMap, (l, r) => l.divide(r)); + case 'modulo': + return _binaryArithmetic(argsMap, (l, r) => l.modulo(r)); case 'constant': case 'null': - final value = argsMap['value']; - if (value == null) { - throw UnsupportedError( - 'constant(null) is not supported on web; use a non-null value.', - ); - } - return _pipelines.constant(jsify(value)!); - // return _pipelines.constant(value.toJS); + return _pipelines.constant(jsify(argsMap[_kValue])!); default: - return throw UnsupportedError( - 'Unsupported expression: $name', - ); + throw UnsupportedError('Unsupported expression: $name'); } } - /// Converts a serialized boolean expression to JS BooleanExpression for where(). - /// Handles equal, not_equal, comparisons, and, or, not, exists, is_absent, is_error, array_contains. - /// Sub-expressions (e.g. left/right of equal) can be value expressions or nested boolean expressions. - /// Returns null if [map] is not a recognized boolean or value expression. - JSAny? toBooleanExpression(Map map) { - final name = map['name'] as String?; - final args = map['args']; - final argsMap = args is Map ? args : {}; - - JSAny leftJs(Map left) => toExpression(left); - JSAny rightJs(Map right) => toExpression(right); + // ── Boolean expressions ─────────────────────────────────────────────────── + /// Converts a serialized boolean expression to a JS BooleanExpression. + /// + /// Returns null if [map] is not a recognized boolean expression. + JSAny? toBooleanExpression(Map map) { + final name = map[_kName] as String?; + final argsMap = _argsOf(map); switch (name) { case 'equal': - return _pipelines.equal(leftJs(argsMap['left'] as Map), - rightJs(argsMap['right'] as Map)); + return _pipelines.equal( + _expr(argsMap, _kLeft), _expr(argsMap, _kRight)); case 'not_equal': return _pipelines.notEqual( - leftJs(argsMap['left'] as Map), - rightJs(argsMap['right'] as Map)); + _expr(argsMap, _kLeft), _expr(argsMap, _kRight)); case 'greater_than': return _pipelines.greaterThan( - leftJs(argsMap['left'] as Map), - rightJs(argsMap['right'] as Map)); + _expr(argsMap, _kLeft), _expr(argsMap, _kRight)); case 'greater_than_or_equal': return _pipelines.greaterThanOrEqual( - leftJs(argsMap['left'] as Map), - rightJs(argsMap['right'] as Map)); + _expr(argsMap, _kLeft), _expr(argsMap, _kRight)); case 'less_than': return _pipelines.lessThan( - leftJs(argsMap['left'] as Map), - rightJs(argsMap['right'] as Map)); + _expr(argsMap, _kLeft), _expr(argsMap, _kRight)); case 'less_than_or_equal': return _pipelines.lessThanOrEqual( - leftJs(argsMap['left'] as Map), - rightJs(argsMap['right'] as Map)); - case 'filter': - final operator = argsMap['operator'] as String?; - final expressions = argsMap['expressions'] as List?; - if (expressions == null || expressions.isEmpty) return null; - final jsList = expressions - .map((e) => toExpression(e as Map)) - .toList(); - if (jsList.length == 1) return jsList.single; - JSAny result = jsList[0]; - for (var i = 1; i < jsList.length; i++) { - result = operator == 'and' - ? _pipelines.and(result, jsList[i]) - : _pipelines.or(result, jsList[i]); - } - return result; + _expr(argsMap, _kLeft), _expr(argsMap, _kRight)); case 'not': - return _pipelines - .not(leftJs(argsMap['expression'] as Map)); + return _pipelines.not(_expr(argsMap, _kExpression)); case 'exists': - return _pipelines - .exists(leftJs(argsMap['expression'] as Map)); + return _pipelines.exists(_expr(argsMap, _kExpression)); case 'is_absent': - return _pipelines - .isAbsent(leftJs(argsMap['expression'] as Map)); + return _pipelines.isAbsent(_expr(argsMap, _kExpression)); case 'is_error': - return _pipelines - .isError(leftJs(argsMap['expression'] as Map)); + return _pipelines.isError(_expr(argsMap, _kExpression)); case 'array_contains': return _pipelines.arrayContains( - leftJs(argsMap['array'] as Map), - rightJs(argsMap['element'] as Map)); + _expr(argsMap, 'array'), _expr(argsMap, 'element')); + case 'filter': + return _buildFilterExpression(argsMap); default: return null; } } - /// Converts orderings list to JS SortStageOptions. Each item: { expression, order_direction }. + // ── Stage options ───────────────────────────────────────────────────────── + + /// Converts orderings list to JS SortStageOptions. + /// + /// Each item shape: `{ expression: Map, order_direction: 'asc' | 'desc' }`. JSAny toSortOptions(List orderings) { final list = []; for (final o in orderings) { final m = o is Map ? o : {}; - final expr = m['expression']; - final dir = m['order_direction'] as String?; + final expr = m[_kExpression]; if (expr == null) continue; final exprJs = toExpression(expr as Map); - final ordering = (dir == 'desc') + final dir = m['order_direction'] as String?; + list.add(dir == 'desc' ? _pipelines.descending(exprJs) - : _pipelines.ascending(exprJs); - list.add(ordering); + : _pipelines.ascending(exprJs)); } if (list.isEmpty) { throw UnsupportedError( @@ -149,67 +127,36 @@ class PipelineExpressionConverterWeb { return obj as JSAny; } - /// Converts add_fields expressions (Selectable[]) to JS AddFieldsStageOptions. - JSAny toAddFieldsOptions(List expressions) { - final list = []; - for (final e in expressions) { - print('e: $e'); - final sel = - toSelectable(e is Map ? e : {}); - if (sel != null) list.add(sel); - } - - return interop.AddFieldsOptionsJsImpl()..fields = list.toJS; - } - /// Converts a single expression map to a JS Selectable (field or aliased). JSAny? toSelectable(Map map) { - final name = map['name'] as String?; - final args = map['args']; - final argsMap = args is Map ? args : {}; + final name = map[_kName] as String?; + final argsMap = _argsOf(map); if (name == 'field') { - final path = (argsMap['field'] as String?) ?? ''; - return _pipelines.field(path.toJS); + return _pipelines.field(((argsMap[_kField] as String?) ?? '').toJS); } - if (name == 'alias') { - final alias = argsMap['alias'] as String?; - final expression = argsMap['expression']; + if (name == _kAlias) { + final alias = argsMap[_kAlias] as String?; + final expression = argsMap[_kExpression]; if (alias == null || expression == null) return null; - final exprJs = toExpression(expression as Map); - if (exprJs == null) return null; - // return _pipelines.aliased(exprJs, alias.toJS); - return exprJs.asAlias(alias.toJS); + return toExpression(expression as Map) + .asAlias(alias.toJS); } - final exprJs = toExpression(map); - return exprJs; + return toExpression(map); } + /// Converts add_fields expressions to JS AddFieldsStageOptions. + JSAny toAddFieldsOptions(List expressions) => + interop.AddFieldsOptionsJsImpl() + ..fields = _toSelectableList(expressions).toJS; + /// Converts select stage expressions to JS SelectStageOptions. - JSAny toSelectOptions(List expressions) { - final list = []; - for (final e in expressions) { - final sel = - toSelectable(e is Map ? e : {}); - if (sel != null) list.add(sel); - } - if (list.isEmpty) { - throw UnsupportedError( - 'Pipeline select() on web requires the Firebase JS pipeline expression API.', - ); - } - final obj = JSObject.new; - setProperty(obj, 'selections', list.toJS); - return obj as JSAny; - } + JSAny toSelectOptions(List expressions) => + interop.SelectStageOptionsJsImpl() + ..selections = _toSelectableList(expressions).toJS; /// Converts distinct stage groups to JS DistinctStageOptions. JSAny toDistinctOptions(List expressions) { - final list = []; - for (final e in expressions) { - final sel = - toSelectable(e is Map ? e : {}); - if (sel != null) list.add(sel); - } + final list = _toSelectableList(expressions); if (list.isEmpty) { throw UnsupportedError( 'Pipeline distinct() on web requires the Firebase JS pipeline expression API.', @@ -220,112 +167,39 @@ class PipelineExpressionConverterWeb { return obj as JSAny; } - /// Converts aggregate stage args to JS AggregateStageOptions. - /// Input shape: { aggregate_functions: [...] } or { aggregate_stage: { aggregate_functions: [...] } } or { accumulators: [...] }. - /// Each list item is an aliased aggregate: { name: 'alias', args: { alias: String, aggregate_function: { name, args?: { expression } } } }. - interop.AggregateStageOptionsJsImpl toAggregateOptions( - Map map) { - final aggregateFunctions = _getAggregateFunctionsList(map); - if (aggregateFunctions.isEmpty) { - throw UnsupportedError( - 'Pipeline aggregate() on web requires aggregate_functions.', - ); - } + // ── Aggregate ───────────────────────────────────────────────────────────── - final accumulators = []; - for (final item in aggregateFunctions) { - final aliased = _asStringKeyMap(item); - if (aliased == null) continue; - - final args = _asStringKeyMap(aliased['args']) ?? {}; - final alias = args['alias'] ?? aliased['alias']; - if (alias is! String) continue; - - final aggregateFn = _asStringKeyMap(args['aggregate_function']) ?? - _asStringKeyMap(args['aggregate']) ?? - _asStringKeyMap(aliased['aggregate_function']); - if (aggregateFn == null) continue; - - final name = aggregateFn['name'] as String?; - if (name == null) continue; - - final fnArgs = _asStringKeyMap(aggregateFn['args']) ?? {}; - final expressionMap = _asStringKeyMap(fnArgs['expression']) ?? - _asStringKeyMap(aggregateFn['expression']); - - JSAny? exprJs; - if (name != 'count_all') { - if (expressionMap == null) continue; - exprJs = toExpression(expressionMap); - if (exprJs == null) continue; - } - - final fn = _buildAggregateFunction(name, exprJs); - if (fn == null) continue; - - accumulators.add(fn.asAlias(alias.toJS)); - } - - if (accumulators.isEmpty) { - throw UnsupportedError( - 'Pipeline aggregate() on web requires the Firebase JS pipeline expression API ' - '(sum, average, count, etc.).', - ); - } - - final options = interop.AggregateStageOptionsJsImpl(); - options.accumulators = accumulators.toJS; - return options; + /// Converts args for the 'aggregate' stage to JS AggregateStageOptions. + /// + /// Expects [map] to contain an [aggregate_functions] list. + interop.AggregateStageOptionsJsImpl toAggregateOptionsFromFunctions( + Map map) { + final list = map['aggregate_functions'] as List; + return _buildAccumulators(list); } - /// Returns the aggregate_functions list from [map] (top-level, under aggregate_stage, or as accumulators). - static List _getAggregateFunctionsList(Map map) { - final list = map['aggregate_functions']; - if (list is List && list.isNotEmpty) return list; - - final stage = _asStringKeyMap(map['aggregate_stage']); - final fromStage = stage?['aggregate_functions']; - if (fromStage is List && fromStage.isNotEmpty) return fromStage; - - final accumulators = map['accumulators']; - if (accumulators is List && accumulators.isNotEmpty) return accumulators; - - return []; + /// Converts args for the 'aggregate_with_options' stage to JS AggregateStageOptions. + /// + /// Expects [map] to contain an [aggregate_stage] map with [accumulators] + /// and optionally [groups]. + interop.AggregateStageOptionsJsImpl toAggregateOptionsFromStageAndOptions( + Map map) { + final stage = map['aggregate_stage'] as Map; + final list = stage['accumulators'] as List; + final groups = stage['groups'] as List?; + return _buildAccumulators(list, groups: groups); } - static Map? _asStringKeyMap(dynamic value) => - value is Map ? value : null; - - /// Builds one aggregate function (sum, average, count_all, etc.) from serialized [name] and optional [exprJs]. - interop.AggregateFunctionJsImpl? _buildAggregateFunction( - String name, JSAny? exprJs) { - switch (name) { - case 'count_all': - return _pipelines.countAll(); - case 'sum': - return exprJs != null ? _pipelines.sum(exprJs) : null; - case 'average': - return exprJs != null ? _pipelines.average(exprJs) : null; - case 'count': - return exprJs != null ? _pipelines.count(exprJs) : null; - case 'count_distinct': - return exprJs != null ? _pipelines.countDistinct(exprJs) : null; - case 'minimum': - return exprJs != null ? _pipelines.minimum(exprJs) : null; - case 'maximum': - return exprJs != null ? _pipelines.maximum(exprJs) : null; - default: - return null; - } - } + // ── Other stage options ─────────────────────────────────────────────────── - /// Converts sample stage args to JS (number or SampleStageOptions). - /// Dart PipelineSample.toMap() returns { type: 'size', value: n } or { type: 'percentage', value: p }. + /// Converts sample stage args to JS (integer count or SampleStageOptions). + /// + /// Dart serializes as `{ type: 'size', value: n }` or a raw number. JSAny toSampleOptions(dynamic args) { if (args is num) return args.toInt().toJS; if (args is Map) { final type = args['type'] as String?; - final value = args['value']; + final value = args[_kValue]; if (type == 'size' && value != null) return (value as num).toInt().toJS; final n = args['documents'] as int? ?? args['count'] as int?; if (n != null) return n.toJS; @@ -335,20 +209,18 @@ class PipelineExpressionConverterWeb { } /// Converts unnest stage args to JS UnnestStageOptions. - /// Dart uses 'expression' (Selectable/Expression toMap). JSAny toUnnestOptions(Map map) { - final selectable = map['selectable'] ?? map['expression'] ?? map['field']; - final indexField = map['index_field'] as String?; + final selectable = map['selectable'] ?? map[_kExpression] ?? map[_kField]; if (selectable == null) { throw UnsupportedError( - 'Pipeline unnest() on web requires selectable or field.', - ); + 'Pipeline unnest() on web requires selectable or field.'); } + final indexField = map['index_field'] as String?; final sel = selectable is Map ? toSelectable(selectable) : toExpression({ - 'name': 'field', - 'args': {'field': selectable.toString()} + _kName: 'field', + _kArgs: {_kField: selectable.toString()} }); if (sel == null) { throw UnsupportedError( @@ -367,7 +239,7 @@ class PipelineExpressionConverterWeb { for (final e in fieldPaths) { final s = e is String ? e - : (e is Map ? e['field'] ?? e['path'] : null)?.toString(); + : (e is Map ? e[_kField] ?? e['path'] : null)?.toString(); if (s != null) paths.add(s.toJS); } if (paths.isEmpty) { @@ -382,21 +254,15 @@ class PipelineExpressionConverterWeb { /// Converts replace_with expression to JS ReplaceWithStageOptions. JSAny toReplaceWithOptions(Map expression) { - final exprJs = toExpression(expression); - if (exprJs == null) { - throw UnsupportedError( - 'Pipeline replaceWith() on web requires the Firebase JS pipeline expression API.', - ); - } final obj = JSObject.new; - setProperty(obj, 'expression', exprJs); + setProperty(obj, _kExpression, toExpression(expression)); return obj as JSAny; } /// Converts find_nearest args to JS FindNearestStageOptions. JSAny toFindNearestOptions(Map map) { final vectorField = - (map['vector_field'] as String?) ?? (map['field'] as String?); + (map['vector_field'] as String?) ?? (map[_kField] as String?); final vectorValue = map['vector_value'] as List?; final distanceMeasure = (map['distance_measure'] as String?) ?? 'cosine'; final limit = map['limit'] as int?; @@ -413,4 +279,109 @@ class PipelineExpressionConverterWeb { if (limit != null) setProperty(obj, 'limit', limit.toJS); return obj as JSAny; } + + // ── Private helpers ─────────────────────────────────────────────────────── + + /// Extracts and safe-casts the 'args' sub-map from an expression map. + static Map _argsOf(Map map) { + final a = map[_kArgs]; + return a is Map ? a : const {}; + } + + /// Resolves [key] from [argsMap] as a value expression. + JSAny _expr(Map argsMap, String key) => + toExpression(argsMap[key] as Map); + + interop.ExpressionJsImpl _binaryArithmetic( + Map argsMap, + interop.ExpressionJsImpl Function( + interop.ExpressionJsImpl left, interop.ExpressionJsImpl right) + op, + ) => + op( + toExpression(argsMap[_kLeft] as Map), + toExpression(argsMap[_kRight] as Map), + ); + + JSAny? _buildFilterExpression(Map argsMap) { + final operator = argsMap['operator'] as String?; + final expressions = argsMap['expressions'] as List?; + if (expressions == null || expressions.isEmpty) return null; + final jsList = expressions + .map((e) => toExpression(e as Map)) + .toList(); + if (jsList.length == 1) return jsList.single; + JSAny result = jsList[0]; + for (var i = 1; i < jsList.length; i++) { + result = operator == 'and' + ? _pipelines.and(result, jsList[i]) + : _pipelines.or(result, jsList[i]); + } + return result; + } + + List _toSelectableList(List expressions) => expressions + .map((e) => + toSelectable(e is Map ? e : {})) + .whereType() + .toList(); + + interop.AggregateStageOptionsJsImpl _buildAccumulators( + List items, { + List? groups, + }) { + final accumulators = items + .map((item) => _parseAccumulator(item as Map)) + .whereType() + .toList(); + + if (accumulators.isEmpty) { + throw UnsupportedError( + 'Pipeline aggregate() on web requires at least one valid accumulator.', + ); + } + + final opts = interop.AggregateStageOptionsJsImpl() + ..accumulators = accumulators.toJS; + if (groups != null && groups.isNotEmpty) { + opts.groups = _toSelectableList(groups).toJS; + } + return opts; + } + + JSAny? _parseAccumulator(Map item) { + final args = item[_kArgs] as Map; + final alias = args[_kAlias] as String?; + final aggregateFn = args['aggregate_function'] as Map?; + if (alias == null || aggregateFn == null) return null; + final fnName = aggregateFn[_kName] as String?; + if (fnName == null) return null; + final expressionMap = (aggregateFn[_kArgs] + as Map?)?[_kExpression] as Map?; + final exprJs = expressionMap != null ? toExpression(expressionMap) : null; + return _buildAggregateFunction(fnName, exprJs)?.asAlias(alias.toJS); + } + + /// Builds one JS aggregate function from a serialized [name] and optional [exprJs]. + interop.AggregateFunctionJsImpl? _buildAggregateFunction( + String name, JSAny? exprJs) { + switch (name) { + case 'count_all': + return _pipelines.countAll(); + case 'sum': + return exprJs != null ? _pipelines.sum(exprJs) : null; + case 'average': + return exprJs != null ? _pipelines.average(exprJs) : null; + case 'count': + return exprJs != null ? _pipelines.count(exprJs) : null; + case 'count_distinct': + return exprJs != null ? _pipelines.countDistinct(exprJs) : null; + case 'minimum': + return exprJs != null ? _pipelines.minimum(exprJs) : null; + case 'maximum': + return exprJs != null ? _pipelines.maximum(exprJs) : null; + default: + return null; + } + } } From b3d8e5350743073fa5008e390741a90d99336b92 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Fri, 6 Mar 2026 11:51:44 +0000 Subject: [PATCH 64/72] chore: add more interop and clean up code --- .../lib/src/interop/firestore.dart | 1 - .../lib/src/interop/firestore_interop.dart | 47 ++++++++++++ .../pipeline_expression_converter_web.dart | 72 ++++++------------- 3 files changed, 69 insertions(+), 51 deletions(-) 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 163ed535cba9..b7e448750a50 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 @@ -1140,7 +1140,6 @@ class PipelineSnapshot static PipelineSnapshot getInstance( firestore_interop.PipelineSnapshotJsImpl jsObject) { - print('PipelineSnapshot.getInstance: jsObject = $jsObject'); // Bypass Expando to test if key type causes the error: // return PipelineSnapshot._fromJsObject(jsObject); return _expando[jsObject] ??= PipelineSnapshot._fromJsObject(jsObject); 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 fdfd7d7ac648..9a4ed9de4d3f 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 @@ -1190,3 +1190,50 @@ extension type PipelineResultJsImpl._(JSObject _) implements JSObject { external JSAny? get createTime; external JSAny? get updateTime; } + +extension type SampleStageOptionsJsImpl._(JSObject _) implements JSObject { + SampleStageOptionsJsImpl() : this._(JSObject.new()); + + // ignore: avoid_setters_without_getters + external set documents(JSAny value); + // ignore: avoid_setters_without_getters + external set percentage(JSAny value); +} + +extension type SortStageOptionsJsImpl._(JSObject _) implements JSObject { + SortStageOptionsJsImpl() : this._(JSObject.new()); + + // ignore: avoid_setters_without_getters + external set orderings(JSAny value); +} + +extension type DistinctStageOptionsJsImpl._(JSObject _) implements JSObject { + DistinctStageOptionsJsImpl() : this._(JSObject.new()); + + // ignore: avoid_setters_without_getters + external set groups(JSAny value); +} + +extension type UnnestStageOptionsJsImpl._(JSObject _) implements JSObject { + UnnestStageOptionsJsImpl() : this._(JSObject.new()); + + // ignore: avoid_setters_without_getters + external set selectable(JSAny value); + // ignore: avoid_setters_without_getters + external set indexField(JSString? value); +} + +extension type RemoveFieldsStageOptionsJsImpl._(JSObject _) + implements JSObject { + RemoveFieldsStageOptionsJsImpl() : this._(JSObject.new()); + + // ignore: avoid_setters_without_getters + external set fields(JSArray value); +} + +extension type ReplaceWithStageOptionsJsImpl._(JSObject _) implements JSObject { + ReplaceWithStageOptionsJsImpl() : this._(JSObject.new()); + + // ignore: avoid_setters_without_getters + external set expression(JSAny value); +} diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_converter_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_converter_web.dart index 81b50707bde0..4459a4504bb3 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_converter_web.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_converter_web.dart @@ -122,22 +122,19 @@ class PipelineExpressionConverterWeb { '(ascending, descending). Ensure the pipelines module is loaded.', ); } - final obj = JSObject.new; - setProperty(obj, 'orderings', list.toJS); - return obj as JSAny; + return interop.SortStageOptionsJsImpl()..orderings = list.toJS; } /// Converts a single expression map to a JS Selectable (field or aliased). - JSAny? toSelectable(Map map) { + JSAny toSelectable(Map map) { final name = map[_kName] as String?; final argsMap = _argsOf(map); if (name == 'field') { return _pipelines.field(((argsMap[_kField] as String?) ?? '').toJS); } if (name == _kAlias) { - final alias = argsMap[_kAlias] as String?; + final alias = argsMap[_kAlias] as String; final expression = argsMap[_kExpression]; - if (alias == null || expression == null) return null; return toExpression(expression as Map) .asAlias(alias.toJS); } @@ -162,9 +159,7 @@ class PipelineExpressionConverterWeb { 'Pipeline distinct() on web requires the Firebase JS pipeline expression API.', ); } - final obj = JSObject.new; - setProperty(obj, 'groups', list.toJS); - return obj as JSAny; + return interop.DistinctStageOptionsJsImpl()..groups = list.toJS; } // ── Aggregate ───────────────────────────────────────────────────────────── @@ -195,42 +190,27 @@ class PipelineExpressionConverterWeb { /// Converts sample stage args to JS (integer count or SampleStageOptions). /// /// Dart serializes as `{ type: 'size', value: n }` or a raw number. - JSAny toSampleOptions(dynamic args) { - if (args is num) return args.toInt().toJS; - if (args is Map) { - final type = args['type'] as String?; - final value = args[_kValue]; - if (type == 'size' && value != null) return (value as num).toInt().toJS; - final n = args['documents'] as int? ?? args['count'] as int?; - if (n != null) return n.toJS; - return (args['documents'] as num?)?.toInt().toJS ?? 0.toJS; + JSAny toSampleOptions(Map map) { + final args = map['type'] as String; + if (args == 'size') { + final value = map['value'] as num; + return interop.SampleStageOptionsJsImpl()..documents = value.toInt().toJS; + } else { + final value = map['value'] as num; + return interop.SampleStageOptionsJsImpl() + ..percentage = value.toDouble().toJS; } - return 0.toJS; } /// Converts unnest stage args to JS UnnestStageOptions. JSAny toUnnestOptions(Map map) { - final selectable = map['selectable'] ?? map[_kExpression] ?? map[_kField]; - if (selectable == null) { - throw UnsupportedError( - 'Pipeline unnest() on web requires selectable or field.'); - } + print('toUnnestOptions: ${map.toString()}'); + final expression = map[_kExpression] as Map; final indexField = map['index_field'] as String?; - final sel = selectable is Map - ? toSelectable(selectable) - : toExpression({ - _kName: 'field', - _kArgs: {_kField: selectable.toString()} - }); - if (sel == null) { - throw UnsupportedError( - 'Pipeline unnest() on web requires the Firebase JS pipeline expression API.', - ); - } - final obj = JSObject.new; - setProperty(obj, 'selectable', sel); - if (indexField != null) setProperty(obj, 'indexField', indexField.toJS); - return obj as JSAny; + final sel = toSelectable(expression); + return interop.UnnestStageOptionsJsImpl() + ..selectable = sel + ..indexField = indexField?.toJS; } /// Converts remove_fields field paths to JS RemoveFieldsStageOptions. @@ -242,21 +222,13 @@ class PipelineExpressionConverterWeb { : (e is Map ? e[_kField] ?? e['path'] : null)?.toString(); if (s != null) paths.add(s.toJS); } - if (paths.isEmpty) { - throw UnsupportedError( - 'Pipeline removeFields() on web requires at least one field path.', - ); - } - final obj = JSObject.new; - setProperty(obj, 'fields', paths.toJS); - return obj as JSAny; + return interop.RemoveFieldsStageOptionsJsImpl()..fields = paths.toJS; } /// Converts replace_with expression to JS ReplaceWithStageOptions. JSAny toReplaceWithOptions(Map expression) { - final obj = JSObject.new; - setProperty(obj, _kExpression, toExpression(expression)); - return obj as JSAny; + return interop.ReplaceWithStageOptionsJsImpl() + ..expression = toExpression(expression); } /// Converts find_nearest args to JS FindNearestStageOptions. From 103d7ffa9343c654ec23c782a802b929dbf37d01 Mon Sep 17 00:00:00 2001 From: Jude Selase Kwashie <64037520+SelaseKay@users.noreply.github.com> Date: Fri, 6 Mar 2026 12:19:16 +0000 Subject: [PATCH 65/72] feat(analytics, iOS): add support for `logTransaction` (#17995) * feat(analytics, iOS): add support for `logTransaction` * chore: add test for `logTransaction` method * chore: update logTransaction to require iOS 15+ or macOS 12+ * chore: update logTransaction to require iOS 15+ or macOS 12+ * chore: fix formatting * chore: add availability check for fetchTransaction method on iOS 15+ and macOS 12+ * chore: enhance logTransaction method with platform-specific availability checks and improved transaction retrieval * chore: update test skip condition to include macOS platform * chore: update logTransaction method to support macOS in addition to iOS * chore: enhance logTransaction tests with error handling for invalid transactionId and missing transactions * chore: update analytics example app to include manual test for `logTransaction` * chore: implement logTransaction method with unimplemented error for Android * chore: update skip conditions for Firebase Analytics E2E tests to include web platform --- .../FlutterFirebaseAnalyticsPlugin.kt | 12 +++ .../GeneratedAndroidFirebaseAnalytics.g.kt | 20 +++++ .../firebase_analytics/example/lib/main.dart | 80 +++++++++++++++++++ .../firebase_analytics/example/pubspec.yaml | 1 + .../FirebaseAnalyticsMessages.g.swift | 22 +++++ .../FirebaseAnalyticsPlugin.swift | 76 ++++++++++++++++++ .../analytics_storekit_config.storekit | 54 +++++++++++++ .../lib/src/firebase_analytics.dart | 17 ++++ .../firebase_analytics/windows/messages.g.cpp | 39 +++++++++ .../firebase_analytics/windows/messages.g.h | 3 + .../method_channel_firebase_analytics.dart | 11 +++ .../lib/src/pigeon/messages.pigeon.dart | 26 ++++++ ...platform_interface_firebase_analytics.dart | 6 ++ .../pigeons/messages.dart | 3 + .../test/pigeon/test_api.dart | 34 ++++++++ .../firebase_analytics_e2e_test.dart | 41 ++++++++++ 16 files changed, 445 insertions(+) create mode 100644 packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/analytics_storekit_config.storekit diff --git a/packages/firebase_analytics/firebase_analytics/android/src/main/kotlin/io/flutter/plugins/firebase/analytics/FlutterFirebaseAnalyticsPlugin.kt b/packages/firebase_analytics/firebase_analytics/android/src/main/kotlin/io/flutter/plugins/firebase/analytics/FlutterFirebaseAnalyticsPlugin.kt index aca07f356d51..3c366eebf7e4 100644 --- a/packages/firebase_analytics/firebase_analytics/android/src/main/kotlin/io/flutter/plugins/firebase/analytics/FlutterFirebaseAnalyticsPlugin.kt +++ b/packages/firebase_analytics/firebase_analytics/android/src/main/kotlin/io/flutter/plugins/firebase/analytics/FlutterFirebaseAnalyticsPlugin.kt @@ -443,4 +443,16 @@ class FlutterFirebaseAnalyticsPlugin : FlutterFirebasePlugin, ) ) } + + override fun logTransaction(transactionId: String, callback: (Result) -> Unit) { + callback( + Result.failure( + FlutterError( + "unimplemented", + "logTransaction is only available on iOS.", + null + ) + ) + ) + } } diff --git a/packages/firebase_analytics/firebase_analytics/android/src/main/kotlin/io/flutter/plugins/firebase/analytics/GeneratedAndroidFirebaseAnalytics.g.kt b/packages/firebase_analytics/firebase_analytics/android/src/main/kotlin/io/flutter/plugins/firebase/analytics/GeneratedAndroidFirebaseAnalytics.g.kt index 0a8351824439..fdfc88dc8f42 100644 --- a/packages/firebase_analytics/firebase_analytics/android/src/main/kotlin/io/flutter/plugins/firebase/analytics/GeneratedAndroidFirebaseAnalytics.g.kt +++ b/packages/firebase_analytics/firebase_analytics/android/src/main/kotlin/io/flutter/plugins/firebase/analytics/GeneratedAndroidFirebaseAnalytics.g.kt @@ -147,6 +147,7 @@ interface FirebaseAnalyticsHostApi { fun getAppInstanceId(callback: (Result) -> Unit) fun getSessionId(callback: (Result) -> Unit) fun initiateOnDeviceConversionMeasurement(arguments: Map, callback: (Result) -> Unit) + fun logTransaction(transactionId: String, callback: (Result) -> Unit) companion object { /** The codec used by FirebaseAnalyticsHostApi. */ @@ -363,6 +364,25 @@ interface FirebaseAnalyticsHostApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.firebase_analytics_platform_interface.FirebaseAnalyticsHostApi.logTransaction$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val transactionIdArg = args[0] as String + api.logTransaction(transactionIdArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(GeneratedAndroidFirebaseAnalyticsPigeonUtils.wrapError(error)) + } else { + reply.reply(GeneratedAndroidFirebaseAnalyticsPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } } } } diff --git a/packages/firebase_analytics/firebase_analytics/example/lib/main.dart b/packages/firebase_analytics/firebase_analytics/example/lib/main.dart index 2b885ed94b1f..37c7dd8eec76 100755 --- a/packages/firebase_analytics/firebase_analytics/example/lib/main.dart +++ b/packages/firebase_analytics/firebase_analytics/example/lib/main.dart @@ -8,6 +8,7 @@ import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:in_app_purchase/in_app_purchase.dart'; import 'firebase_options.dart'; import 'tabs_page.dart'; @@ -62,6 +63,44 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { String _message = ''; + StreamSubscription>? _purchaseSubscription; + + static const String _testProductId = '123456'; + + @override + void initState() { + super.initState(); + _purchaseSubscription = + InAppPurchase.instance.purchaseStream.listen(_onPurchaseUpdate); + } + + @override + void dispose() { + _purchaseSubscription?.cancel(); + super.dispose(); + } + + void _onPurchaseUpdate(List purchases) { + for (final purchase in purchases) { + if (purchase.pendingCompletePurchase) { + InAppPurchase.instance.completePurchase(purchase); + } + if (purchase.status == PurchaseStatus.purchased || + purchase.status == PurchaseStatus.restored) { + final transactionId = purchase.purchaseID; + print('transactionId: $transactionId'); + if (transactionId != null) { + widget.analytics.logTransaction(transactionId).then((_) { + setMessage('logTransaction succeeded with ID: $transactionId'); + }).catchError((e) { + setMessage('logTransaction failed: $e'); + }); + } + } else if (purchase.status == PurchaseStatus.error) { + setMessage('Purchase error: ${purchase.error?.message}'); + } + } + } void setMessage(String message) { setState(() { @@ -158,6 +197,40 @@ class _MyHomePageState extends State { setMessage('initiateOnDeviceConversionMeasurement succeeded'); } + Future _testLogTransaction() async { + if (kIsWeb || + (defaultTargetPlatform != TargetPlatform.iOS && + defaultTargetPlatform != TargetPlatform.macOS)) { + setMessage('logTransaction() is only supported on iOS and macOS'); + return; + } + + setMessage('Loading product $_testProductId...'); + + final response = + await InAppPurchase.instance.queryProductDetails({_testProductId}); + + if (response.error != null) { + setMessage('Failed to load product: ${response.error!.message}'); + return; + } + + if (response.productDetails.isEmpty) { + setMessage( + 'Product "$_testProductId" not found. ' + 'Make sure your StoreKit config file is set up correctly.', + ); + return; + } + + final product = response.productDetails.first; + setMessage('Initiating purchase for "${product.id}"...'); + + await InAppPurchase.instance.buyNonConsumable( + purchaseParam: PurchaseParam(productDetails: product), + ); + } + AnalyticsEventItem itemCreator() { return AnalyticsEventItem( affiliation: 'affil', @@ -365,6 +438,13 @@ class _MyHomePageState extends State { onPressed: _testInitiateOnDeviceConversionMeasurement, child: const Text('Test initiateOnDeviceConversionMeasurement'), ), + if (!kIsWeb && + (defaultTargetPlatform == TargetPlatform.iOS || + defaultTargetPlatform == TargetPlatform.macOS)) + MaterialButton( + onPressed: _testLogTransaction, + child: const Text('Test logTransaction (product: 123456)'), + ), Text( _message, style: const TextStyle(color: Color.fromARGB(255, 0, 155, 0)), diff --git a/packages/firebase_analytics/firebase_analytics/example/pubspec.yaml b/packages/firebase_analytics/firebase_analytics/example/pubspec.yaml index 02a21d62da40..d94b9a167ced 100755 --- a/packages/firebase_analytics/firebase_analytics/example/pubspec.yaml +++ b/packages/firebase_analytics/firebase_analytics/example/pubspec.yaml @@ -10,6 +10,7 @@ dependencies: firebase_core: ^4.5.0 flutter: sdk: flutter + in_app_purchase: ^3.2.3 flutter: uses-material-design: true diff --git a/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/Sources/firebase_analytics/FirebaseAnalyticsMessages.g.swift b/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/Sources/firebase_analytics/FirebaseAnalyticsMessages.g.swift index f8124ea1dc43..d8b175ba6e20 100644 --- a/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/Sources/firebase_analytics/FirebaseAnalyticsMessages.g.swift +++ b/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/Sources/firebase_analytics/FirebaseAnalyticsMessages.g.swift @@ -220,6 +220,7 @@ protocol FirebaseAnalyticsHostApi { func getSessionId(completion: @escaping (Result) -> Void) func initiateOnDeviceConversionMeasurement(arguments: [String: String?], completion: @escaping (Result) -> Void) + func logTransaction(transactionId: String, completion: @escaping (Result) -> Void) } /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. @@ -459,5 +460,26 @@ class FirebaseAnalyticsHostApiSetup { } else { initiateOnDeviceConversionMeasurementChannel.setMessageHandler(nil) } + let logTransactionChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.firebase_analytics_platform_interface.FirebaseAnalyticsHostApi.logTransaction\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec + ) + if let api { + logTransactionChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let transactionIdArg = args[0] as! String + api.logTransaction(transactionId: transactionIdArg) { result in + switch result { + case .success: + reply(wrapResult(nil)) + case let .failure(error): + reply(wrapError(error)) + } + } + } + } else { + logTransactionChannel.setMessageHandler(nil) + } } } diff --git a/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/Sources/firebase_analytics/FirebaseAnalyticsPlugin.swift b/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/Sources/firebase_analytics/FirebaseAnalyticsPlugin.swift index 9fde6c52ec13..41651b2ace45 100644 --- a/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/Sources/firebase_analytics/FirebaseAnalyticsPlugin.swift +++ b/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/Sources/firebase_analytics/FirebaseAnalyticsPlugin.swift @@ -14,6 +14,7 @@ import firebase_core_shared #endif import FirebaseAnalytics +import StoreKit let kFLTFirebaseAnalyticsName = "name" let kFLTFirebaseAnalyticsValue = "value" @@ -28,6 +29,8 @@ let kFLTFirebaseAnalyticsUserId = "userId" let FLTFirebaseAnalyticsChannelName = "plugins.flutter.io/firebase_analytics" +extension FlutterError: Error {} + public class FirebaseAnalyticsPlugin: NSObject, FLTFirebasePluginProtocol, FlutterPlugin, FirebaseAnalyticsHostApi { public static func register(with registrar: any FlutterPluginRegistrar) { @@ -142,6 +145,79 @@ public class FirebaseAnalyticsPlugin: NSObject, FLTFirebasePluginProtocol, Flutt completion(.success(())) } + func logTransaction(transactionId: String, + completion: @escaping (Result) -> Void) { + #if os(macOS) + if #available(macOS 12.0, *) { + logTransactionWithStoreKit(transactionId: transactionId, completion: completion) + } else { + completion(.failure(FlutterError( + code: "firebase_analytics", + message: "logTransaction() is only supported on macOS 12.0 or newer", + details: nil + ))) + } + #else + if #available(iOS 15.0, *) { + logTransactionWithStoreKit(transactionId: transactionId, completion: completion) + } else { + completion(.failure(FlutterError( + code: "firebase_analytics", + message: "logTransaction() is only supported on iOS 15.0 or newer", + details: nil + ))) + } + #endif + } + + #if os(macOS) + @available(macOS 12.0, *) + #else + @available(iOS 15.0, *) + #endif + private func logTransactionWithStoreKit(transactionId: String, + completion: @escaping (Result) -> Void) { + Task { + do { + guard let id = UInt64(transactionId) else { + completion(.failure(FlutterError( + code: "firebase_analytics", + message: "Invalid transactionId", + details: nil + ))) + return + } + + var foundTransaction: Transaction? + for await result in Transaction.all { + switch result { + case let .verified(transaction): + if transaction.id == id { + foundTransaction = transaction + break + } + case .unverified: + continue + } + } + + guard let transaction = foundTransaction else { + completion(.failure(FlutterError( + code: "firebase_analytics", + message: "Transaction not found", + details: nil + ))) + return + } + + Analytics.logTransaction(transaction) + completion(.success(())) + } catch { + completion(.failure(error)) + } + } + } + private func hexStringToData(_ hexString: String) -> Data? { let length = hexString.count guard length % 2 == 0 else { return nil } diff --git a/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/analytics_storekit_config.storekit b/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/analytics_storekit_config.storekit new file mode 100644 index 000000000000..092f5c7b86c4 --- /dev/null +++ b/packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/analytics_storekit_config.storekit @@ -0,0 +1,54 @@ +{ + "appPolicies" : { + "eula" : "", + "policies" : [ + { + "locale" : "en_US", + "policyText" : "", + "policyURL" : "" + } + ] + }, + "identifier" : "D50F15B4", + "nonRenewingSubscriptions" : [ + + ], + "products" : [ + { + "displayPrice" : "0.99", + "familyShareable" : false, + "internalID" : "FAAD0643", + "localizations" : [ + { + "description" : "", + "displayName" : "", + "locale" : "en_US" + } + ], + "productID" : "123456", + "referenceName" : "premium_upgrade", + "type" : "NonConsumable" + } + ], + "settings" : { + "_askToBuyEnabled" : false, + "_billingGracePeriodEnabled" : false, + "_billingIssuesEnabled" : false, + "_disableDialogs" : false, + "_failTransactionsEnabled" : false, + "_locale" : "en_US", + "_renewalBillingIssuesEnabled" : false, + "_storefront" : "USA", + "_storeKitErrors" : [ + + ], + "_timeRate" : 0 + }, + "subscriptionGroups" : [ + + ], + "version" : { + "major" : 4, + "minor" : 0 + } +} diff --git a/packages/firebase_analytics/firebase_analytics/lib/src/firebase_analytics.dart b/packages/firebase_analytics/firebase_analytics/lib/src/firebase_analytics.dart index 237a972b19c7..38c5a9077977 100755 --- a/packages/firebase_analytics/firebase_analytics/lib/src/firebase_analytics.dart +++ b/packages/firebase_analytics/firebase_analytics/lib/src/firebase_analytics.dart @@ -1242,6 +1242,23 @@ class FirebaseAnalytics extends FirebasePluginPlatform { ); } + /// Logs verified in-app purchase events in Google Analytics for Firebase + /// after a purchase is successful. + /// + /// Only available on iOS. + /// + /// You can obtain the [transactionId] from the + /// [in_app_purchase](https://pub.dev/packages/in_app_purchase) package. + Future logTransaction(String transactionId) async { + if (defaultTargetPlatform != TargetPlatform.iOS && + defaultTargetPlatform != TargetPlatform.macOS) { + throw UnimplementedError( + 'logTransaction() is only supported on iOS and macOS.', + ); + } + return _delegate.logTransaction(transactionId: transactionId); + } + /// Sets the duration of inactivity that terminates the current session. /// /// The default value is 1800000 milliseconds (30 minutes). diff --git a/packages/firebase_analytics/firebase_analytics/windows/messages.g.cpp b/packages/firebase_analytics/firebase_analytics/windows/messages.g.cpp index 2402f854a82a..14a92edf1452 100644 --- a/packages/firebase_analytics/firebase_analytics/windows/messages.g.cpp +++ b/packages/firebase_analytics/firebase_analytics/windows/messages.g.cpp @@ -525,6 +525,45 @@ void FirebaseAnalyticsHostApi::SetUp( channel.SetMessageHandler(nullptr); } } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_analytics_platform_interface." + "FirebaseAnalyticsHostApi.logTransaction" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_transaction_id_arg = args.at(0); + if (encodable_transaction_id_arg.IsNull()) { + reply(WrapError("transaction_id_arg unexpectedly null.")); + return; + } + const auto& transaction_id_arg = + std::get(encodable_transaction_id_arg); + api->LogTransaction( + transaction_id_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } } EncodableValue FirebaseAnalyticsHostApi::WrapError( diff --git a/packages/firebase_analytics/firebase_analytics/windows/messages.g.h b/packages/firebase_analytics/firebase_analytics/windows/messages.g.h index 69c859c6e515..b755d9ef6045 100644 --- a/packages/firebase_analytics/firebase_analytics/windows/messages.g.h +++ b/packages/firebase_analytics/firebase_analytics/windows/messages.g.h @@ -138,6 +138,9 @@ class FirebaseAnalyticsHostApi { virtual void InitiateOnDeviceConversionMeasurement( const flutter::EncodableMap& arguments, std::function reply)> result) = 0; + virtual void LogTransaction( + const std::string& transaction_id, + std::function reply)> result) = 0; // The codec used by FirebaseAnalyticsHostApi. static const flutter::StandardMessageCodec& GetCodec(); diff --git a/packages/firebase_analytics/firebase_analytics_platform_interface/lib/src/method_channel/method_channel_firebase_analytics.dart b/packages/firebase_analytics/firebase_analytics_platform_interface/lib/src/method_channel/method_channel_firebase_analytics.dart index 0ad8119fe21c..e3c5d41eaa42 100644 --- a/packages/firebase_analytics/firebase_analytics_platform_interface/lib/src/method_channel/method_channel_firebase_analytics.dart +++ b/packages/firebase_analytics/firebase_analytics_platform_interface/lib/src/method_channel/method_channel_firebase_analytics.dart @@ -195,4 +195,15 @@ class MethodChannelFirebaseAnalytics extends FirebaseAnalyticsPlatform { convertPlatformException(e, s); } } + + @override + Future logTransaction({ + required String transactionId, + }) { + try { + return _api.logTransaction(transactionId); + } catch (e, s) { + convertPlatformException(e, s); + } + } } diff --git a/packages/firebase_analytics/firebase_analytics_platform_interface/lib/src/pigeon/messages.pigeon.dart b/packages/firebase_analytics/firebase_analytics_platform_interface/lib/src/pigeon/messages.pigeon.dart index 6b74cc466a3d..003dd773639e 100644 --- a/packages/firebase_analytics/firebase_analytics_platform_interface/lib/src/pigeon/messages.pigeon.dart +++ b/packages/firebase_analytics/firebase_analytics_platform_interface/lib/src/pigeon/messages.pigeon.dart @@ -416,4 +416,30 @@ class FirebaseAnalyticsHostApi { return; } } + + Future logTransaction(String transactionId) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.firebase_analytics_platform_interface.FirebaseAnalyticsHostApi.logTransaction$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([transactionId]); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } } diff --git a/packages/firebase_analytics/firebase_analytics_platform_interface/lib/src/platform_interface/platform_interface_firebase_analytics.dart b/packages/firebase_analytics/firebase_analytics_platform_interface/lib/src/platform_interface/platform_interface_firebase_analytics.dart index 8fbe90066d5b..f69c816e707d 100644 --- a/packages/firebase_analytics/firebase_analytics_platform_interface/lib/src/platform_interface/platform_interface_firebase_analytics.dart +++ b/packages/firebase_analytics/firebase_analytics_platform_interface/lib/src/platform_interface/platform_interface_firebase_analytics.dart @@ -209,4 +209,10 @@ abstract class FirebaseAnalyticsPlatform extends PlatformInterface { 'initiateOnDeviceConversionMeasurement() is not implemented', ); } + + Future logTransaction({ + required String transactionId, + }) { + throw UnimplementedError('logTransaction() is not implemented'); + } } diff --git a/packages/firebase_analytics/firebase_analytics_platform_interface/pigeons/messages.dart b/packages/firebase_analytics/firebase_analytics_platform_interface/pigeons/messages.dart index 4c1ada746e85..b7008b4d3871 100644 --- a/packages/firebase_analytics/firebase_analytics_platform_interface/pigeons/messages.dart +++ b/packages/firebase_analytics/firebase_analytics_platform_interface/pigeons/messages.dart @@ -66,4 +66,7 @@ abstract class FirebaseAnalyticsHostApi { @async void initiateOnDeviceConversionMeasurement(Map arguments); + + @async + void logTransaction(String transactionId); } diff --git a/packages/firebase_analytics/firebase_analytics_platform_interface/test/pigeon/test_api.dart b/packages/firebase_analytics/firebase_analytics_platform_interface/test/pigeon/test_api.dart index cbef0d67a9eb..591cce9f19e2 100644 --- a/packages/firebase_analytics/firebase_analytics_platform_interface/test/pigeon/test_api.dart +++ b/packages/firebase_analytics/firebase_analytics_platform_interface/test/pigeon/test_api.dart @@ -67,6 +67,8 @@ abstract class TestFirebaseAnalyticsHostApi { Future initiateOnDeviceConversionMeasurement( Map arguments); + Future logTransaction(String transactionId); + static void setUp( TestFirebaseAnalyticsHostApi? api, { BinaryMessenger? binaryMessenger, @@ -409,5 +411,37 @@ abstract class TestFirebaseAnalyticsHostApi { }); } } + { + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.firebase_analytics_platform_interface.FirebaseAnalyticsHostApi.logTransaction$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.firebase_analytics_platform_interface.FirebaseAnalyticsHostApi.logTransaction was null.'); + final List args = (message as List?)!; + final String? arg_transactionId = (args[0] as String?); + assert(arg_transactionId != null, + 'Argument for dev.flutter.pigeon.firebase_analytics_platform_interface.FirebaseAnalyticsHostApi.logTransaction was null, expected non-null String.'); + try { + await api.logTransaction(arg_transactionId!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } } } diff --git a/tests/integration_test/firebase_analytics/firebase_analytics_e2e_test.dart b/tests/integration_test/firebase_analytics/firebase_analytics_e2e_test.dart index f7d309a3b306..674bfa0cdf80 100644 --- a/tests/integration_test/firebase_analytics/firebase_analytics_e2e_test.dart +++ b/tests/integration_test/firebase_analytics/firebase_analytics_e2e_test.dart @@ -5,6 +5,7 @@ import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:tests/firebase_options.dart'; @@ -356,5 +357,45 @@ void main() { }, skip: kIsWeb || defaultTargetPlatform != TargetPlatform.iOS, ); + + group('logTransaction', () { + test( + 'throws when transactionId is not a valid numeric string', + () async { + await expectLater( + FirebaseAnalytics.instance.logTransaction('not_a_number'), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Invalid transactionId', + ), + ), + ); + }, + skip: kIsWeb || + (defaultTargetPlatform != TargetPlatform.iOS && + defaultTargetPlatform != TargetPlatform.macOS), + ); + + test( + 'throws when transactionId is valid format but transaction not found in StoreKit', + () async { + await expectLater( + FirebaseAnalytics.instance.logTransaction('12345'), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Transaction not found', + ), + ), + ); + }, + skip: kIsWeb || + (defaultTargetPlatform != TargetPlatform.iOS && + defaultTargetPlatform != TargetPlatform.macOS), + ); + }); }); } From 007689f99866582828a063d174c52ebba13ac0ef Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Fri, 6 Mar 2026 13:51:51 +0100 Subject: [PATCH 66/72] feat(database,windows): add support for Realtime Database to windows (#18079) * feat(database,windows): add support for Realtime Database to windows * format * fix * clean * additionnal librairies * emulator support attempt * format * fix tests * clean * details for CI * clean * clean * fix * clear * fix * removing dumb emulator support * fixing event channels * skip test for database, only work in manual testing * format * skip test * clean * clean --- .github/workflows/windows.yaml | 2 +- .../firebase_core/windows/CMakeLists.txt | 2 +- .../FirebaseDatabaseMessages.g.swift | 179 +- .../firebase_database/pubspec.yaml | 2 + .../firebase_database/windows/CMakeLists.txt | 60 + .../windows/firebase_database_plugin.cpp | 1123 +++++++++++ .../windows/firebase_database_plugin.h | 144 ++ .../firebase_database_plugin_c_api.cpp | 16 + .../firebase_database_plugin_c_api.h | 29 + .../firebase_database/windows/messages.g.cpp | 1742 +++++++++++++++++ .../firebase_database/windows/messages.g.h | 449 +++++ .../windows/plugin_version.h.in | 13 + .../pigeons/messages.dart | 3 + tests/windows/flutter/CMakeLists.txt | 7 +- .../flutter/generated_plugin_registrant.cc | 3 + tests/windows/flutter/generated_plugins.cmake | 1 + 16 files changed, 3683 insertions(+), 92 deletions(-) create mode 100644 packages/firebase_database/firebase_database/windows/CMakeLists.txt create mode 100644 packages/firebase_database/firebase_database/windows/firebase_database_plugin.cpp create mode 100644 packages/firebase_database/firebase_database/windows/firebase_database_plugin.h create mode 100644 packages/firebase_database/firebase_database/windows/firebase_database_plugin_c_api.cpp create mode 100644 packages/firebase_database/firebase_database/windows/include/firebase_database/firebase_database_plugin_c_api.h create mode 100644 packages/firebase_database/firebase_database/windows/messages.g.cpp create mode 100644 packages/firebase_database/firebase_database/windows/messages.g.h create mode 100644 packages/firebase_database/firebase_database/windows/plugin_version.h.in diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index ff82836a8a18..1783aa967913 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -56,7 +56,7 @@ jobs: - name: "Build Windows (Release)" run: cd tests && flutter build windows --release - name: Start Firebase Emulator and run tests - run: cd ./.github/workflows/scripts && firebase emulators:exec --project flutterfire-e2e-tests "cd ../../../tests && flutter test .\integration_test\e2e_test.dart -d windows" + run: cd ./.github/workflows/scripts && firebase emulators:exec --project flutterfire-e2e-tests "cd ../../../tests && flutter test .\integration_test\e2e_test.dart -d windows --verbose" # We cannot run the tests but we can still try to build the app because of https://github.com/flutter/flutter/issues/79213 windows-firestore: diff --git a/packages/firebase_core/firebase_core/windows/CMakeLists.txt b/packages/firebase_core/firebase_core/windows/CMakeLists.txt index b5d1fce438e1..d944944c8c8a 100644 --- a/packages/firebase_core/firebase_core/windows/CMakeLists.txt +++ b/packages/firebase_core/firebase_core/windows/CMakeLists.txt @@ -122,7 +122,7 @@ add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL) target_include_directories(${PLUGIN_NAME} INTERFACE "${FIREBASE_CPP_SDK_DIR}/include") -set(FIREBASE_RELEASE_PATH_LIBS firebase_app firebase_auth firebase_remote_config firebase_storage firebase_firestore) +set(FIREBASE_RELEASE_PATH_LIBS firebase_app firebase_auth firebase_remote_config firebase_storage firebase_firestore firebase_database) foreach(firebase_lib IN ITEMS ${FIREBASE_RELEASE_PATH_LIBS}) get_target_property(firebase_lib_path ${firebase_lib} IMPORTED_LOCATION) string(REPLACE "Debug" "Release" firebase_lib_release_path ${firebase_lib_path}) diff --git a/packages/firebase_database/firebase_database/ios/firebase_database/Sources/firebase_database/FirebaseDatabaseMessages.g.swift b/packages/firebase_database/firebase_database/ios/firebase_database/Sources/firebase_database/FirebaseDatabaseMessages.g.swift index 174bb8cfc69b..e91097627f7e 100644 --- a/packages/firebase_database/firebase_database/ios/firebase_database/Sources/firebase_database/FirebaseDatabaseMessages.g.swift +++ b/packages/firebase_database/firebase_database/ios/firebase_database/Sources/firebase_database/FirebaseDatabaseMessages.g.swift @@ -59,7 +59,8 @@ private func wrapError(_ error: Any) -> [Any?] { private func createConnectionError(withChannelName channelName: String) -> PigeonError { PigeonError( - code: "channel-error", message: "Unable to establish connection on channel: '\(channelName)'.", + code: "channel-error", + message: "Unable to establish connection on channel: '\(channelName)'.", details: "" ) } @@ -490,9 +491,10 @@ private class FirebaseDatabaseMessagesPigeonCodecReaderWriter: FlutterStandardRe } class FirebaseDatabaseMessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { - static let shared = FirebaseDatabaseMessagesPigeonCodec( - readerWriter: FirebaseDatabaseMessagesPigeonCodecReaderWriter() - ) + static let shared = + FirebaseDatabaseMessagesPigeonCodec( + readerWriter: FirebaseDatabaseMessagesPigeonCodecReaderWriter() + ) } /// Generated protocol from Pigeon that represents a handler of messages from Flutter. @@ -558,9 +560,9 @@ class FirebaseDatabaseHostApiSetup { messageChannelSuffix: String = "") { let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" let goOnlineChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.goOnline\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.goOnline\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { goOnlineChannel.setMessageHandler { message, reply in @@ -579,9 +581,9 @@ class FirebaseDatabaseHostApiSetup { goOnlineChannel.setMessageHandler(nil) } let goOfflineChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.goOffline\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.goOffline\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { goOfflineChannel.setMessageHandler { message, reply in @@ -600,9 +602,9 @@ class FirebaseDatabaseHostApiSetup { goOfflineChannel.setMessageHandler(nil) } let setPersistenceEnabledChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.setPersistenceEnabled\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.setPersistenceEnabled\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { setPersistenceEnabledChannel.setMessageHandler { message, reply in @@ -622,9 +624,9 @@ class FirebaseDatabaseHostApiSetup { setPersistenceEnabledChannel.setMessageHandler(nil) } let setPersistenceCacheSizeBytesChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.setPersistenceCacheSizeBytes\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.setPersistenceCacheSizeBytes\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { setPersistenceCacheSizeBytesChannel.setMessageHandler { message, reply in @@ -644,9 +646,9 @@ class FirebaseDatabaseHostApiSetup { setPersistenceCacheSizeBytesChannel.setMessageHandler(nil) } let setLoggingEnabledChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.setLoggingEnabled\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.setLoggingEnabled\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { setLoggingEnabledChannel.setMessageHandler { message, reply in @@ -666,9 +668,9 @@ class FirebaseDatabaseHostApiSetup { setLoggingEnabledChannel.setMessageHandler(nil) } let useDatabaseEmulatorChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.useDatabaseEmulator\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.useDatabaseEmulator\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { useDatabaseEmulatorChannel.setMessageHandler { message, reply in @@ -689,9 +691,9 @@ class FirebaseDatabaseHostApiSetup { useDatabaseEmulatorChannel.setMessageHandler(nil) } let refChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.ref\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.ref\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { refChannel.setMessageHandler { message, reply in @@ -711,9 +713,9 @@ class FirebaseDatabaseHostApiSetup { refChannel.setMessageHandler(nil) } let refFromURLChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.refFromURL\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.refFromURL\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { refFromURLChannel.setMessageHandler { message, reply in @@ -733,9 +735,9 @@ class FirebaseDatabaseHostApiSetup { refFromURLChannel.setMessageHandler(nil) } let purgeOutstandingWritesChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.purgeOutstandingWrites\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.purgeOutstandingWrites\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { purgeOutstandingWritesChannel.setMessageHandler { message, reply in @@ -754,9 +756,9 @@ class FirebaseDatabaseHostApiSetup { purgeOutstandingWritesChannel.setMessageHandler(nil) } let databaseReferenceSetChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.databaseReferenceSet\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.databaseReferenceSet\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { databaseReferenceSetChannel.setMessageHandler { message, reply in @@ -776,9 +778,9 @@ class FirebaseDatabaseHostApiSetup { databaseReferenceSetChannel.setMessageHandler(nil) } let databaseReferenceSetWithPriorityChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.databaseReferenceSetWithPriority\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.databaseReferenceSetWithPriority\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { databaseReferenceSetWithPriorityChannel.setMessageHandler { message, reply in @@ -798,9 +800,9 @@ class FirebaseDatabaseHostApiSetup { databaseReferenceSetWithPriorityChannel.setMessageHandler(nil) } let databaseReferenceUpdateChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.databaseReferenceUpdate\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.databaseReferenceUpdate\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { databaseReferenceUpdateChannel.setMessageHandler { message, reply in @@ -820,9 +822,9 @@ class FirebaseDatabaseHostApiSetup { databaseReferenceUpdateChannel.setMessageHandler(nil) } let databaseReferenceSetPriorityChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.databaseReferenceSetPriority\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.databaseReferenceSetPriority\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { databaseReferenceSetPriorityChannel.setMessageHandler { message, reply in @@ -842,9 +844,9 @@ class FirebaseDatabaseHostApiSetup { databaseReferenceSetPriorityChannel.setMessageHandler(nil) } let databaseReferenceRunTransactionChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.databaseReferenceRunTransaction\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.databaseReferenceRunTransaction\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { databaseReferenceRunTransactionChannel.setMessageHandler { message, reply in @@ -864,32 +866,33 @@ class FirebaseDatabaseHostApiSetup { databaseReferenceRunTransactionChannel.setMessageHandler(nil) } let databaseReferenceGetTransactionResultChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.databaseReferenceGetTransactionResult\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.databaseReferenceGetTransactionResult\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { databaseReferenceGetTransactionResultChannel.setMessageHandler { message, reply in let args = message as! [Any?] let appArg = args[0] as! DatabasePigeonFirebaseApp let transactionKeyArg = args[1] as! Int64 - api.databaseReferenceGetTransactionResult(app: appArg, transactionKey: transactionKeyArg) { - result in - switch result { - case let .success(res): - reply(wrapResult(res)) - case let .failure(error): - reply(wrapError(error)) + api + .databaseReferenceGetTransactionResult(app: appArg, + transactionKey: transactionKeyArg) { result in + switch result { + case let .success(res): + reply(wrapResult(res)) + case let .failure(error): + reply(wrapError(error)) + } } - } } } else { databaseReferenceGetTransactionResultChannel.setMessageHandler(nil) } let onDisconnectSetChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.onDisconnectSet\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.onDisconnectSet\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { onDisconnectSetChannel.setMessageHandler { message, reply in @@ -909,9 +912,9 @@ class FirebaseDatabaseHostApiSetup { onDisconnectSetChannel.setMessageHandler(nil) } let onDisconnectSetWithPriorityChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.onDisconnectSetWithPriority\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.onDisconnectSetWithPriority\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { onDisconnectSetWithPriorityChannel.setMessageHandler { message, reply in @@ -931,9 +934,9 @@ class FirebaseDatabaseHostApiSetup { onDisconnectSetWithPriorityChannel.setMessageHandler(nil) } let onDisconnectUpdateChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.onDisconnectUpdate\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.onDisconnectUpdate\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { onDisconnectUpdateChannel.setMessageHandler { message, reply in @@ -953,9 +956,9 @@ class FirebaseDatabaseHostApiSetup { onDisconnectUpdateChannel.setMessageHandler(nil) } let onDisconnectCancelChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.onDisconnectCancel\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.onDisconnectCancel\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { onDisconnectCancelChannel.setMessageHandler { message, reply in @@ -975,9 +978,9 @@ class FirebaseDatabaseHostApiSetup { onDisconnectCancelChannel.setMessageHandler(nil) } let queryObserveChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.queryObserve\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.queryObserve\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { queryObserveChannel.setMessageHandler { message, reply in @@ -997,9 +1000,9 @@ class FirebaseDatabaseHostApiSetup { queryObserveChannel.setMessageHandler(nil) } let queryKeepSyncedChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.queryKeepSynced\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.queryKeepSynced\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { queryKeepSyncedChannel.setMessageHandler { message, reply in @@ -1019,9 +1022,9 @@ class FirebaseDatabaseHostApiSetup { queryKeepSyncedChannel.setMessageHandler(nil) } let queryGetChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.queryGet\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec + name: "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseHostApi.queryGet\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec ) if let api { queryGetChannel.setMessageHandler { message, reply in @@ -1067,10 +1070,11 @@ class FirebaseDatabaseFlutterApi: FirebaseDatabaseFlutterApiProtocol { snapshotValue snapshotValueArg: Any?, completion: @escaping (Result) -> Void) { - let channelName = - "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseFlutterApi.callTransactionHandler\(messageChannelSuffix)" + let channelName = "dev.flutter.pigeon.firebase_database_platform_interface.FirebaseDatabaseFlutterApi.callTransactionHandler\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( - name: channelName, binaryMessenger: binaryMessenger, codec: codec + name: channelName, + binaryMessenger: binaryMessenger, + codec: codec ) channel.sendMessage([transactionKeyArg, snapshotValueArg] as [Any?]) { response in guard let listResponse = response as? [Any?] else { @@ -1083,14 +1087,11 @@ class FirebaseDatabaseFlutterApi: FirebaseDatabaseFlutterApiProtocol { let details: String? = nilOrValue(listResponse[2]) completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { - completion( - .failure( - PigeonError( - code: "null-error", - message: "Flutter api returned null value for non-null return value.", details: "" - ) - ) - ) + completion(.failure(PigeonError( + code: "null-error", + message: "Flutter api returned null value for non-null return value.", + details: "" + ))) } else { let result = listResponse[0] as! TransactionHandlerResult completion(.success(result)) diff --git a/packages/firebase_database/firebase_database/pubspec.yaml b/packages/firebase_database/firebase_database/pubspec.yaml index 708d05cfe5ac..abbe7eb8e522 100755 --- a/packages/firebase_database/firebase_database/pubspec.yaml +++ b/packages/firebase_database/firebase_database/pubspec.yaml @@ -39,5 +39,7 @@ flutter: pluginClass: FLTFirebaseDatabasePlugin macos: pluginClass: FLTFirebaseDatabasePlugin + windows: + pluginClass: FirebaseDatabasePluginCApi web: default_package: firebase_database_web diff --git a/packages/firebase_database/firebase_database/windows/CMakeLists.txt b/packages/firebase_database/firebase_database/windows/CMakeLists.txt new file mode 100644 index 000000000000..b85c61d20594 --- /dev/null +++ b/packages/firebase_database/firebase_database/windows/CMakeLists.txt @@ -0,0 +1,60 @@ +cmake_minimum_required(VERSION 3.14) + +set(PROJECT_NAME "flutterfire_database") +project(${PROJECT_NAME} LANGUAGES CXX) + +set(PLUGIN_NAME "firebase_database_plugin") + +list(APPEND PLUGIN_SOURCES + "firebase_database_plugin.cpp" + "firebase_database_plugin.h" + "messages.g.cpp" + "messages.g.h" +) + +# Read version from pubspec.yaml +file(STRINGS "../pubspec.yaml" pubspec_content) +foreach(line ${pubspec_content}) + string(FIND ${line} "version: " has_version) + + if("${has_version}" STREQUAL "0") + string(FIND ${line} ": " version_start_pos) + math(EXPR version_start_pos "${version_start_pos} + 2") + string(LENGTH ${line} version_end_pos) + math(EXPR len "${version_end_pos} - ${version_start_pos}") + string(SUBSTRING ${line} ${version_start_pos} ${len} PLUGIN_VERSION) + break() + endif() +endforeach(line) + +configure_file(plugin_version.h.in ${CMAKE_BINARY_DIR}/generated/firebase_database/plugin_version.h) +include_directories(${CMAKE_BINARY_DIR}/generated/) + +add_library(${PLUGIN_NAME} STATIC + "include/firebase_database/firebase_database_plugin_c_api.h" + "firebase_database_plugin_c_api.cpp" + ${PLUGIN_SOURCES} + ${CMAKE_BINARY_DIR}/generated/firebase_database/plugin_version.h +) + +apply_standard_settings(${PLUGIN_NAME}) + +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PUBLIC FLUTTER_PLUGIN_IMPL) +target_compile_definitions(${PLUGIN_NAME} PRIVATE -DINTERNAL_EXPERIMENTAL=1) + +set(MSVC_RUNTIME_MODE MD) +set(firebase_libs firebase_core_plugin firebase_database) +set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32 rpcrt4 ole32 shell32 Bcrypt.lib DbgHelp.lib) +set(RTDB_ADDITIONAL_LIBS iphlpapi psapi userenv) +target_link_libraries(${PLUGIN_NAME} PRIVATE "${firebase_libs}" "${ADDITIONAL_LIBS}" "${RTDB_ADDITIONAL_LIBS}") + +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) + +set(firebase_database_bundled_libraries + "" + PARENT_SCOPE +) diff --git a/packages/firebase_database/firebase_database/windows/firebase_database_plugin.cpp b/packages/firebase_database/firebase_database/windows/firebase_database_plugin.cpp new file mode 100644 index 000000000000..f523a86ffe59 --- /dev/null +++ b/packages/firebase_database/firebase_database/windows/firebase_database_plugin.cpp @@ -0,0 +1,1123 @@ +// Copyright 2025, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#include "firebase_database_plugin.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "firebase/app.h" +#include "firebase/database.h" +#include "firebase/database/common.h" +#include "firebase/database/data_snapshot.h" +#include "firebase/database/database_reference.h" +#include "firebase/database/disconnection.h" +#include "firebase/database/listener.h" +#include "firebase/database/mutable_data.h" +#include "firebase/database/query.h" +#include "firebase/future.h" +#include "firebase/log.h" +#include "firebase/variant.h" +#include "firebase_database/plugin_version.h" +#include "messages.g.h" + +using firebase::App; +using firebase::Future; +using firebase::Variant; +using firebase::database::Database; +using firebase::database::DatabaseReference; +using firebase::database::DataSnapshot; +using firebase::database::Error; +using firebase::database::MutableData; +using firebase::database::TransactionResult; +using flutter::EncodableList; +using flutter::EncodableMap; +using flutter::EncodableValue; + +namespace firebase_database_windows { + +static const std::string kLibraryName = "flutter-fire-db"; + +// Static member initialization +flutter::BinaryMessenger* FirebaseDatabasePlugin::messenger_ = nullptr; +std::map>> + FirebaseDatabasePlugin::event_channels_; +std::map>> + FirebaseDatabasePlugin::stream_handlers_; +std::map + FirebaseDatabasePlugin::database_instances_; + +// atexit handler: clean up Database resources before static destruction. +// 1. Clear event channels to trigger StreamHandler destruction, which +// unregisters listeners from the Database while it's still alive. +// 2. Call GoOffline() to close WebSocket connections so thread joins +// during App::~App() → Database::DeleteInternal() complete quickly. +static void CleanupBeforeStaticDestruction() { + // Destroy event channels and stream handlers first. This triggers + // StreamHandler destructors which call RemoveValueListener / + // RemoveChildListener while the Database is still valid. + FirebaseDatabasePlugin::event_channels_.clear(); + FirebaseDatabasePlugin::stream_handlers_.clear(); + + // Disconnect all Database instances to close WebSocket connections. + for (auto& pair : FirebaseDatabasePlugin::database_instances_) { + if (pair.second) { + pair.second->GoOffline(); + } + } + // Give the scheduler thread time to process GoOffline callbacks. + std::this_thread::sleep_for(std::chrono::milliseconds(100)); +} + +// --- Helper: Register an EventChannel with a generated name --- +static std::string RegisterEventChannel( + const std::string& prefix, + std::unique_ptr> handler) { + static int channel_counter = 0; + std::string channelName = + prefix + std::to_string(channel_counter++) + "_" + + std::to_string( + std::chrono::system_clock::now().time_since_epoch().count()); + + FirebaseDatabasePlugin::event_channels_[channelName] = + std::make_unique>( + FirebaseDatabasePlugin::messenger_, channelName, + &flutter::StandardMethodCodec::GetInstance()); + FirebaseDatabasePlugin::stream_handlers_[channelName] = std::move(handler); + FirebaseDatabasePlugin::event_channels_[channelName]->SetStreamHandler( + std::move(FirebaseDatabasePlugin::stream_handlers_[channelName])); + return channelName; +} + +// --- Helper: Convert firebase::Variant to flutter::EncodableValue --- +EncodableValue FirebaseDatabasePlugin::VariantToEncodableValue( + const Variant& variant) { + switch (variant.type()) { + case Variant::kTypeNull: + return EncodableValue(); + case Variant::kTypeInt64: + return EncodableValue(variant.int64_value()); + case Variant::kTypeDouble: + return EncodableValue(variant.double_value()); + case Variant::kTypeBool: + return EncodableValue(variant.bool_value()); + case Variant::kTypeStaticString: + return EncodableValue(std::string(variant.string_value())); + case Variant::kTypeMutableString: + return EncodableValue(variant.mutable_string()); + case Variant::kTypeVector: { + EncodableList list; + for (const auto& item : variant.vector()) { + list.push_back(VariantToEncodableValue(item)); + } + return EncodableValue(list); + } + case Variant::kTypeMap: { + EncodableMap map; + for (const auto& kv : variant.map()) { + EncodableValue key = VariantToEncodableValue(kv.first); + EncodableValue value = VariantToEncodableValue(kv.second); + map[key] = value; + } + return EncodableValue(map); + } + case Variant::kTypeStaticBlob: { + std::vector blob(variant.blob_data(), + variant.blob_data() + variant.blob_size()); + return EncodableValue(blob); + } + case Variant::kTypeMutableBlob: { + std::vector blob( + variant.mutable_blob_data(), + variant.mutable_blob_data() + variant.blob_size()); + return EncodableValue(blob); + } + default: + return EncodableValue(); + } +} + +// --- Helper: Convert flutter::EncodableValue to firebase::Variant --- +Variant FirebaseDatabasePlugin::EncodableValueToVariant( + const EncodableValue& value) { + if (std::holds_alternative(value)) { + return Variant::Null(); + } else if (std::holds_alternative(value)) { + return Variant(std::get(value)); + } else if (std::holds_alternative(value)) { + return Variant(static_cast(std::get(value))); + } else if (std::holds_alternative(value)) { + return Variant(std::get(value)); + } else if (std::holds_alternative(value)) { + return Variant(std::get(value)); + } else if (std::holds_alternative(value)) { + return Variant(std::get(value)); + } else if (std::holds_alternative>(value)) { + const auto& blob = std::get>(value); + return Variant::FromMutableBlob(blob.data(), blob.size()); + } else if (std::holds_alternative(value)) { + const auto& list = std::get(value); + std::vector vec; + vec.reserve(list.size()); + for (const auto& item : list) { + vec.push_back(EncodableValueToVariant(item)); + } + return Variant(vec); + } else if (std::holds_alternative(value)) { + const auto& map = std::get(value); + std::map variant_map; + for (const auto& kv : map) { + variant_map[EncodableValueToVariant(kv.first)] = + EncodableValueToVariant(kv.second); + } + return Variant(variant_map); + } + return Variant::Null(); +} + +// --- Helper: Error code string from C++ SDK Error enum --- +std::string FirebaseDatabasePlugin::GetDatabaseErrorCode(Error error) { + switch (error) { + case Error::kErrorNone: + return "none"; + case Error::kErrorDisconnected: + return "disconnected"; + case Error::kErrorExpiredToken: + return "expired-token"; + case Error::kErrorInvalidToken: + return "invalid-token"; + case Error::kErrorMaxRetries: + return "max-retries"; + case Error::kErrorNetworkError: + return "network-error"; + case Error::kErrorOperationFailed: + return "operation-failed"; + case Error::kErrorOverriddenBySet: + return "overridden-by-set"; + case Error::kErrorPermissionDenied: + return "permission-denied"; + case Error::kErrorUnavailable: + return "unavailable"; + case Error::kErrorWriteCanceled: + return "write-canceled"; + case Error::kErrorInvalidVariantType: + return "invalid-variant-type"; + case Error::kErrorConflictingOperationInProgress: + return "conflicting-operation-in-progress"; + case Error::kErrorTransactionAbortedByUser: + return "transaction-aborted-by-user"; + default: + return "unknown"; + } +} + +std::string FirebaseDatabasePlugin::GetDatabaseErrorMessage(Error error) { + const char* msg = firebase::database::GetErrorMessage(error); + return msg ? std::string(msg) : "Unknown error"; +} + +FlutterError FirebaseDatabasePlugin::ParseError( + const firebase::FutureBase& future) { + Error error = static_cast(future.error()); + std::string code = GetDatabaseErrorCode(error); + std::string message = + future.error_message() ? future.error_message() : "Unknown error"; + return FlutterError(code, message); +} + +// --- Helper: Convert DataSnapshot to EncodableMap --- +EncodableMap FirebaseDatabasePlugin::DataSnapshotToEncodableMap( + const DataSnapshot& snapshot) { + EncodableMap result; + result[EncodableValue("key")] = + snapshot.key() ? EncodableValue(std::string(snapshot.key())) + : EncodableValue(); + result[EncodableValue("value")] = VariantToEncodableValue(snapshot.value()); + result[EncodableValue("priority")] = + VariantToEncodableValue(snapshot.priority()); + + EncodableList childKeys; + std::vector children = snapshot.children(); + for (const auto& child : children) { + if (child.key()) { + childKeys.push_back(EncodableValue(std::string(child.key()))); + } + } + result[EncodableValue("childKeys")] = EncodableValue(childKeys); + + return result; +} + +// --- Helper: Apply query modifiers --- +firebase::database::Query FirebaseDatabasePlugin::ApplyQueryModifiers( + firebase::database::Query query, const EncodableList& modifiers) { + for (const auto& mod_value : modifiers) { + const auto& mod = std::get(mod_value); + + auto type_it = mod.find(EncodableValue("type")); + if (type_it == mod.end()) continue; + std::string type = std::get(type_it->second); + + auto name_it = mod.find(EncodableValue("name")); + if (name_it == mod.end()) continue; + std::string name = std::get(name_it->second); + + if (type == "orderBy") { + if (name == "orderByChild") { + auto path_it = mod.find(EncodableValue("path")); + if (path_it != mod.end()) { + query = query.OrderByChild( + std::get(path_it->second).c_str()); + } + } else if (name == "orderByKey") { + query = query.OrderByKey(); + } else if (name == "orderByValue") { + query = query.OrderByValue(); + } else if (name == "orderByPriority") { + query = query.OrderByPriority(); + } + } else if (type == "cursor") { + auto value_it = mod.find(EncodableValue("value")); + Variant cursor_value = Variant::Null(); + if (value_it != mod.end()) { + cursor_value = EncodableValueToVariant(value_it->second); + } + + auto key_it = mod.find(EncodableValue("key")); + const char* child_key = nullptr; + std::string key_str; + if (key_it != mod.end() && + std::holds_alternative(key_it->second)) { + key_str = std::get(key_it->second); + child_key = key_str.c_str(); + } + + if (name == "startAt") { + query = child_key ? query.StartAt(cursor_value, child_key) + : query.StartAt(cursor_value); + } else if (name == "startAfter") { + // C++ SDK doesn't have StartAfter; use StartAt workaround + query = child_key ? query.StartAt(cursor_value, child_key) + : query.StartAt(cursor_value); + } else if (name == "endAt") { + query = child_key ? query.EndAt(cursor_value, child_key) + : query.EndAt(cursor_value); + } else if (name == "endBefore") { + // C++ SDK doesn't have EndBefore; use EndAt workaround + query = child_key ? query.EndAt(cursor_value, child_key) + : query.EndAt(cursor_value); + } + } else if (type == "limit") { + auto limit_it = mod.find(EncodableValue("limit")); + if (limit_it != mod.end()) { + int limit = 0; + if (std::holds_alternative(limit_it->second)) { + limit = std::get(limit_it->second); + } else if (std::holds_alternative(limit_it->second)) { + limit = static_cast(std::get(limit_it->second)); + } + if (name == "limitToFirst") { + query = query.LimitToFirst(static_cast(limit)); + } else if (name == "limitToLast") { + query = query.LimitToLast(static_cast(limit)); + } + } + } + } + return query; +} + +// ===== Plugin Implementation ===== + +FirebaseDatabasePlugin::FirebaseDatabasePlugin() {} + +FirebaseDatabasePlugin::~FirebaseDatabasePlugin() {} + +void FirebaseDatabasePlugin::RegisterWithRegistrar( + flutter::PluginRegistrarWindows* registrar) { + auto plugin = std::make_unique(); + messenger_ = registrar->messenger(); + FirebaseDatabaseHostApi::SetUp(registrar->messenger(), plugin.get()); + registrar->AddPlugin(std::move(plugin)); + App::RegisterLibrary(kLibraryName.c_str(), getPluginVersion().c_str(), + nullptr); + + // Register atexit handler to clean up listeners and disconnect + // before static destruction triggers thread joins in the C++ SDK. + std::atexit(CleanupBeforeStaticDestruction); +} + +// --- Helper: Get Database instance from Pigeon app --- +Database* FirebaseDatabasePlugin::GetDatabaseFromPigeon( + const DatabasePigeonFirebaseApp& app) { + App* firebase_app = App::GetInstance(app.app_name().c_str()); + if (!firebase_app) { + return nullptr; + } + + const auto& settings = app.settings(); + const std::string* url = app.database_u_r_l(); + + // Build a cache key from app name + effective URL (like Firestore does) + std::string effective_url; + if (url && !url->empty()) { + effective_url = *url; + } + + std::string cache_key = app.app_name() + "-" + effective_url; + + // Return cached instance if available (raw pointer, not owned). + // The C++ SDK manages Database instance lifetime internally. + // App::~App() triggers Database::DeleteInternal() during static destruction. + auto it = database_instances_.find(cache_key); + if (it != database_instances_.end()) { + return it->second; + } + + // Create new instance + // Always pass the URL explicitly - the C++ SDK on desktop may not + // properly read database_url from app options without it. + const char* app_db_url = firebase_app->options().database_url(); + if (effective_url.empty() && app_db_url && strlen(app_db_url) > 0) { + effective_url = app_db_url; + } + Database* database = nullptr; + if (!effective_url.empty()) { + database = Database::GetInstance(firebase_app, effective_url.c_str()); + } else { + database = Database::GetInstance(firebase_app); + } + + if (!database) return nullptr; + + if (settings.persistence_enabled()) { + database->set_persistence_enabled(*settings.persistence_enabled()); + } + if (settings.logging_enabled() && *settings.logging_enabled()) { + database->set_log_level(firebase::kLogLevelDebug); + } + + // Cache raw pointer. We do NOT take ownership — the C++ SDK manages + // the Database lifetime via App's CleanupNotifier. This matches the + // pattern used by firebase_auth and firebase_storage. + database_instances_[cache_key] = database; + + return database; +} + +// ===== Database methods ===== + +void FirebaseDatabasePlugin::GoOnline( + const DatabasePigeonFirebaseApp& app, + std::function reply)> result) { + Database* database = GetDatabaseFromPigeon(app); + if (!database) { + result(FlutterError("unknown", "Database instance not found")); + return; + } + database->GoOnline(); + result(std::nullopt); +} + +void FirebaseDatabasePlugin::GoOffline( + const DatabasePigeonFirebaseApp& app, + std::function reply)> result) { + Database* database = GetDatabaseFromPigeon(app); + if (!database) { + result(FlutterError("unknown", "Database instance not found")); + return; + } + database->GoOffline(); + result(std::nullopt); +} + +void FirebaseDatabasePlugin::SetPersistenceEnabled( + const DatabasePigeonFirebaseApp& app, bool enabled, + std::function reply)> result) { + Database* database = GetDatabaseFromPigeon(app); + if (!database) { + result(FlutterError("unknown", "Database instance not found")); + return; + } + database->set_persistence_enabled(enabled); + result(std::nullopt); +} + +void FirebaseDatabasePlugin::SetPersistenceCacheSizeBytes( + const DatabasePigeonFirebaseApp& app, int64_t cache_size, + std::function reply)> result) { + // C++ SDK doesn't directly support setting cache size + result(std::nullopt); +} + +void FirebaseDatabasePlugin::SetLoggingEnabled( + const DatabasePigeonFirebaseApp& app, bool enabled, + std::function reply)> result) { + Database* database = GetDatabaseFromPigeon(app); + if (!database) { + result(FlutterError("unknown", "Database instance not found")); + return; + } + database->set_log_level(enabled ? firebase::kLogLevelDebug + : firebase::kLogLevelInfo); + result(std::nullopt); +} + +void FirebaseDatabasePlugin::UseDatabaseEmulator( + const DatabasePigeonFirebaseApp& app, const std::string& host, int64_t port, + std::function reply)> result) { + // The C++ SDK for Realtime Database does not have a UseEmulator API. + // On Windows, tests run against the live Firebase instance. + result(std::nullopt); +} + +void FirebaseDatabasePlugin::Ref( + const DatabasePigeonFirebaseApp& app, const std::string* path, + std::function reply)> result) { + Database* database = GetDatabaseFromPigeon(app); + if (!database) { + result(FlutterError("unknown", "Database instance not found")); + return; + } + + DatabaseReference ref; + if (path && !path->empty()) { + ref = database->GetReference(path->c_str()); + } else { + ref = database->GetReference(); + } + + std::string ref_path; + if (ref.key()) { + // Build path from the URL + std::string url = ref.url(); + // Extract path from URL (after the host) + auto pos = url.find(".com/"); + if (pos != std::string::npos) { + ref_path = url.substr(pos + 4); + } else { + ref_path = path ? *path : "/"; + } + } else { + ref_path = path ? *path : "/"; + } + + result(DatabaseReferencePlatform(ref_path)); +} + +void FirebaseDatabasePlugin::RefFromURL( + const DatabasePigeonFirebaseApp& app, const std::string& url, + std::function reply)> result) { + Database* database = GetDatabaseFromPigeon(app); + if (!database) { + result(FlutterError("unknown", "Database instance not found")); + return; + } + + DatabaseReference ref = database->GetReferenceFromUrl(url.c_str()); + + std::string ref_path; + std::string ref_url = ref.url(); + auto pos = ref_url.find(".com/"); + if (pos != std::string::npos) { + ref_path = ref_url.substr(pos + 4); + } else { + ref_path = "/"; + } + + result(DatabaseReferencePlatform(ref_path)); +} + +void FirebaseDatabasePlugin::PurgeOutstandingWrites( + const DatabasePigeonFirebaseApp& app, + std::function reply)> result) { + Database* database = GetDatabaseFromPigeon(app); + if (!database) { + result(FlutterError("unknown", "Database instance not found")); + return; + } + database->PurgeOutstandingWrites(); + result(std::nullopt); +} + +// ===== DatabaseReference methods ===== + +void FirebaseDatabasePlugin::DatabaseReferenceSet( + const DatabasePigeonFirebaseApp& app, + const DatabaseReferenceRequest& request, + std::function reply)> result) { + Database* database = GetDatabaseFromPigeon(app); + if (!database) { + result(FlutterError("unknown", "Database instance not found")); + return; + } + + DatabaseReference ref = database->GetReference(request.path().c_str()); + Variant value = request.value() ? EncodableValueToVariant(*request.value()) + : Variant::Null(); + + ref.SetValue(value).OnCompletion([result](const Future& future) { + if (future.error() == Error::kErrorNone) { + result(std::nullopt); + } else { + result(FirebaseDatabasePlugin::ParseError(future)); + } + }); +} + +void FirebaseDatabasePlugin::DatabaseReferenceSetWithPriority( + const DatabasePigeonFirebaseApp& app, + const DatabaseReferenceRequest& request, + std::function reply)> result) { + Database* database = GetDatabaseFromPigeon(app); + if (!database) { + result(FlutterError("unknown", "Database instance not found")); + return; + } + + DatabaseReference ref = database->GetReference(request.path().c_str()); + Variant value = request.value() ? EncodableValueToVariant(*request.value()) + : Variant::Null(); + Variant priority = request.priority() + ? EncodableValueToVariant(*request.priority()) + : Variant::Null(); + + ref.SetValueAndPriority(value, priority) + .OnCompletion([result](const Future& future) { + if (future.error() == Error::kErrorNone) { + result(std::nullopt); + } else { + result(FirebaseDatabasePlugin::ParseError(future)); + } + }); +} + +void FirebaseDatabasePlugin::DatabaseReferenceUpdate( + const DatabasePigeonFirebaseApp& app, const UpdateRequest& request, + std::function reply)> result) { + Database* database = GetDatabaseFromPigeon(app); + if (!database) { + result(FlutterError("unknown", "Database instance not found")); + return; + } + + DatabaseReference ref = database->GetReference(request.path().c_str()); + Variant values = EncodableValueToVariant(EncodableValue(request.value())); + + ref.UpdateChildren(values).OnCompletion([result](const Future& future) { + if (future.error() == Error::kErrorNone) { + result(std::nullopt); + } else { + result(FirebaseDatabasePlugin::ParseError(future)); + } + }); +} + +void FirebaseDatabasePlugin::DatabaseReferenceSetPriority( + const DatabasePigeonFirebaseApp& app, + const DatabaseReferenceRequest& request, + std::function reply)> result) { + Database* database = GetDatabaseFromPigeon(app); + if (!database) { + result(FlutterError("unknown", "Database instance not found")); + return; + } + + DatabaseReference ref = database->GetReference(request.path().c_str()); + Variant priority = request.priority() + ? EncodableValueToVariant(*request.priority()) + : Variant::Null(); + + ref.SetPriority(priority).OnCompletion([result](const Future& future) { + if (future.error() == Error::kErrorNone) { + result(std::nullopt); + } else { + result(FirebaseDatabasePlugin::ParseError(future)); + } + }); +} + +void FirebaseDatabasePlugin::DatabaseReferenceRunTransaction( + const DatabasePigeonFirebaseApp& app, const TransactionRequest& request, + std::function reply)> result) { + Database* database = GetDatabaseFromPigeon(app); + if (!database) { + result(FlutterError("unknown", "Database instance not found")); + return; + } + + DatabaseReference ref = database->GetReference(request.path().c_str()); + int64_t transaction_key = request.transaction_key(); + bool apply_locally = request.apply_locally(); + + struct TransactionContext { + flutter::BinaryMessenger* messenger; + int64_t transaction_key; + std::map* transaction_results; + std::function reply)> result; + }; + + auto* ctx = new TransactionContext{messenger_, transaction_key, + &transaction_results_, result}; + + ref.RunTransaction( + [](MutableData* data, + void* context) -> firebase::database::TransactionResult { + auto* ctx = static_cast(context); + + // Convert current data to EncodableValue + Variant current_value = data->value(); + EncodableValue snapshot_value = + FirebaseDatabasePlugin::VariantToEncodableValue(current_value); + + // Call the Flutter transaction handler synchronously using a semaphore + std::mutex mtx; + std::condition_variable cv; + bool handler_complete = false; + TransactionHandlerResult* handler_result = nullptr; + + auto flutter_api = + std::make_unique(ctx->messenger); + + const EncodableValue* snapshot_ptr = + std::holds_alternative(snapshot_value) + ? nullptr + : &snapshot_value; + + flutter_api->CallTransactionHandler( + ctx->transaction_key, snapshot_ptr, + [&](const TransactionHandlerResult& result) { + handler_result = new TransactionHandlerResult( + result.value(), result.aborted(), result.exception()); + std::lock_guard lock(mtx); + handler_complete = true; + cv.notify_one(); + }, + [&](const FlutterError& error) { + handler_result = new TransactionHandlerResult(true, true); + std::lock_guard lock(mtx); + handler_complete = true; + cv.notify_one(); + }); + + // Wait for the Flutter callback to complete + { + std::unique_lock lock(mtx); + cv.wait(lock, [&] { return handler_complete; }); + } + + if (!handler_result || handler_result->aborted() || + handler_result->exception()) { + delete handler_result; + return firebase::database::kTransactionResultAbort; + } + + // Apply the result value + if (handler_result->value()) { + Variant new_value = FirebaseDatabasePlugin::EncodableValueToVariant( + *handler_result->value()); + data->set_value(new_value); + } else { + data->set_value(Variant::Null()); + } + + delete handler_result; + return firebase::database::kTransactionResultSuccess; + }, + ctx, apply_locally); + + // Wait for the transaction to complete + ref.RunTransactionLastResult().OnCompletion( + [ctx](const Future& future) { + if (future.error() == Error::kErrorNone) { + const DataSnapshot* snapshot = future.result(); + EncodableMap result_map; + result_map[EncodableValue("committed")] = EncodableValue(true); + if (snapshot) { + result_map[EncodableValue("snapshot")] = EncodableValue( + FirebaseDatabasePlugin::DataSnapshotToEncodableMap(*snapshot)); + } else { + result_map[EncodableValue("snapshot")] = EncodableValue(); + } + (*ctx->transaction_results)[ctx->transaction_key] = result_map; + ctx->result(std::nullopt); + } else { + // Transaction failed but may have been aborted + EncodableMap result_map; + result_map[EncodableValue("committed")] = EncodableValue(false); + result_map[EncodableValue("snapshot")] = EncodableValue(EncodableMap{ + {EncodableValue("key"), EncodableValue()}, + {EncodableValue("value"), EncodableValue()}, + {EncodableValue("priority"), EncodableValue()}, + {EncodableValue("childKeys"), EncodableValue(EncodableList{})}, + }); + (*ctx->transaction_results)[ctx->transaction_key] = result_map; + + if (static_cast(future.error()) == + Error::kErrorTransactionAbortedByUser) { + // Aborted by user is not an error condition + ctx->result(std::nullopt); + } else { + ctx->result(FirebaseDatabasePlugin::ParseError(future)); + } + } + delete ctx; + }); +} + +void FirebaseDatabasePlugin::DatabaseReferenceGetTransactionResult( + const DatabasePigeonFirebaseApp& app, int64_t transaction_key, + std::function reply)> result) { + auto it = transaction_results_.find(transaction_key); + if (it != transaction_results_.end()) { + result(it->second); + transaction_results_.erase(it); + } else { + // Return default result + EncodableMap default_result; + default_result[EncodableValue("committed")] = EncodableValue(false); + default_result[EncodableValue("snapshot")] = EncodableValue(EncodableMap{ + {EncodableValue("key"), EncodableValue()}, + {EncodableValue("value"), EncodableValue()}, + {EncodableValue("priority"), EncodableValue()}, + {EncodableValue("childKeys"), EncodableValue(EncodableList{})}, + }); + result(default_result); + } +} + +// ===== OnDisconnect methods ===== + +void FirebaseDatabasePlugin::OnDisconnectSet( + const DatabasePigeonFirebaseApp& app, + const DatabaseReferenceRequest& request, + std::function reply)> result) { + Database* database = GetDatabaseFromPigeon(app); + if (!database) { + result(FlutterError("unknown", "Database instance not found")); + return; + } + + DatabaseReference ref = database->GetReference(request.path().c_str()); + Variant value = request.value() ? EncodableValueToVariant(*request.value()) + : Variant::Null(); + + ref.OnDisconnect()->SetValue(value).OnCompletion( + [result](const Future& future) { + if (future.error() == Error::kErrorNone) { + result(std::nullopt); + } else { + result(FirebaseDatabasePlugin::ParseError(future)); + } + }); +} + +void FirebaseDatabasePlugin::OnDisconnectSetWithPriority( + const DatabasePigeonFirebaseApp& app, + const DatabaseReferenceRequest& request, + std::function reply)> result) { + Database* database = GetDatabaseFromPigeon(app); + if (!database) { + result(FlutterError("unknown", "Database instance not found")); + return; + } + + DatabaseReference ref = database->GetReference(request.path().c_str()); + Variant value = request.value() ? EncodableValueToVariant(*request.value()) + : Variant::Null(); + Variant priority = request.priority() + ? EncodableValueToVariant(*request.priority()) + : Variant::Null(); + + ref.OnDisconnect() + ->SetValueAndPriority(value, priority) + .OnCompletion([result](const Future& future) { + if (future.error() == Error::kErrorNone) { + result(std::nullopt); + } else { + result(FirebaseDatabasePlugin::ParseError(future)); + } + }); +} + +void FirebaseDatabasePlugin::OnDisconnectUpdate( + const DatabasePigeonFirebaseApp& app, const UpdateRequest& request, + std::function reply)> result) { + Database* database = GetDatabaseFromPigeon(app); + if (!database) { + result(FlutterError("unknown", "Database instance not found")); + return; + } + + DatabaseReference ref = database->GetReference(request.path().c_str()); + Variant values = EncodableValueToVariant(EncodableValue(request.value())); + + ref.OnDisconnect()->UpdateChildren(values).OnCompletion( + [result](const Future& future) { + if (future.error() == Error::kErrorNone) { + result(std::nullopt); + } else { + result(FirebaseDatabasePlugin::ParseError(future)); + } + }); +} + +void FirebaseDatabasePlugin::OnDisconnectCancel( + const DatabasePigeonFirebaseApp& app, const std::string& path, + std::function reply)> result) { + Database* database = GetDatabaseFromPigeon(app); + if (!database) { + result(FlutterError("unknown", "Database instance not found")); + return; + } + + DatabaseReference ref = database->GetReference(path.c_str()); + + ref.OnDisconnect()->Cancel().OnCompletion( + [result](const Future& future) { + if (future.error() == Error::kErrorNone) { + result(std::nullopt); + } else { + result(FirebaseDatabasePlugin::ParseError(future)); + } + }); +} + +// ===== Query methods ===== + +void FirebaseDatabasePlugin::QueryObserve( + const DatabasePigeonFirebaseApp& app, const QueryRequest& request, + std::function reply)> result) { + Database* database = GetDatabaseFromPigeon(app); + if (!database) { + result(FlutterError("unknown", "Database instance not found")); + return; + } + + DatabaseReference ref = database->GetReference(request.path().c_str()); + firebase::database::Query query = + ApplyQueryModifiers(ref, request.modifiers()); + + // The event type will be passed as an argument when the Dart side calls + // listen on the EventChannel. We need to create the appropriate handler. + // Since we don't know the event type here, we create both a value and child + // handler based on a shared approach: the Dart side passes eventType as an + // argument to the EventChannel's listen call. + + // We use a generic approach: create one handler that reads the eventType + // from the listen arguments. + class DatabaseGenericStreamHandler + : public flutter::StreamHandler { + public: + DatabaseGenericStreamHandler(firebase::database::Query query) + : query_(query), value_listener_(nullptr), child_listener_(nullptr) {} + + ~DatabaseGenericStreamHandler() override { + // Remove listeners before deleting to avoid dangling pointers in the + // Database's internal listener list. Query::RemoveXxxListener() checks + // if (internal_) first, so this is a safe no-op if the Database was + // already destroyed (the cleanup mechanism nullifies internal_). + if (value_listener_) { + query_.RemoveValueListener(value_listener_); + delete value_listener_; + value_listener_ = nullptr; + } + if (child_listener_) { + query_.RemoveChildListener(child_listener_); + delete child_listener_; + child_listener_ = nullptr; + } + } + + protected: + std::unique_ptr> + OnListenInternal( + const flutter::EncodableValue* arguments, + std::unique_ptr>&& events) + override { + events_ = std::move(events); + + // Extract eventType from arguments + std::string event_type = "value"; + if (arguments && std::holds_alternative(*arguments)) { + const auto& args_map = std::get(*arguments); + auto it = args_map.find(EncodableValue("eventType")); + if (it != args_map.end() && + std::holds_alternative(it->second)) { + event_type = std::get(it->second); + } + } + + if (event_type == "value") { + // Value listener + class VL : public firebase::database::ValueListener { + public: + VL(flutter::EventSink* events) + : events_(events) {} + void OnValueChanged(const DataSnapshot& snapshot) override { + EncodableMap event; + event[EncodableValue("eventType")] = EncodableValue("value"); + event[EncodableValue("previousChildKey")] = EncodableValue(); + event[EncodableValue("snapshot")] = EncodableValue( + FirebaseDatabasePlugin::DataSnapshotToEncodableMap(snapshot)); + events_->Success(EncodableValue(event)); + } + void OnCancelled(const Error& error, + const char* error_message) override { + events_->Error(FirebaseDatabasePlugin::GetDatabaseErrorCode(error), + error_message ? error_message : "Unknown error"); + } + + private: + flutter::EventSink* events_; + }; + value_listener_ = new VL(events_.get()); + query_.AddValueListener(value_listener_); + } else { + // Child listener + class CL : public firebase::database::ChildListener { + public: + CL(flutter::EventSink* events, + const std::string& event_type) + : events_(events), event_type_(event_type) {} + void OnChildAdded(const DataSnapshot& snapshot, + const char* prev) override { + if (event_type_ == "childAdded") Send("childAdded", snapshot, prev); + } + void OnChildChanged(const DataSnapshot& snapshot, + const char* prev) override { + if (event_type_ == "childChanged") + Send("childChanged", snapshot, prev); + } + void OnChildMoved(const DataSnapshot& snapshot, + const char* prev) override { + if (event_type_ == "childMoved") Send("childMoved", snapshot, prev); + } + void OnChildRemoved(const DataSnapshot& snapshot) override { + if (event_type_ == "childRemoved") + Send("childRemoved", snapshot, nullptr); + } + void OnCancelled(const Error& error, + const char* error_message) override { + events_->Error(FirebaseDatabasePlugin::GetDatabaseErrorCode(error), + error_message ? error_message : "Unknown error"); + } + + private: + void Send(const std::string& type, const DataSnapshot& snapshot, + const char* prev) { + EncodableMap event; + event[EncodableValue("eventType")] = EncodableValue(type); + event[EncodableValue("previousChildKey")] = + prev ? EncodableValue(std::string(prev)) : EncodableValue(); + event[EncodableValue("snapshot")] = EncodableValue( + FirebaseDatabasePlugin::DataSnapshotToEncodableMap(snapshot)); + events_->Success(EncodableValue(event)); + } + flutter::EventSink* events_; + std::string event_type_; + }; + child_listener_ = new CL(events_.get(), event_type); + query_.AddChildListener(child_listener_); + } + + return nullptr; + } + + std::unique_ptr> + OnCancelInternal(const flutter::EncodableValue* arguments) override { + if (value_listener_) { + query_.RemoveValueListener(value_listener_); + delete value_listener_; + value_listener_ = nullptr; + } + if (child_listener_) { + query_.RemoveChildListener(child_listener_); + delete child_listener_; + child_listener_ = nullptr; + } + if (events_) { + events_->EndOfStream(); + } + return nullptr; + } + + private: + firebase::database::Query query_; + firebase::database::ValueListener* value_listener_; + firebase::database::ChildListener* child_listener_; + std::unique_ptr> events_; + }; + + auto handler = std::make_unique(query); + std::string channelName = RegisterEventChannel( + "plugins.flutter.io/firebase_database/query/", std::move(handler)); + + result(channelName); +} + +void FirebaseDatabasePlugin::QueryKeepSynced( + const DatabasePigeonFirebaseApp& app, const QueryRequest& request, + std::function reply)> result) { + Database* database = GetDatabaseFromPigeon(app); + if (!database) { + result(FlutterError("unknown", "Database instance not found")); + return; + } + + DatabaseReference ref = database->GetReference(request.path().c_str()); + firebase::database::Query query = + ApplyQueryModifiers(ref, request.modifiers()); + + bool keep_synced = request.value() ? *request.value() : false; + query.SetKeepSynchronized(keep_synced); + result(std::nullopt); +} + +void FirebaseDatabasePlugin::QueryGet( + const DatabasePigeonFirebaseApp& app, const QueryRequest& request, + std::function reply)> result) { + Database* database = GetDatabaseFromPigeon(app); + if (!database) { + result(FlutterError("unknown", "Database instance not found")); + return; + } + + DatabaseReference ref = database->GetReference(request.path().c_str()); + firebase::database::Query query = + ApplyQueryModifiers(ref, request.modifiers()); + + query.GetValue().OnCompletion([result](const Future& future) { + if (future.error() == Error::kErrorNone) { + const DataSnapshot* snapshot = future.result(); + EncodableMap result_map; + if (snapshot) { + result_map[EncodableValue("snapshot")] = EncodableValue( + FirebaseDatabasePlugin::DataSnapshotToEncodableMap(*snapshot)); + } else { + result_map[EncodableValue("snapshot")] = EncodableValue(); + } + result(result_map); + } else { + result(FirebaseDatabasePlugin::ParseError(future)); + } + }); +} + +} // namespace firebase_database_windows diff --git a/packages/firebase_database/firebase_database/windows/firebase_database_plugin.h b/packages/firebase_database/firebase_database/windows/firebase_database_plugin.h new file mode 100644 index 000000000000..6a6fa145f6e0 --- /dev/null +++ b/packages/firebase_database/firebase_database/windows/firebase_database_plugin.h @@ -0,0 +1,144 @@ +/* + * Copyright 2025, the Chromium project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +#ifndef FLUTTER_PLUGIN_FIREBASE_DATABASE_PLUGIN_H_ +#define FLUTTER_PLUGIN_FIREBASE_DATABASE_PLUGIN_H_ + +#include +#include +#include + +#include + +#include "firebase/database.h" +#include "firebase/database/common.h" +#include "firebase/database/data_snapshot.h" +#include "messages.g.h" + +namespace firebase_database_windows { + +class FirebaseDatabasePlugin : public flutter::Plugin, + public FirebaseDatabaseHostApi { + public: + static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar); + + FirebaseDatabasePlugin(); + + virtual ~FirebaseDatabasePlugin(); + + // Disallow copy and assign. + FirebaseDatabasePlugin(const FirebaseDatabasePlugin&) = delete; + FirebaseDatabasePlugin& operator=(const FirebaseDatabasePlugin&) = delete; + + // Helper functions + static flutter::EncodableValue VariantToEncodableValue( + const firebase::Variant& variant); + static firebase::Variant EncodableValueToVariant( + const flutter::EncodableValue& value); + static std::string GetDatabaseErrorCode(firebase::database::Error error); + static std::string GetDatabaseErrorMessage(firebase::database::Error error); + static FlutterError ParseError(const firebase::FutureBase& future); + static flutter::EncodableMap DataSnapshotToEncodableMap( + const firebase::database::DataSnapshot& snapshot); + + // FirebaseDatabaseHostApi methods + void GoOnline( + const DatabasePigeonFirebaseApp& app, + std::function reply)> result) override; + void GoOffline( + const DatabasePigeonFirebaseApp& app, + std::function reply)> result) override; + void SetPersistenceEnabled( + const DatabasePigeonFirebaseApp& app, bool enabled, + std::function reply)> result) override; + void SetPersistenceCacheSizeBytes( + const DatabasePigeonFirebaseApp& app, int64_t cache_size, + std::function reply)> result) override; + void SetLoggingEnabled( + const DatabasePigeonFirebaseApp& app, bool enabled, + std::function reply)> result) override; + void UseDatabaseEmulator( + const DatabasePigeonFirebaseApp& app, const std::string& host, + int64_t port, + std::function reply)> result) override; + void Ref(const DatabasePigeonFirebaseApp& app, const std::string* path, + std::function reply)> result) + override; + void RefFromURL(const DatabasePigeonFirebaseApp& app, const std::string& url, + std::function reply)> + result) override; + void PurgeOutstandingWrites( + const DatabasePigeonFirebaseApp& app, + std::function reply)> result) override; + void DatabaseReferenceSet( + const DatabasePigeonFirebaseApp& app, + const DatabaseReferenceRequest& request, + std::function reply)> result) override; + void DatabaseReferenceSetWithPriority( + const DatabasePigeonFirebaseApp& app, + const DatabaseReferenceRequest& request, + std::function reply)> result) override; + void DatabaseReferenceUpdate( + const DatabasePigeonFirebaseApp& app, const UpdateRequest& request, + std::function reply)> result) override; + void DatabaseReferenceSetPriority( + const DatabasePigeonFirebaseApp& app, + const DatabaseReferenceRequest& request, + std::function reply)> result) override; + void DatabaseReferenceRunTransaction( + const DatabasePigeonFirebaseApp& app, const TransactionRequest& request, + std::function reply)> result) override; + void DatabaseReferenceGetTransactionResult( + const DatabasePigeonFirebaseApp& app, int64_t transaction_key, + std::function reply)> result) + override; + void OnDisconnectSet( + const DatabasePigeonFirebaseApp& app, + const DatabaseReferenceRequest& request, + std::function reply)> result) override; + void OnDisconnectSetWithPriority( + const DatabasePigeonFirebaseApp& app, + const DatabaseReferenceRequest& request, + std::function reply)> result) override; + void OnDisconnectUpdate( + const DatabasePigeonFirebaseApp& app, const UpdateRequest& request, + std::function reply)> result) override; + void OnDisconnectCancel( + const DatabasePigeonFirebaseApp& app, const std::string& path, + std::function reply)> result) override; + void QueryObserve( + const DatabasePigeonFirebaseApp& app, const QueryRequest& request, + std::function reply)> result) override; + void QueryKeepSynced( + const DatabasePigeonFirebaseApp& app, const QueryRequest& request, + std::function reply)> result) override; + void QueryGet(const DatabasePigeonFirebaseApp& app, + const QueryRequest& request, + std::function reply)> + result) override; + + static flutter::BinaryMessenger* messenger_; + static std::map< + std::string, + std::unique_ptr>> + event_channels_; + static std::map>> + stream_handlers_; + static std::map + database_instances_; + + private: + firebase::database::Database* GetDatabaseFromPigeon( + const DatabasePigeonFirebaseApp& app); + firebase::database::Query ApplyQueryModifiers( + firebase::database::Query query, const flutter::EncodableList& modifiers); + + std::map transaction_results_; +}; + +} // namespace firebase_database_windows + +#endif /* FLUTTER_PLUGIN_FIREBASE_DATABASE_PLUGIN_H_ */ diff --git a/packages/firebase_database/firebase_database/windows/firebase_database_plugin_c_api.cpp b/packages/firebase_database/firebase_database/windows/firebase_database_plugin_c_api.cpp new file mode 100644 index 000000000000..41cf8fae1737 --- /dev/null +++ b/packages/firebase_database/firebase_database/windows/firebase_database_plugin_c_api.cpp @@ -0,0 +1,16 @@ +// Copyright 2025, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#include "include/firebase_database/firebase_database_plugin_c_api.h" + +#include + +#include "firebase_database_plugin.h" + +void FirebaseDatabasePluginCApiRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar) { + firebase_database_windows::FirebaseDatabasePlugin::RegisterWithRegistrar( + flutter::PluginRegistrarManager::GetInstance() + ->GetRegistrar(registrar)); +} diff --git a/packages/firebase_database/firebase_database/windows/include/firebase_database/firebase_database_plugin_c_api.h b/packages/firebase_database/firebase_database/windows/include/firebase_database/firebase_database_plugin_c_api.h new file mode 100644 index 000000000000..f333dc88edf3 --- /dev/null +++ b/packages/firebase_database/firebase_database/windows/include/firebase_database/firebase_database_plugin_c_api.h @@ -0,0 +1,29 @@ +/* + * Copyright 2025, the Chromium project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +#ifndef FLUTTER_PLUGIN_FIREBASE_DATABASE_PLUGIN_C_API_H_ +#define FLUTTER_PLUGIN_FIREBASE_DATABASE_PLUGIN_C_API_H_ + +#include + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) +#else +#define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +FLUTTER_PLUGIN_EXPORT void FirebaseDatabasePluginCApiRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif /* FLUTTER_PLUGIN_FIREBASE_DATABASE_PLUGIN_C_API_H_ */ diff --git a/packages/firebase_database/firebase_database/windows/messages.g.cpp b/packages/firebase_database/firebase_database/windows/messages.g.cpp new file mode 100644 index 000000000000..8c0aeeb54adc --- /dev/null +++ b/packages/firebase_database/firebase_database/windows/messages.g.cpp @@ -0,0 +1,1742 @@ +// Copyright 2025, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +// Autogenerated from Pigeon (v25.3.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +#undef _HAS_EXCEPTIONS + +#include "messages.g.h" + +#include +#include +#include +#include + +#include +#include +#include + +namespace firebase_database_windows { +using flutter::BasicMessageChannel; +using flutter::CustomEncodableValue; +using flutter::EncodableList; +using flutter::EncodableMap; +using flutter::EncodableValue; + +FlutterError CreateConnectionError(const std::string channel_name) { + return FlutterError( + "channel-error", + "Unable to establish connection on channel: '" + channel_name + "'.", + EncodableValue("")); +} + +// DatabasePigeonSettings + +DatabasePigeonSettings::DatabasePigeonSettings() {} + +DatabasePigeonSettings::DatabasePigeonSettings(const bool* persistence_enabled, + const int64_t* cache_size_bytes, + const bool* logging_enabled, + const std::string* emulator_host, + const int64_t* emulator_port) + : persistence_enabled_(persistence_enabled + ? std::optional(*persistence_enabled) + : std::nullopt), + cache_size_bytes_(cache_size_bytes + ? std::optional(*cache_size_bytes) + : std::nullopt), + logging_enabled_(logging_enabled ? std::optional(*logging_enabled) + : std::nullopt), + emulator_host_(emulator_host ? std::optional(*emulator_host) + : std::nullopt), + emulator_port_(emulator_port ? std::optional(*emulator_port) + : std::nullopt) {} + +const bool* DatabasePigeonSettings::persistence_enabled() const { + return persistence_enabled_ ? &(*persistence_enabled_) : nullptr; +} + +void DatabasePigeonSettings::set_persistence_enabled(const bool* value_arg) { + persistence_enabled_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void DatabasePigeonSettings::set_persistence_enabled(bool value_arg) { + persistence_enabled_ = value_arg; +} + +const int64_t* DatabasePigeonSettings::cache_size_bytes() const { + return cache_size_bytes_ ? &(*cache_size_bytes_) : nullptr; +} + +void DatabasePigeonSettings::set_cache_size_bytes(const int64_t* value_arg) { + cache_size_bytes_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void DatabasePigeonSettings::set_cache_size_bytes(int64_t value_arg) { + cache_size_bytes_ = value_arg; +} + +const bool* DatabasePigeonSettings::logging_enabled() const { + return logging_enabled_ ? &(*logging_enabled_) : nullptr; +} + +void DatabasePigeonSettings::set_logging_enabled(const bool* value_arg) { + logging_enabled_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void DatabasePigeonSettings::set_logging_enabled(bool value_arg) { + logging_enabled_ = value_arg; +} + +const std::string* DatabasePigeonSettings::emulator_host() const { + return emulator_host_ ? &(*emulator_host_) : nullptr; +} + +void DatabasePigeonSettings::set_emulator_host( + const std::string_view* value_arg) { + emulator_host_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void DatabasePigeonSettings::set_emulator_host(std::string_view value_arg) { + emulator_host_ = value_arg; +} + +const int64_t* DatabasePigeonSettings::emulator_port() const { + return emulator_port_ ? &(*emulator_port_) : nullptr; +} + +void DatabasePigeonSettings::set_emulator_port(const int64_t* value_arg) { + emulator_port_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void DatabasePigeonSettings::set_emulator_port(int64_t value_arg) { + emulator_port_ = value_arg; +} + +EncodableList DatabasePigeonSettings::ToEncodableList() const { + EncodableList list; + list.reserve(5); + list.push_back(persistence_enabled_ ? EncodableValue(*persistence_enabled_) + : EncodableValue()); + list.push_back(cache_size_bytes_ ? EncodableValue(*cache_size_bytes_) + : EncodableValue()); + list.push_back(logging_enabled_ ? EncodableValue(*logging_enabled_) + : EncodableValue()); + list.push_back(emulator_host_ ? EncodableValue(*emulator_host_) + : EncodableValue()); + list.push_back(emulator_port_ ? EncodableValue(*emulator_port_) + : EncodableValue()); + return list; +} + +DatabasePigeonSettings DatabasePigeonSettings::FromEncodableList( + const EncodableList& list) { + DatabasePigeonSettings decoded; + auto& encodable_persistence_enabled = list[0]; + if (!encodable_persistence_enabled.IsNull()) { + decoded.set_persistence_enabled( + std::get(encodable_persistence_enabled)); + } + auto& encodable_cache_size_bytes = list[1]; + if (!encodable_cache_size_bytes.IsNull()) { + decoded.set_cache_size_bytes(std::get(encodable_cache_size_bytes)); + } + auto& encodable_logging_enabled = list[2]; + if (!encodable_logging_enabled.IsNull()) { + decoded.set_logging_enabled(std::get(encodable_logging_enabled)); + } + auto& encodable_emulator_host = list[3]; + if (!encodable_emulator_host.IsNull()) { + decoded.set_emulator_host(std::get(encodable_emulator_host)); + } + auto& encodable_emulator_port = list[4]; + if (!encodable_emulator_port.IsNull()) { + decoded.set_emulator_port(std::get(encodable_emulator_port)); + } + return decoded; +} + +// DatabasePigeonFirebaseApp + +DatabasePigeonFirebaseApp::DatabasePigeonFirebaseApp( + const std::string& app_name, const DatabasePigeonSettings& settings) + : app_name_(app_name), + settings_(std::make_unique(settings)) {} + +DatabasePigeonFirebaseApp::DatabasePigeonFirebaseApp( + const std::string& app_name, const std::string* database_u_r_l, + const DatabasePigeonSettings& settings) + : app_name_(app_name), + database_u_r_l_(database_u_r_l + ? std::optional(*database_u_r_l) + : std::nullopt), + settings_(std::make_unique(settings)) {} + +DatabasePigeonFirebaseApp::DatabasePigeonFirebaseApp( + const DatabasePigeonFirebaseApp& other) + : app_name_(other.app_name_), + database_u_r_l_(other.database_u_r_l_ + ? std::optional(*other.database_u_r_l_) + : std::nullopt), + settings_(std::make_unique(*other.settings_)) {} + +DatabasePigeonFirebaseApp& DatabasePigeonFirebaseApp::operator=( + const DatabasePigeonFirebaseApp& other) { + app_name_ = other.app_name_; + database_u_r_l_ = other.database_u_r_l_; + settings_ = std::make_unique(*other.settings_); + return *this; +} + +const std::string& DatabasePigeonFirebaseApp::app_name() const { + return app_name_; +} + +void DatabasePigeonFirebaseApp::set_app_name(std::string_view value_arg) { + app_name_ = value_arg; +} + +const std::string* DatabasePigeonFirebaseApp::database_u_r_l() const { + return database_u_r_l_ ? &(*database_u_r_l_) : nullptr; +} + +void DatabasePigeonFirebaseApp::set_database_u_r_l( + const std::string_view* value_arg) { + database_u_r_l_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void DatabasePigeonFirebaseApp::set_database_u_r_l(std::string_view value_arg) { + database_u_r_l_ = value_arg; +} + +const DatabasePigeonSettings& DatabasePigeonFirebaseApp::settings() const { + return *settings_; +} + +void DatabasePigeonFirebaseApp::set_settings( + const DatabasePigeonSettings& value_arg) { + settings_ = std::make_unique(value_arg); +} + +EncodableList DatabasePigeonFirebaseApp::ToEncodableList() const { + EncodableList list; + list.reserve(3); + list.push_back(EncodableValue(app_name_)); + list.push_back(database_u_r_l_ ? EncodableValue(*database_u_r_l_) + : EncodableValue()); + list.push_back(CustomEncodableValue(*settings_)); + return list; +} + +DatabasePigeonFirebaseApp DatabasePigeonFirebaseApp::FromEncodableList( + const EncodableList& list) { + DatabasePigeonFirebaseApp decoded( + std::get(list[0]), + std::any_cast( + std::get(list[2]))); + auto& encodable_database_u_r_l = list[1]; + if (!encodable_database_u_r_l.IsNull()) { + decoded.set_database_u_r_l(std::get(encodable_database_u_r_l)); + } + return decoded; +} + +// DatabaseReferencePlatform + +DatabaseReferencePlatform::DatabaseReferencePlatform(const std::string& path) + : path_(path) {} + +const std::string& DatabaseReferencePlatform::path() const { return path_; } + +void DatabaseReferencePlatform::set_path(std::string_view value_arg) { + path_ = value_arg; +} + +EncodableList DatabaseReferencePlatform::ToEncodableList() const { + EncodableList list; + list.reserve(1); + list.push_back(EncodableValue(path_)); + return list; +} + +DatabaseReferencePlatform DatabaseReferencePlatform::FromEncodableList( + const EncodableList& list) { + DatabaseReferencePlatform decoded(std::get(list[0])); + return decoded; +} + +// DatabaseReferenceRequest + +DatabaseReferenceRequest::DatabaseReferenceRequest(const std::string& path) + : path_(path) {} + +DatabaseReferenceRequest::DatabaseReferenceRequest( + const std::string& path, const EncodableValue* value, + const EncodableValue* priority) + : path_(path), + value_(value ? std::optional(*value) : std::nullopt), + priority_(priority ? std::optional(*priority) + : std::nullopt) {} + +const std::string& DatabaseReferenceRequest::path() const { return path_; } + +void DatabaseReferenceRequest::set_path(std::string_view value_arg) { + path_ = value_arg; +} + +const EncodableValue* DatabaseReferenceRequest::value() const { + return value_ ? &(*value_) : nullptr; +} + +void DatabaseReferenceRequest::set_value(const EncodableValue* value_arg) { + value_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void DatabaseReferenceRequest::set_value(const EncodableValue& value_arg) { + value_ = value_arg; +} + +const EncodableValue* DatabaseReferenceRequest::priority() const { + return priority_ ? &(*priority_) : nullptr; +} + +void DatabaseReferenceRequest::set_priority(const EncodableValue* value_arg) { + priority_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void DatabaseReferenceRequest::set_priority(const EncodableValue& value_arg) { + priority_ = value_arg; +} + +EncodableList DatabaseReferenceRequest::ToEncodableList() const { + EncodableList list; + list.reserve(3); + list.push_back(EncodableValue(path_)); + list.push_back(value_ ? *value_ : EncodableValue()); + list.push_back(priority_ ? *priority_ : EncodableValue()); + return list; +} + +DatabaseReferenceRequest DatabaseReferenceRequest::FromEncodableList( + const EncodableList& list) { + DatabaseReferenceRequest decoded(std::get(list[0])); + auto& encodable_value = list[1]; + if (!encodable_value.IsNull()) { + decoded.set_value(encodable_value); + } + auto& encodable_priority = list[2]; + if (!encodable_priority.IsNull()) { + decoded.set_priority(encodable_priority); + } + return decoded; +} + +// UpdateRequest + +UpdateRequest::UpdateRequest(const std::string& path, const EncodableMap& value) + : path_(path), value_(value) {} + +const std::string& UpdateRequest::path() const { return path_; } + +void UpdateRequest::set_path(std::string_view value_arg) { path_ = value_arg; } + +const EncodableMap& UpdateRequest::value() const { return value_; } + +void UpdateRequest::set_value(const EncodableMap& value_arg) { + value_ = value_arg; +} + +EncodableList UpdateRequest::ToEncodableList() const { + EncodableList list; + list.reserve(2); + list.push_back(EncodableValue(path_)); + list.push_back(EncodableValue(value_)); + return list; +} + +UpdateRequest UpdateRequest::FromEncodableList(const EncodableList& list) { + UpdateRequest decoded(std::get(list[0]), + std::get(list[1])); + return decoded; +} + +// TransactionRequest + +TransactionRequest::TransactionRequest(const std::string& path, + int64_t transaction_key, + bool apply_locally) + : path_(path), + transaction_key_(transaction_key), + apply_locally_(apply_locally) {} + +const std::string& TransactionRequest::path() const { return path_; } + +void TransactionRequest::set_path(std::string_view value_arg) { + path_ = value_arg; +} + +int64_t TransactionRequest::transaction_key() const { return transaction_key_; } + +void TransactionRequest::set_transaction_key(int64_t value_arg) { + transaction_key_ = value_arg; +} + +bool TransactionRequest::apply_locally() const { return apply_locally_; } + +void TransactionRequest::set_apply_locally(bool value_arg) { + apply_locally_ = value_arg; +} + +EncodableList TransactionRequest::ToEncodableList() const { + EncodableList list; + list.reserve(3); + list.push_back(EncodableValue(path_)); + list.push_back(EncodableValue(transaction_key_)); + list.push_back(EncodableValue(apply_locally_)); + return list; +} + +TransactionRequest TransactionRequest::FromEncodableList( + const EncodableList& list) { + TransactionRequest decoded(std::get(list[0]), + std::get(list[1]), + std::get(list[2])); + return decoded; +} + +// QueryRequest + +QueryRequest::QueryRequest(const std::string& path, + const EncodableList& modifiers) + : path_(path), modifiers_(modifiers) {} + +QueryRequest::QueryRequest(const std::string& path, + const EncodableList& modifiers, const bool* value) + : path_(path), + modifiers_(modifiers), + value_(value ? std::optional(*value) : std::nullopt) {} + +const std::string& QueryRequest::path() const { return path_; } + +void QueryRequest::set_path(std::string_view value_arg) { path_ = value_arg; } + +const EncodableList& QueryRequest::modifiers() const { return modifiers_; } + +void QueryRequest::set_modifiers(const EncodableList& value_arg) { + modifiers_ = value_arg; +} + +const bool* QueryRequest::value() const { + return value_ ? &(*value_) : nullptr; +} + +void QueryRequest::set_value(const bool* value_arg) { + value_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void QueryRequest::set_value(bool value_arg) { value_ = value_arg; } + +EncodableList QueryRequest::ToEncodableList() const { + EncodableList list; + list.reserve(3); + list.push_back(EncodableValue(path_)); + list.push_back(EncodableValue(modifiers_)); + list.push_back(value_ ? EncodableValue(*value_) : EncodableValue()); + return list; +} + +QueryRequest QueryRequest::FromEncodableList(const EncodableList& list) { + QueryRequest decoded(std::get(list[0]), + std::get(list[1])); + auto& encodable_value = list[2]; + if (!encodable_value.IsNull()) { + decoded.set_value(std::get(encodable_value)); + } + return decoded; +} + +// TransactionHandlerResult + +TransactionHandlerResult::TransactionHandlerResult(bool aborted, bool exception) + : aborted_(aborted), exception_(exception) {} + +TransactionHandlerResult::TransactionHandlerResult(const EncodableValue* value, + bool aborted, bool exception) + : value_(value ? std::optional(*value) : std::nullopt), + aborted_(aborted), + exception_(exception) {} + +const EncodableValue* TransactionHandlerResult::value() const { + return value_ ? &(*value_) : nullptr; +} + +void TransactionHandlerResult::set_value(const EncodableValue* value_arg) { + value_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void TransactionHandlerResult::set_value(const EncodableValue& value_arg) { + value_ = value_arg; +} + +bool TransactionHandlerResult::aborted() const { return aborted_; } + +void TransactionHandlerResult::set_aborted(bool value_arg) { + aborted_ = value_arg; +} + +bool TransactionHandlerResult::exception() const { return exception_; } + +void TransactionHandlerResult::set_exception(bool value_arg) { + exception_ = value_arg; +} + +EncodableList TransactionHandlerResult::ToEncodableList() const { + EncodableList list; + list.reserve(3); + list.push_back(value_ ? *value_ : EncodableValue()); + list.push_back(EncodableValue(aborted_)); + list.push_back(EncodableValue(exception_)); + return list; +} + +TransactionHandlerResult TransactionHandlerResult::FromEncodableList( + const EncodableList& list) { + TransactionHandlerResult decoded(std::get(list[1]), + std::get(list[2])); + auto& encodable_value = list[0]; + if (!encodable_value.IsNull()) { + decoded.set_value(encodable_value); + } + return decoded; +} + +PigeonInternalCodecSerializer::PigeonInternalCodecSerializer() {} + +EncodableValue PigeonInternalCodecSerializer::ReadValueOfType( + uint8_t type, flutter::ByteStreamReader* stream) const { + switch (type) { + case 129: { + return CustomEncodableValue(DatabasePigeonSettings::FromEncodableList( + std::get(ReadValue(stream)))); + } + case 130: { + return CustomEncodableValue(DatabasePigeonFirebaseApp::FromEncodableList( + std::get(ReadValue(stream)))); + } + case 131: { + return CustomEncodableValue(DatabaseReferencePlatform::FromEncodableList( + std::get(ReadValue(stream)))); + } + case 132: { + return CustomEncodableValue(DatabaseReferenceRequest::FromEncodableList( + std::get(ReadValue(stream)))); + } + case 133: { + return CustomEncodableValue(UpdateRequest::FromEncodableList( + std::get(ReadValue(stream)))); + } + case 134: { + return CustomEncodableValue(TransactionRequest::FromEncodableList( + std::get(ReadValue(stream)))); + } + case 135: { + return CustomEncodableValue(QueryRequest::FromEncodableList( + std::get(ReadValue(stream)))); + } + case 136: { + return CustomEncodableValue(TransactionHandlerResult::FromEncodableList( + std::get(ReadValue(stream)))); + } + default: + return flutter::StandardCodecSerializer::ReadValueOfType(type, stream); + } +} + +void PigeonInternalCodecSerializer::WriteValue( + const EncodableValue& value, flutter::ByteStreamWriter* stream) const { + if (const CustomEncodableValue* custom_value = + std::get_if(&value)) { + if (custom_value->type() == typeid(DatabasePigeonSettings)) { + stream->WriteByte(129); + WriteValue( + EncodableValue(std::any_cast(*custom_value) + .ToEncodableList()), + stream); + return; + } + if (custom_value->type() == typeid(DatabasePigeonFirebaseApp)) { + stream->WriteByte(130); + WriteValue( + EncodableValue(std::any_cast(*custom_value) + .ToEncodableList()), + stream); + return; + } + if (custom_value->type() == typeid(DatabaseReferencePlatform)) { + stream->WriteByte(131); + WriteValue( + EncodableValue(std::any_cast(*custom_value) + .ToEncodableList()), + stream); + return; + } + if (custom_value->type() == typeid(DatabaseReferenceRequest)) { + stream->WriteByte(132); + WriteValue( + EncodableValue(std::any_cast(*custom_value) + .ToEncodableList()), + stream); + return; + } + if (custom_value->type() == typeid(UpdateRequest)) { + stream->WriteByte(133); + WriteValue( + EncodableValue( + std::any_cast(*custom_value).ToEncodableList()), + stream); + return; + } + if (custom_value->type() == typeid(TransactionRequest)) { + stream->WriteByte(134); + WriteValue(EncodableValue(std::any_cast(*custom_value) + .ToEncodableList()), + stream); + return; + } + if (custom_value->type() == typeid(QueryRequest)) { + stream->WriteByte(135); + WriteValue( + EncodableValue( + std::any_cast(*custom_value).ToEncodableList()), + stream); + return; + } + if (custom_value->type() == typeid(TransactionHandlerResult)) { + stream->WriteByte(136); + WriteValue( + EncodableValue(std::any_cast(*custom_value) + .ToEncodableList()), + stream); + return; + } + } + flutter::StandardCodecSerializer::WriteValue(value, stream); +} + +/// The codec used by FirebaseDatabaseHostApi. +const flutter::StandardMessageCodec& FirebaseDatabaseHostApi::GetCodec() { + return flutter::StandardMessageCodec::GetInstance( + &PigeonInternalCodecSerializer::GetInstance()); +} + +// Sets up an instance of `FirebaseDatabaseHostApi` to handle messages through +// the `binary_messenger`. +void FirebaseDatabaseHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, + FirebaseDatabaseHostApi* api) { + FirebaseDatabaseHostApi::SetUp(binary_messenger, api, ""); +} + +void FirebaseDatabaseHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, + FirebaseDatabaseHostApi* api, + const std::string& message_channel_suffix) { + const std::string prepended_suffix = + message_channel_suffix.length() > 0 + ? std::string(".") + message_channel_suffix + : ""; + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.goOnline" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + api->GoOnline(app_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.goOffline" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + api->GoOffline(app_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.setPersistenceEnabled" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_enabled_arg = args.at(1); + if (encodable_enabled_arg.IsNull()) { + reply(WrapError("enabled_arg unexpectedly null.")); + return; + } + const auto& enabled_arg = std::get(encodable_enabled_arg); + api->SetPersistenceEnabled( + app_arg, enabled_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.setPersistenceCacheSizeBytes" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_cache_size_arg = args.at(1); + if (encodable_cache_size_arg.IsNull()) { + reply(WrapError("cache_size_arg unexpectedly null.")); + return; + } + const int64_t cache_size_arg = + encodable_cache_size_arg.LongValue(); + api->SetPersistenceCacheSizeBytes( + app_arg, cache_size_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.setLoggingEnabled" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_enabled_arg = args.at(1); + if (encodable_enabled_arg.IsNull()) { + reply(WrapError("enabled_arg unexpectedly null.")); + return; + } + const auto& enabled_arg = std::get(encodable_enabled_arg); + api->SetLoggingEnabled( + app_arg, enabled_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.useDatabaseEmulator" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_host_arg = args.at(1); + if (encodable_host_arg.IsNull()) { + reply(WrapError("host_arg unexpectedly null.")); + return; + } + const auto& host_arg = std::get(encodable_host_arg); + const auto& encodable_port_arg = args.at(2); + if (encodable_port_arg.IsNull()) { + reply(WrapError("port_arg unexpectedly null.")); + return; + } + const int64_t port_arg = encodable_port_arg.LongValue(); + api->UseDatabaseEmulator( + app_arg, host_arg, port_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.ref" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_path_arg = args.at(1); + const auto* path_arg = + std::get_if(&encodable_path_arg); + api->Ref(app_arg, path_arg, + [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back(CustomEncodableValue( + std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.refFromURL" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_url_arg = args.at(1); + if (encodable_url_arg.IsNull()) { + reply(WrapError("url_arg unexpectedly null.")); + return; + } + const auto& url_arg = std::get(encodable_url_arg); + api->RefFromURL( + app_arg, url_arg, + [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back( + CustomEncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.purgeOutstandingWrites" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + api->PurgeOutstandingWrites( + app_arg, [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.databaseReferenceSet" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_request_arg = args.at(1); + if (encodable_request_arg.IsNull()) { + reply(WrapError("request_arg unexpectedly null.")); + return; + } + const auto& request_arg = + std::any_cast( + std::get(encodable_request_arg)); + api->DatabaseReferenceSet( + app_arg, request_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.databaseReferenceSetWithPriority" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_request_arg = args.at(1); + if (encodable_request_arg.IsNull()) { + reply(WrapError("request_arg unexpectedly null.")); + return; + } + const auto& request_arg = + std::any_cast( + std::get(encodable_request_arg)); + api->DatabaseReferenceSetWithPriority( + app_arg, request_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.databaseReferenceUpdate" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_request_arg = args.at(1); + if (encodable_request_arg.IsNull()) { + reply(WrapError("request_arg unexpectedly null.")); + return; + } + const auto& request_arg = std::any_cast( + std::get(encodable_request_arg)); + api->DatabaseReferenceUpdate( + app_arg, request_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.databaseReferenceSetPriority" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_request_arg = args.at(1); + if (encodable_request_arg.IsNull()) { + reply(WrapError("request_arg unexpectedly null.")); + return; + } + const auto& request_arg = + std::any_cast( + std::get(encodable_request_arg)); + api->DatabaseReferenceSetPriority( + app_arg, request_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.databaseReferenceRunTransaction" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_request_arg = args.at(1); + if (encodable_request_arg.IsNull()) { + reply(WrapError("request_arg unexpectedly null.")); + return; + } + const auto& request_arg = + std::any_cast( + std::get(encodable_request_arg)); + api->DatabaseReferenceRunTransaction( + app_arg, request_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.databaseReferenceGetTransactionResult" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_transaction_key_arg = args.at(1); + if (encodable_transaction_key_arg.IsNull()) { + reply(WrapError("transaction_key_arg unexpectedly null.")); + return; + } + const int64_t transaction_key_arg = + encodable_transaction_key_arg.LongValue(); + api->DatabaseReferenceGetTransactionResult( + app_arg, transaction_key_arg, + [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back( + EncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.onDisconnectSet" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_request_arg = args.at(1); + if (encodable_request_arg.IsNull()) { + reply(WrapError("request_arg unexpectedly null.")); + return; + } + const auto& request_arg = + std::any_cast( + std::get(encodable_request_arg)); + api->OnDisconnectSet( + app_arg, request_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.onDisconnectSetWithPriority" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_request_arg = args.at(1); + if (encodable_request_arg.IsNull()) { + reply(WrapError("request_arg unexpectedly null.")); + return; + } + const auto& request_arg = + std::any_cast( + std::get(encodable_request_arg)); + api->OnDisconnectSetWithPriority( + app_arg, request_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.onDisconnectUpdate" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_request_arg = args.at(1); + if (encodable_request_arg.IsNull()) { + reply(WrapError("request_arg unexpectedly null.")); + return; + } + const auto& request_arg = std::any_cast( + std::get(encodable_request_arg)); + api->OnDisconnectUpdate( + app_arg, request_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.onDisconnectCancel" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_path_arg = args.at(1); + if (encodable_path_arg.IsNull()) { + reply(WrapError("path_arg unexpectedly null.")); + return; + } + const auto& path_arg = std::get(encodable_path_arg); + api->OnDisconnectCancel( + app_arg, path_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.queryObserve" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_request_arg = args.at(1); + if (encodable_request_arg.IsNull()) { + reply(WrapError("request_arg unexpectedly null.")); + return; + } + const auto& request_arg = std::any_cast( + std::get(encodable_request_arg)); + api->QueryObserve( + app_arg, request_arg, [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back( + EncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.queryKeepSynced" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_request_arg = args.at(1); + if (encodable_request_arg.IsNull()) { + reply(WrapError("request_arg unexpectedly null.")); + return; + } + const auto& request_arg = std::any_cast( + std::get(encodable_request_arg)); + api->QueryKeepSynced( + app_arg, request_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseHostApi.queryGet" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_request_arg = args.at(1); + if (encodable_request_arg.IsNull()) { + reply(WrapError("request_arg unexpectedly null.")); + return; + } + const auto& request_arg = std::any_cast( + std::get(encodable_request_arg)); + api->QueryGet(app_arg, request_arg, + [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue( + std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } +} + +EncodableValue FirebaseDatabaseHostApi::WrapError( + std::string_view error_message) { + return EncodableValue( + EncodableList{EncodableValue(std::string(error_message)), + EncodableValue("Error"), EncodableValue()}); +} + +EncodableValue FirebaseDatabaseHostApi::WrapError(const FlutterError& error) { + return EncodableValue(EncodableList{EncodableValue(error.code()), + EncodableValue(error.message()), + error.details()}); +} + +// Generated class from Pigeon that represents Flutter messages that can be +// called from C++. +FirebaseDatabaseFlutterApi::FirebaseDatabaseFlutterApi( + flutter::BinaryMessenger* binary_messenger) + : binary_messenger_(binary_messenger), message_channel_suffix_("") {} + +FirebaseDatabaseFlutterApi::FirebaseDatabaseFlutterApi( + flutter::BinaryMessenger* binary_messenger, + const std::string& message_channel_suffix) + : binary_messenger_(binary_messenger), + message_channel_suffix_(message_channel_suffix.length() > 0 + ? std::string(".") + message_channel_suffix + : "") {} + +const flutter::StandardMessageCodec& FirebaseDatabaseFlutterApi::GetCodec() { + return flutter::StandardMessageCodec::GetInstance( + &PigeonInternalCodecSerializer::GetInstance()); +} + +void FirebaseDatabaseFlutterApi::CallTransactionHandler( + int64_t transaction_key_arg, const EncodableValue* snapshot_value_arg, + std::function&& on_success, + std::function&& on_error) { + const std::string channel_name = + "dev.flutter.pigeon.firebase_database_platform_interface." + "FirebaseDatabaseFlutterApi.callTransactionHandler" + + message_channel_suffix_; + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); + EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ + EncodableValue(transaction_key_arg), + snapshot_value_arg ? *snapshot_value_arg : EncodableValue(), + }); + channel.Send( + encoded_api_arguments, [channel_name, on_success = std::move(on_success), + on_error = std::move(on_error)]( + const uint8_t* reply, size_t reply_size) { + std::unique_ptr response = + GetCodec().DecodeMessage(reply, reply_size); + const auto& encodable_return_value = *response; + const auto* list_return_value = + std::get_if(&encodable_return_value); + if (list_return_value) { + if (list_return_value->size() > 1) { + on_error( + FlutterError(std::get(list_return_value->at(0)), + std::get(list_return_value->at(1)), + list_return_value->at(2))); + } else { + const auto& return_value = + std::any_cast( + std::get(list_return_value->at(0))); + on_success(return_value); + } + } else { + on_error(CreateConnectionError(channel_name)); + } + }); +} + +} // namespace firebase_database_windows diff --git a/packages/firebase_database/firebase_database/windows/messages.g.h b/packages/firebase_database/firebase_database/windows/messages.g.h new file mode 100644 index 000000000000..0a44f40b593f --- /dev/null +++ b/packages/firebase_database/firebase_database/windows/messages.g.h @@ -0,0 +1,449 @@ +// Copyright 2025, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +// Autogenerated from Pigeon (v25.3.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +#ifndef PIGEON_MESSAGES_G_H_ +#define PIGEON_MESSAGES_G_H_ +#include +#include +#include +#include + +#include +#include +#include + +namespace firebase_database_windows { + +// Generated class from Pigeon. + +class FlutterError { + public: + explicit FlutterError(const std::string& code) : code_(code) {} + explicit FlutterError(const std::string& code, const std::string& message) + : code_(code), message_(message) {} + explicit FlutterError(const std::string& code, const std::string& message, + const flutter::EncodableValue& details) + : code_(code), message_(message), details_(details) {} + + const std::string& code() const { return code_; } + const std::string& message() const { return message_; } + const flutter::EncodableValue& details() const { return details_; } + + private: + std::string code_; + std::string message_; + flutter::EncodableValue details_; +}; + +template +class ErrorOr { + public: + ErrorOr(const T& rhs) : v_(rhs) {} + ErrorOr(const T&& rhs) : v_(std::move(rhs)) {} + ErrorOr(const FlutterError& rhs) : v_(rhs) {} + ErrorOr(const FlutterError&& rhs) : v_(std::move(rhs)) {} + + bool has_error() const { return std::holds_alternative(v_); } + const T& value() const { return std::get(v_); }; + const FlutterError& error() const { return std::get(v_); }; + + private: + friend class FirebaseDatabaseHostApi; + friend class FirebaseDatabaseFlutterApi; + ErrorOr() = default; + T TakeValue() && { return std::get(std::move(v_)); } + + std::variant v_; +}; + +// Generated class from Pigeon that represents data sent in messages. +class DatabasePigeonSettings { + public: + // Constructs an object setting all non-nullable fields. + DatabasePigeonSettings(); + + // Constructs an object setting all fields. + explicit DatabasePigeonSettings(const bool* persistence_enabled, + const int64_t* cache_size_bytes, + const bool* logging_enabled, + const std::string* emulator_host, + const int64_t* emulator_port); + + const bool* persistence_enabled() const; + void set_persistence_enabled(const bool* value_arg); + void set_persistence_enabled(bool value_arg); + + const int64_t* cache_size_bytes() const; + void set_cache_size_bytes(const int64_t* value_arg); + void set_cache_size_bytes(int64_t value_arg); + + const bool* logging_enabled() const; + void set_logging_enabled(const bool* value_arg); + void set_logging_enabled(bool value_arg); + + const std::string* emulator_host() const; + void set_emulator_host(const std::string_view* value_arg); + void set_emulator_host(std::string_view value_arg); + + const int64_t* emulator_port() const; + void set_emulator_port(const int64_t* value_arg); + void set_emulator_port(int64_t value_arg); + + private: + static DatabasePigeonSettings FromEncodableList( + const flutter::EncodableList& list); + flutter::EncodableList ToEncodableList() const; + friend class DatabasePigeonFirebaseApp; + friend class FirebaseDatabaseHostApi; + friend class FirebaseDatabaseFlutterApi; + friend class PigeonInternalCodecSerializer; + std::optional persistence_enabled_; + std::optional cache_size_bytes_; + std::optional logging_enabled_; + std::optional emulator_host_; + std::optional emulator_port_; +}; + +// Generated class from Pigeon that represents data sent in messages. +class DatabasePigeonFirebaseApp { + public: + // Constructs an object setting all non-nullable fields. + explicit DatabasePigeonFirebaseApp(const std::string& app_name, + const DatabasePigeonSettings& settings); + + // Constructs an object setting all fields. + explicit DatabasePigeonFirebaseApp(const std::string& app_name, + const std::string* database_u_r_l, + const DatabasePigeonSettings& settings); + + ~DatabasePigeonFirebaseApp() = default; + DatabasePigeonFirebaseApp(const DatabasePigeonFirebaseApp& other); + DatabasePigeonFirebaseApp& operator=(const DatabasePigeonFirebaseApp& other); + DatabasePigeonFirebaseApp(DatabasePigeonFirebaseApp&& other) = default; + DatabasePigeonFirebaseApp& operator=( + DatabasePigeonFirebaseApp&& other) noexcept = default; + const std::string& app_name() const; + void set_app_name(std::string_view value_arg); + + const std::string* database_u_r_l() const; + void set_database_u_r_l(const std::string_view* value_arg); + void set_database_u_r_l(std::string_view value_arg); + + const DatabasePigeonSettings& settings() const; + void set_settings(const DatabasePigeonSettings& value_arg); + + private: + static DatabasePigeonFirebaseApp FromEncodableList( + const flutter::EncodableList& list); + flutter::EncodableList ToEncodableList() const; + friend class FirebaseDatabaseHostApi; + friend class FirebaseDatabaseFlutterApi; + friend class PigeonInternalCodecSerializer; + std::string app_name_; + std::optional database_u_r_l_; + std::unique_ptr settings_; +}; + +// Generated class from Pigeon that represents data sent in messages. +class DatabaseReferencePlatform { + public: + // Constructs an object setting all fields. + explicit DatabaseReferencePlatform(const std::string& path); + + const std::string& path() const; + void set_path(std::string_view value_arg); + + private: + static DatabaseReferencePlatform FromEncodableList( + const flutter::EncodableList& list); + flutter::EncodableList ToEncodableList() const; + friend class FirebaseDatabaseHostApi; + friend class FirebaseDatabaseFlutterApi; + friend class PigeonInternalCodecSerializer; + std::string path_; +}; + +// Generated class from Pigeon that represents data sent in messages. +class DatabaseReferenceRequest { + public: + // Constructs an object setting all non-nullable fields. + explicit DatabaseReferenceRequest(const std::string& path); + + // Constructs an object setting all fields. + explicit DatabaseReferenceRequest(const std::string& path, + const flutter::EncodableValue* value, + const flutter::EncodableValue* priority); + + const std::string& path() const; + void set_path(std::string_view value_arg); + + const flutter::EncodableValue* value() const; + void set_value(const flutter::EncodableValue* value_arg); + void set_value(const flutter::EncodableValue& value_arg); + + const flutter::EncodableValue* priority() const; + void set_priority(const flutter::EncodableValue* value_arg); + void set_priority(const flutter::EncodableValue& value_arg); + + private: + static DatabaseReferenceRequest FromEncodableList( + const flutter::EncodableList& list); + flutter::EncodableList ToEncodableList() const; + friend class FirebaseDatabaseHostApi; + friend class FirebaseDatabaseFlutterApi; + friend class PigeonInternalCodecSerializer; + std::string path_; + std::optional value_; + std::optional priority_; +}; + +// Generated class from Pigeon that represents data sent in messages. +class UpdateRequest { + public: + // Constructs an object setting all fields. + explicit UpdateRequest(const std::string& path, + const flutter::EncodableMap& value); + + const std::string& path() const; + void set_path(std::string_view value_arg); + + const flutter::EncodableMap& value() const; + void set_value(const flutter::EncodableMap& value_arg); + + private: + static UpdateRequest FromEncodableList(const flutter::EncodableList& list); + flutter::EncodableList ToEncodableList() const; + friend class FirebaseDatabaseHostApi; + friend class FirebaseDatabaseFlutterApi; + friend class PigeonInternalCodecSerializer; + std::string path_; + flutter::EncodableMap value_; +}; + +// Generated class from Pigeon that represents data sent in messages. +class TransactionRequest { + public: + // Constructs an object setting all fields. + explicit TransactionRequest(const std::string& path, int64_t transaction_key, + bool apply_locally); + + const std::string& path() const; + void set_path(std::string_view value_arg); + + int64_t transaction_key() const; + void set_transaction_key(int64_t value_arg); + + bool apply_locally() const; + void set_apply_locally(bool value_arg); + + private: + static TransactionRequest FromEncodableList( + const flutter::EncodableList& list); + flutter::EncodableList ToEncodableList() const; + friend class FirebaseDatabaseHostApi; + friend class FirebaseDatabaseFlutterApi; + friend class PigeonInternalCodecSerializer; + std::string path_; + int64_t transaction_key_; + bool apply_locally_; +}; + +// Generated class from Pigeon that represents data sent in messages. +class QueryRequest { + public: + // Constructs an object setting all non-nullable fields. + explicit QueryRequest(const std::string& path, + const flutter::EncodableList& modifiers); + + // Constructs an object setting all fields. + explicit QueryRequest(const std::string& path, + const flutter::EncodableList& modifiers, + const bool* value); + + const std::string& path() const; + void set_path(std::string_view value_arg); + + const flutter::EncodableList& modifiers() const; + void set_modifiers(const flutter::EncodableList& value_arg); + + const bool* value() const; + void set_value(const bool* value_arg); + void set_value(bool value_arg); + + private: + static QueryRequest FromEncodableList(const flutter::EncodableList& list); + flutter::EncodableList ToEncodableList() const; + friend class FirebaseDatabaseHostApi; + friend class FirebaseDatabaseFlutterApi; + friend class PigeonInternalCodecSerializer; + std::string path_; + flutter::EncodableList modifiers_; + std::optional value_; +}; + +// Generated class from Pigeon that represents data sent in messages. +class TransactionHandlerResult { + public: + // Constructs an object setting all non-nullable fields. + explicit TransactionHandlerResult(bool aborted, bool exception); + + // Constructs an object setting all fields. + explicit TransactionHandlerResult(const flutter::EncodableValue* value, + bool aborted, bool exception); + + const flutter::EncodableValue* value() const; + void set_value(const flutter::EncodableValue* value_arg); + void set_value(const flutter::EncodableValue& value_arg); + + bool aborted() const; + void set_aborted(bool value_arg); + + bool exception() const; + void set_exception(bool value_arg); + + private: + static TransactionHandlerResult FromEncodableList( + const flutter::EncodableList& list); + flutter::EncodableList ToEncodableList() const; + friend class FirebaseDatabaseHostApi; + friend class FirebaseDatabaseFlutterApi; + friend class PigeonInternalCodecSerializer; + std::optional value_; + bool aborted_; + bool exception_; +}; + +class PigeonInternalCodecSerializer : public flutter::StandardCodecSerializer { + public: + PigeonInternalCodecSerializer(); + inline static PigeonInternalCodecSerializer& GetInstance() { + static PigeonInternalCodecSerializer sInstance; + return sInstance; + } + + void WriteValue(const flutter::EncodableValue& value, + flutter::ByteStreamWriter* stream) const override; + + protected: + flutter::EncodableValue ReadValueOfType( + uint8_t type, flutter::ByteStreamReader* stream) const override; +}; + +// Generated interface from Pigeon that represents a handler of messages from +// Flutter. +class FirebaseDatabaseHostApi { + public: + FirebaseDatabaseHostApi(const FirebaseDatabaseHostApi&) = delete; + FirebaseDatabaseHostApi& operator=(const FirebaseDatabaseHostApi&) = delete; + virtual ~FirebaseDatabaseHostApi() {} + virtual void GoOnline( + const DatabasePigeonFirebaseApp& app, + std::function reply)> result) = 0; + virtual void GoOffline( + const DatabasePigeonFirebaseApp& app, + std::function reply)> result) = 0; + virtual void SetPersistenceEnabled( + const DatabasePigeonFirebaseApp& app, bool enabled, + std::function reply)> result) = 0; + virtual void SetPersistenceCacheSizeBytes( + const DatabasePigeonFirebaseApp& app, int64_t cache_size, + std::function reply)> result) = 0; + virtual void SetLoggingEnabled( + const DatabasePigeonFirebaseApp& app, bool enabled, + std::function reply)> result) = 0; + virtual void UseDatabaseEmulator( + const DatabasePigeonFirebaseApp& app, const std::string& host, + int64_t port, + std::function reply)> result) = 0; + virtual void Ref( + const DatabasePigeonFirebaseApp& app, const std::string* path, + std::function reply)> result) = 0; + virtual void RefFromURL( + const DatabasePigeonFirebaseApp& app, const std::string& url, + std::function reply)> result) = 0; + virtual void PurgeOutstandingWrites( + const DatabasePigeonFirebaseApp& app, + std::function reply)> result) = 0; + virtual void DatabaseReferenceSet( + const DatabasePigeonFirebaseApp& app, + const DatabaseReferenceRequest& request, + std::function reply)> result) = 0; + virtual void DatabaseReferenceSetWithPriority( + const DatabasePigeonFirebaseApp& app, + const DatabaseReferenceRequest& request, + std::function reply)> result) = 0; + virtual void DatabaseReferenceUpdate( + const DatabasePigeonFirebaseApp& app, const UpdateRequest& request, + std::function reply)> result) = 0; + virtual void DatabaseReferenceSetPriority( + const DatabasePigeonFirebaseApp& app, + const DatabaseReferenceRequest& request, + std::function reply)> result) = 0; + virtual void DatabaseReferenceRunTransaction( + const DatabasePigeonFirebaseApp& app, const TransactionRequest& request, + std::function reply)> result) = 0; + virtual void DatabaseReferenceGetTransactionResult( + const DatabasePigeonFirebaseApp& app, int64_t transaction_key, + std::function reply)> result) = 0; + virtual void OnDisconnectSet( + const DatabasePigeonFirebaseApp& app, + const DatabaseReferenceRequest& request, + std::function reply)> result) = 0; + virtual void OnDisconnectSetWithPriority( + const DatabasePigeonFirebaseApp& app, + const DatabaseReferenceRequest& request, + std::function reply)> result) = 0; + virtual void OnDisconnectUpdate( + const DatabasePigeonFirebaseApp& app, const UpdateRequest& request, + std::function reply)> result) = 0; + virtual void OnDisconnectCancel( + const DatabasePigeonFirebaseApp& app, const std::string& path, + std::function reply)> result) = 0; + virtual void QueryObserve( + const DatabasePigeonFirebaseApp& app, const QueryRequest& request, + std::function reply)> result) = 0; + virtual void QueryKeepSynced( + const DatabasePigeonFirebaseApp& app, const QueryRequest& request, + std::function reply)> result) = 0; + virtual void QueryGet( + const DatabasePigeonFirebaseApp& app, const QueryRequest& request, + std::function reply)> result) = 0; + + // The codec used by FirebaseDatabaseHostApi. + static const flutter::StandardMessageCodec& GetCodec(); + // Sets up an instance of `FirebaseDatabaseHostApi` to handle messages through + // the `binary_messenger`. + static void SetUp(flutter::BinaryMessenger* binary_messenger, + FirebaseDatabaseHostApi* api); + static void SetUp(flutter::BinaryMessenger* binary_messenger, + FirebaseDatabaseHostApi* api, + const std::string& message_channel_suffix); + static flutter::EncodableValue WrapError(std::string_view error_message); + static flutter::EncodableValue WrapError(const FlutterError& error); + + protected: + FirebaseDatabaseHostApi() = default; +}; +// Generated class from Pigeon that represents Flutter messages that can be +// called from C++. +class FirebaseDatabaseFlutterApi { + public: + FirebaseDatabaseFlutterApi(flutter::BinaryMessenger* binary_messenger); + FirebaseDatabaseFlutterApi(flutter::BinaryMessenger* binary_messenger, + const std::string& message_channel_suffix); + static const flutter::StandardMessageCodec& GetCodec(); + void CallTransactionHandler( + int64_t transaction_key, const flutter::EncodableValue* snapshot_value, + std::function&& on_success, + std::function&& on_error); + + private: + flutter::BinaryMessenger* binary_messenger_; + std::string message_channel_suffix_; +}; + +} // namespace firebase_database_windows +#endif // PIGEON_MESSAGES_G_H_ diff --git a/packages/firebase_database/firebase_database/windows/plugin_version.h.in b/packages/firebase_database/firebase_database/windows/plugin_version.h.in new file mode 100644 index 000000000000..7a405b7c9894 --- /dev/null +++ b/packages/firebase_database/firebase_database/windows/plugin_version.h.in @@ -0,0 +1,13 @@ +// Copyright 2025, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#ifndef PLUGIN_VERSION_CONFIG_H +#define PLUGIN_VERSION_CONFIG_H + +namespace firebase_database_windows { + +std::string getPluginVersion() { return "@PLUGIN_VERSION@"; } +} // namespace firebase_database_windows + +#endif // PLUGIN_VERSION_CONFIG_H diff --git a/packages/firebase_database/firebase_database_platform_interface/pigeons/messages.dart b/packages/firebase_database/firebase_database_platform_interface/pigeons/messages.dart index 3a752d5c8640..2db035bbae4c 100644 --- a/packages/firebase_database/firebase_database_platform_interface/pigeons/messages.dart +++ b/packages/firebase_database/firebase_database_platform_interface/pigeons/messages.dart @@ -16,6 +16,9 @@ import 'package:pigeon/pigeon.dart'; ), swiftOut: '../firebase_database/ios/firebase_database/Sources/firebase_database/FirebaseDatabaseMessages.g.swift', + cppHeaderOut: '../firebase_database/windows/messages.g.h', + cppSourceOut: '../firebase_database/windows/messages.g.cpp', + cppOptions: CppOptions(namespace: 'firebase_database_windows'), copyrightHeader: 'pigeons/copyright.txt', ), ) diff --git a/tests/windows/flutter/CMakeLists.txt b/tests/windows/flutter/CMakeLists.txt index 930d2071a324..903f4899d6fc 100644 --- a/tests/windows/flutter/CMakeLists.txt +++ b/tests/windows/flutter/CMakeLists.txt @@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") @@ -92,7 +97,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/tests/windows/flutter/generated_plugin_registrant.cc b/tests/windows/flutter/generated_plugin_registrant.cc index 2fda6bf3095a..9a180bb54481 100644 --- a/tests/windows/flutter/generated_plugin_registrant.cc +++ b/tests/windows/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -16,6 +17,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi")); FirebaseCorePluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); + FirebaseDatabasePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FirebaseDatabasePluginCApi")); FirebaseRemoteConfigPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FirebaseRemoteConfigPluginCApi")); FirebaseStoragePluginCApiRegisterWithRegistrar( diff --git a/tests/windows/flutter/generated_plugins.cmake b/tests/windows/flutter/generated_plugins.cmake index 7a8854af51e0..1a9da22be867 100644 --- a/tests/windows/flutter/generated_plugins.cmake +++ b/tests/windows/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST firebase_auth firebase_core + firebase_database firebase_remote_config firebase_storage ) From 531ceeb3a1a64fa2e6a3c994deeb0c9773d8dac7 Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Fri, 6 Mar 2026 13:52:02 +0100 Subject: [PATCH 67/72] ci: update swift-integration tests to work on main (#18083) --- .github/workflows/scripts/swift-integration.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/scripts/swift-integration.dart b/.github/workflows/scripts/swift-integration.dart index e64390df2bf4..1c4e3f75cdd2 100644 --- a/.github/workflows/scripts/swift-integration.dart +++ b/.github/workflows/scripts/swift-integration.dart @@ -214,7 +214,10 @@ Future updatePackageSwiftForPackage( } // handles forked repositories - final repoSlug = headRepo != baseRepo ? headRepo : baseRepo; + final repoSlug = + (headRepo != null && headRepo.isNotEmpty && headRepo != baseRepo) + ? headRepo + : baseRepo; print('repoSlug: $repoSlug'); print('branch: $branch'); From 476ba53f016f20009fd571ad6ab359631f97094b Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Fri, 6 Mar 2026 13:52:15 +0100 Subject: [PATCH 68/72] docs(auth): add documentation about errors code when Email Enumeration Protection is activated (#18084) --- docs/auth/password-auth.md | 16 ++++++++++++- .../firebase_auth/lib/src/firebase_auth.dart | 24 +++++++++++++------ .../platform_interface_firebase_auth.dart | 24 +++++++++++++------ 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/docs/auth/password-auth.md b/docs/auth/password-auth.md index ce1acecd2185..adbea74e3686 100644 --- a/docs/auth/password-auth.md +++ b/docs/auth/password-auth.md @@ -73,14 +73,28 @@ try { password: password ); } on FirebaseAuthException catch (e) { - if (e.code == 'user-not-found') { + if (e.code == 'invalid-credential') { + // Email or password is incorrect. Projects with email enumeration + // protection enabled (the default since September 2023) return this + // code instead of 'user-not-found' or 'wrong-password'. + print('Invalid email or password.'); + } else if (e.code == 'user-not-found') { + // Only returned when email enumeration protection is disabled. print('No user found for that email.'); } else if (e.code == 'wrong-password') { + // Only returned when email enumeration protection is disabled. print('Wrong password provided for that user.'); } } ``` +Note: Since September 2023, Firebase enables +[email enumeration protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) +by default on new projects. With this feature enabled, `user-not-found` and +`wrong-password` error codes are replaced by `invalid-credential` to prevent +revealing whether an email address is registered. You can manage this setting in +the Firebase console under **Authentication > Settings**. + Caution: When a user uninstalls your app on iOS or macOS, the user's authentication state can persist between app re-installs, as the Firebase iOS SDK persists authentication state to the system keychain. diff --git a/packages/firebase_auth/firebase_auth/lib/src/firebase_auth.dart b/packages/firebase_auth/firebase_auth/lib/src/firebase_auth.dart index bdef18cbd7e1..027892a8c8d9 100644 --- a/packages/firebase_auth/firebase_auth/lib/src/firebase_auth.dart +++ b/packages/firebase_auth/firebase_auth/lib/src/firebase_auth.dart @@ -535,11 +535,19 @@ class FirebaseAuth extends FirebasePluginPlatform { /// - Thrown if the email address is not valid. /// - **user-disabled**: /// - Thrown if the user corresponding to the given email has been disabled. - /// - **user-not-found**: + /// - **user-not-found** _(deprecated)_: /// - Thrown if there is no user corresponding to the given email. - /// - **wrong-password**: + /// **Note:** This code is no longer returned on projects that have + /// [email enumeration protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) + /// enabled (the default for new projects since September 2023). + /// Use **invalid-credential** instead. + /// - **wrong-password** _(deprecated)_: /// - Thrown if the password is invalid for the given email, or the account /// corresponding to the email does not have a password set. + /// **Note:** This code is no longer returned on projects that have + /// [email enumeration protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) + /// enabled (the default for new projects since September 2023). + /// Use **invalid-credential** instead. /// - **too-many-requests**: /// - Thrown if the user sent too many requests at the same time, for security /// the api will not allow too many attempts at the same time, user will have @@ -550,11 +558,13 @@ class FirebaseAuth extends FirebasePluginPlatform { /// - **network-request-failed**: /// - Thrown if there was a network request error, for example the user /// doesn't have internet connection - /// - **INVALID_LOGIN_CREDENTIALS** or **invalid-credential**: - /// - Thrown if the password is invalid for the given email, or the account - /// corresponding to the email does not have a password set. - /// Depending on if you are using firebase emulator or not the code is - /// different + /// - **invalid-credential**: + /// - Thrown if the email or password is incorrect. On projects with + /// [email enumeration protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) + /// enabled (the default since September 2023), this replaces + /// **user-not-found** and **wrong-password** to prevent revealing + /// whether an account exists. On the Firebase emulator, the code may + /// appear as **INVALID_LOGIN_CREDENTIALS**. /// - **operation-not-allowed**: /// - Thrown if email/password accounts are not enabled. Enable /// email/password accounts in the Firebase Console, under the Auth tab. diff --git a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/platform_interface/platform_interface_firebase_auth.dart b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/platform_interface/platform_interface_firebase_auth.dart index 9a4fcb1c0308..a57a98532592 100644 --- a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/platform_interface/platform_interface_firebase_auth.dart +++ b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/platform_interface/platform_interface_firebase_auth.dart @@ -514,11 +514,19 @@ abstract class FirebaseAuthPlatform extends PlatformInterface { /// - Thrown if the email address is not valid. /// - **user-disabled**: /// - Thrown if the user corresponding to the given email has been disabled. - /// - **user-not-found**: + /// - **user-not-found** _(deprecated)_: /// - Thrown if there is no user corresponding to the given email. - /// - **wrong-password**: + /// **Note:** This code is no longer returned on projects that have + /// [email enumeration protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) + /// enabled (the default for new projects since September 2023). + /// Use **invalid-credential** instead. + /// - **wrong-password** _(deprecated)_: /// - Thrown if the password is invalid for the given email, or the account /// corresponding to the email does not have a password set. + /// **Note:** This code is no longer returned on projects that have + /// [email enumeration protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) + /// enabled (the default for new projects since September 2023). + /// Use **invalid-credential** instead. /// - **too-many-requests**: /// - Thrown if the user sent too many requests at the same time, for security /// the api will not allow too many attempts at the same time, user will have @@ -529,11 +537,13 @@ abstract class FirebaseAuthPlatform extends PlatformInterface { /// - **network-request-failed**: /// - Thrown if there was a network request error, for example the user /// doesn't have internet connection - /// - **INVALID_LOGIN_CREDENTIALS** or **invalid-credential**: - /// - Thrown if the password is invalid for the given email, or the account - /// corresponding to the email does not have a password set. - /// Depending on if you are using firebase emulator or not the code is - /// different + /// - **invalid-credential**: + /// - Thrown if the email or password is incorrect. On projects with + /// [email enumeration protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) + /// enabled (the default since September 2023), this replaces + /// **user-not-found** and **wrong-password** to prevent revealing + /// whether an account exists. On the Firebase emulator, the code may + /// appear as **INVALID_LOGIN_CREDENTIALS**. /// - **operation-not-allowed**: /// - Thrown if email/password accounts are not enabled. Enable /// email/password accounts in the Firebase Console, under the Auth tab. From 60b5cd5c7888fa932124958125e87bd39e1c323c Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Fri, 6 Mar 2026 16:19:25 +0100 Subject: [PATCH 69/72] fix(auth): fix inconsistence in casing in the native iOS SDK and Web SDK (#18086) * fix(auth, ios): fix inconsistence in casing in the native iOS SDK with a workaround * format * fix for web --- .../firebase_auth/FLTFirebaseAuthPlugin.m | 105 +++++++++++++++++- .../lib/src/interop/auth_interop.dart | 1 + .../lib/src/utils/web_utils.dart | 22 +++- .../firebase_auth_instance_e2e_test.dart | 53 +++++++++ 4 files changed, 179 insertions(+), 2 deletions(-) diff --git a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/FLTFirebaseAuthPlugin.m b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/FLTFirebaseAuthPlugin.m index 22a26ae98a97..7edbadb78910 100644 --- a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/FLTFirebaseAuthPlugin.m +++ b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/FLTFirebaseAuthPlugin.m @@ -113,6 +113,9 @@ @implementation FLTFirebaseAuthPlugin { // Map an id to a MultiFactorResolver object. NSMutableDictionary *_multiFactorTotpSecretMap; + // Emulator host/port per app, used to build REST URLs for workarounds. + NSMutableDictionary *_emulatorConfigs; + NSObject *_binaryMessenger; NSMutableDictionary *_eventChannels; NSMutableDictionary *> *_streamHandlers; @@ -134,6 +137,7 @@ - (instancetype)init:(NSObject *)messenger { _multiFactorResolverMap = [NSMutableDictionary dictionary]; _multiFactorAssertionMap = [NSMutableDictionary dictionary]; _multiFactorTotpSecretMap = [NSMutableDictionary dictionary]; + _emulatorConfigs = [NSMutableDictionary dictionary]; } return self; } @@ -1137,7 +1141,20 @@ - (void)checkActionCodeApp:(nonnull AuthPigeonFirebaseApp *)app if (error != nil) { completion(nil, [FLTFirebaseAuthPlugin convertToFlutterError:error]); } else { - completion([self parseActionCode:info], nil); + PigeonActionCodeInfo *result = [self parseActionCode:info]; + if (result.operation == ActionCodeInfoOperationUnknown) { + // Workaround: Firebase iOS SDK >=11.12.0 returns .unknown because + // actionCodeOperation(forRequestType:) only matches camelCase but the + // REST API returns SCREAMING_SNAKE_CASE (e.g. "VERIFY_EMAIL"). + // Re-fetch the raw requestType via REST to resolve the operation. + // See: https://github.com/firebase/flutterfire/issues/17452 + [self resolveActionCodeOperationForApp:app + code:code + fallbackInfo:result + completion:completion]; + } else { + completion(result, nil); + } } }]; } @@ -1167,6 +1184,91 @@ - (PigeonActionCodeInfo *_Nullable)parseActionCode:(nonnull FIRActionCodeInfo *) return [PigeonActionCodeInfo makeWithOperation:operation data:data]; } +/// Maps a raw requestType string (either camelCase or SCREAMING_SNAKE_CASE) to +/// the corresponding Pigeon enum value. ++ (ActionCodeInfoOperation)operationFromRequestType:(nullable NSString *)requestType { + static NSDictionary *mapping; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + mapping = @{ + @"PASSWORD_RESET" : @(ActionCodeInfoOperationPasswordReset), + @"resetPassword" : @(ActionCodeInfoOperationPasswordReset), + @"VERIFY_EMAIL" : @(ActionCodeInfoOperationVerifyEmail), + @"verifyEmail" : @(ActionCodeInfoOperationVerifyEmail), + @"RECOVER_EMAIL" : @(ActionCodeInfoOperationRecoverEmail), + @"recoverEmail" : @(ActionCodeInfoOperationRecoverEmail), + @"EMAIL_SIGNIN" : @(ActionCodeInfoOperationEmailSignIn), + @"signIn" : @(ActionCodeInfoOperationEmailSignIn), + @"VERIFY_AND_CHANGE_EMAIL" : @(ActionCodeInfoOperationVerifyAndChangeEmail), + @"verifyAndChangeEmail" : @(ActionCodeInfoOperationVerifyAndChangeEmail), + @"REVERT_SECOND_FACTOR_ADDITION" : @(ActionCodeInfoOperationRevertSecondFactorAddition), + @"revertSecondFactorAddition" : @(ActionCodeInfoOperationRevertSecondFactorAddition), + }; + }); + + NSNumber *value = mapping[requestType]; + return value ? (ActionCodeInfoOperation)value.integerValue : ActionCodeInfoOperationUnknown; +} + +/// Calls the Identity Toolkit REST API directly to retrieve the raw requestType +/// string, which the iOS SDK fails to parse correctly. Falls back to the original +/// result if the REST call fails for any reason. +- (void)resolveActionCodeOperationForApp:(nonnull AuthPigeonFirebaseApp *)app + code:(nonnull NSString *)code + fallbackInfo:(nonnull PigeonActionCodeInfo *)fallbackInfo + completion:(nonnull void (^)(PigeonActionCodeInfo *_Nullable, + FlutterError *_Nullable))completion { + FIRApp *firebaseApp = [FLTFirebasePlugin firebaseAppNamed:app.appName]; + NSString *apiKey = firebaseApp.options.APIKey; + + NSString *baseURL; + NSDictionary *emulatorConfig = _emulatorConfigs[app.appName]; + if (emulatorConfig) { + baseURL = [NSString stringWithFormat:@"http://%@:%@/identitytoolkit.googleapis.com", + emulatorConfig[@"host"], emulatorConfig[@"port"]]; + } else { + baseURL = @"https://identitytoolkit.googleapis.com"; + } + + NSString *urlString = + [NSString stringWithFormat:@"%@/v1/accounts:resetPassword?key=%@", baseURL, apiKey]; + NSURL *url = [NSURL URLWithString:urlString]; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + request.HTTPMethod = @"POST"; + [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + request.HTTPBody = [NSJSONSerialization dataWithJSONObject:@{@"oobCode" : code} + options:0 + error:nil]; + + NSURLSessionDataTask *task = [[NSURLSession sharedSession] + dataTaskWithRequest:request + completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, + NSError *_Nullable error) { + if (error || !data) { + completion(fallbackInfo, nil); + return; + } + + NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + if (!json || json[@"error"]) { + completion(fallbackInfo, nil); + return; + } + + ActionCodeInfoOperation operation = + [FLTFirebaseAuthPlugin operationFromRequestType:json[@"requestType"]]; + + if (operation != ActionCodeInfoOperationUnknown) { + completion([PigeonActionCodeInfo makeWithOperation:operation data:fallbackInfo.data], + nil); + } else { + completion(fallbackInfo, nil); + } + }]; + [task resume]; +} + - (void)confirmPasswordResetApp:(nonnull AuthPigeonFirebaseApp *)app code:(nonnull NSString *)code newPassword:(nonnull NSString *)newPassword @@ -1600,6 +1702,7 @@ - (void)useEmulatorApp:(nonnull AuthPigeonFirebaseApp *)app completion:(nonnull void (^)(FlutterError *_Nullable))completion { FIRAuth *auth = [self getFIRAuthFromAppNameFromPigeon:app]; [auth useEmulatorWithHost:host port:port]; + _emulatorConfigs[app.appName] = @{@"host" : host, @"port" : @(port)}; completion(nil); } diff --git a/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth_interop.dart b/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth_interop.dart index 67ce4631d71c..7d66865f0d35 100644 --- a/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth_interop.dart +++ b/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth_interop.dart @@ -575,6 +575,7 @@ extension type ConfirmationResultJsImpl._(JSObject _) implements JSObject { /// See: . extension type ActionCodeInfo._(JSObject _) implements JSObject { external ActionCodeData get data; + external JSString get operation; } /// Interface representing a user's metadata. diff --git a/packages/firebase_auth/firebase_auth_web/lib/src/utils/web_utils.dart b/packages/firebase_auth/firebase_auth_web/lib/src/utils/web_utils.dart index b1a1e865c249..7e215a3299b4 100644 --- a/packages/firebase_auth/firebase_auth_web/lib/src/utils/web_utils.dart +++ b/packages/firebase_auth/firebase_auth_web/lib/src/utils/web_utils.dart @@ -172,7 +172,8 @@ ActionCodeInfo? convertWebActionCodeInfo( } return ActionCodeInfo( - operation: ActionCodeInfoOperation.passwordReset, + operation: + _convertWebActionCodeOperation(webActionCodeInfo.operation.toDart), data: ActionCodeInfoData( email: webActionCodeInfo.data.email?.toDart, previousEmail: webActionCodeInfo.data.previousEmail?.toDart, @@ -180,6 +181,25 @@ ActionCodeInfo? convertWebActionCodeInfo( ); } +ActionCodeInfoOperation _convertWebActionCodeOperation(String operation) { + switch (operation) { + case 'EMAIL_SIGNIN': + return ActionCodeInfoOperation.emailSignIn; + case 'PASSWORD_RESET': + return ActionCodeInfoOperation.passwordReset; + case 'RECOVER_EMAIL': + return ActionCodeInfoOperation.recoverEmail; + case 'REVERT_SECOND_FACTOR_ADDITION': + return ActionCodeInfoOperation.revertSecondFactorAddition; + case 'VERIFY_AND_CHANGE_EMAIL': + return ActionCodeInfoOperation.verifyAndChangeEmail; + case 'VERIFY_EMAIL': + return ActionCodeInfoOperation.verifyEmail; + default: + return ActionCodeInfoOperation.unknown; + } +} + /// Converts a [auth_interop.AdditionalUserInfo] into a [AdditionalUserInfo]. AdditionalUserInfo? convertWebAdditionalUserInfo( auth_interop.AdditionalUserInfo? webAdditionalUserInfo, diff --git a/tests/integration_test/firebase_auth/firebase_auth_instance_e2e_test.dart b/tests/integration_test/firebase_auth/firebase_auth_instance_e2e_test.dart index 1935cf7dfabf..2c4e6d70c89f 100644 --- a/tests/integration_test/firebase_auth/firebase_auth_instance_e2e_test.dart +++ b/tests/integration_test/firebase_auth/firebase_auth_instance_e2e_test.dart @@ -271,6 +271,59 @@ void main() { fail(e.toString()); } }); + + test('returns correct operation for verifyEmail action code', + () async { + final email = generateRandomEmail(); + await FirebaseAuth.instance.createUserWithEmailAndPassword( + email: email, + password: testPassword, + ); + + await FirebaseAuth.instance.currentUser!.sendEmailVerification(); + + final oobCode = await emulatorOutOfBandCode( + email, + EmulatorOobCodeType.verifyEmail, + ); + expect(oobCode, isNotNull); + + final actionCodeInfo = await FirebaseAuth.instance.checkActionCode( + oobCode!.oobCode!, + ); + + expect( + actionCodeInfo.operation, + equals(ActionCodeInfoOperation.verifyEmail), + ); + }); + + test('returns correct operation for passwordReset action code', + () async { + final email = generateRandomEmail(); + await FirebaseAuth.instance.createUserWithEmailAndPassword( + email: email, + password: testPassword, + ); + await ensureSignedOut(); + + await FirebaseAuth.instance.sendPasswordResetEmail(email: email); + + final oobCode = await emulatorOutOfBandCode( + email, + EmulatorOobCodeType.passwordReset, + ); + expect(oobCode, isNotNull); + + final actionCodeInfo = await FirebaseAuth.instance.checkActionCode( + oobCode!.oobCode!, + ); + + expect( + actionCodeInfo.operation, + equals(ActionCodeInfoOperation.passwordReset), + ); + }); }, skip: !kIsWeb && Platform.isWindows, ); From c2ac0161d54e44e9e69d401827076efbad66fdee Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Fri, 6 Mar 2026 15:47:37 +0000 Subject: [PATCH 70/72] chore: fix bugs and refactored code --- .../firestore/utils/ExpressionParsers.java | 13 ++++ .../firestore/utils/PipelineParser.java | 5 +- .../cloud_firestore/FLTPipelineParser.m | 26 ++++++- .../lib/src/pipeline_expression.dart | 8 +- .../lib/src/interop/firestore_interop.dart | 19 ++++- .../lib/src/pipeline_builder_web.dart | 12 +-- ...rt => pipeline_expression_parser_web.dart} | 76 +++++++++++++++---- 7 files changed, 128 insertions(+), 31 deletions(-) rename packages/cloud_firestore/cloud_firestore_web/lib/src/{pipeline_expression_converter_web.dart => pipeline_expression_parser_web.dart} (83%) diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java index 1f80f671a97b..9d52b9f188e5 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java @@ -8,6 +8,7 @@ import android.util.Log; import androidx.annotation.NonNull; +import com.google.firebase.firestore.FirebaseFirestore; import com.google.firebase.firestore.pipeline.AggregateFunction; import com.google.firebase.firestore.pipeline.AggregateOptions; import com.google.firebase.firestore.pipeline.AggregateStage; @@ -24,6 +25,12 @@ class ExpressionParsers { private static final String TAG = "ExpressionParsers"; + private final FirebaseFirestore firestore; + + ExpressionParsers(@NonNull FirebaseFirestore firestore) { + this.firestore = firestore; + } + /** Binary operation on two expressions. Used instead of BiFunction for API 23 compatibility. */ private interface BinaryExpressionOp { R apply(Expression left, Expression right); @@ -65,6 +72,12 @@ Expression parseExpression(@NonNull Map expressionMap) { case "constant": { Object value = args.get("value"); + if (value instanceof Map) { + @SuppressWarnings("unchecked") + Map valueMap = (Map) value; + String path = (String) valueMap.get("path"); + return Expression.constant(firestore.document(path)); + } return ExpressionHelpers.parseConstantValue(value); } case "alias": diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineParser.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineParser.java index 2e0296d92f74..9528a5ad8eb6 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineParser.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineParser.java @@ -21,9 +21,6 @@ public class PipelineParser { private static final String TAG = "PipelineParser"; - private static final ExpressionParsers expressionParsers = new ExpressionParsers(); - private static final PipelineStageHandlers stageHandlers = - new PipelineStageHandlers(expressionParsers); /** * Executes a pipeline from a list of stage maps. @@ -50,6 +47,8 @@ public static Snapshot executePipeline( @SuppressWarnings("unchecked") public static Pipeline buildPipeline( @NonNull FirebaseFirestore firestore, @NonNull List> stages) { + ExpressionParsers expressionParsers = new ExpressionParsers(firestore); + PipelineStageHandlers stageHandlers = new PipelineStageHandlers(expressionParsers); PipelineSource pipelineSource = firestore.pipeline(); Pipeline pipeline = null; diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m index bcee8288a06a..03f795ba9987 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m @@ -35,6 +35,8 @@ } @interface FLTPipelineExpressionParser : NSObject +@property(nonatomic, strong) FIRFirestore *firestore; +- (instancetype)initWithFirestore:(FIRFirestore *)firestore; - (FIRExprBridge *)parseExpression:(NSDictionary *)map error:(NSError **)error; - (FIRExprBridge *)parseBooleanExpression:(NSDictionary *)map error:(NSError **)error; @@ -43,6 +45,14 @@ - (FIRExprBridge *)rightExprFromValue:(id)value error:(NSError **)error; @implementation FLTPipelineExpressionParser +- (instancetype)initWithFirestore:(FIRFirestore *)firestore { + self = [super init]; + if (self) { + _firestore = firestore; + } + return self; +} + - (FIRExprBridge *)parseExpression:(NSDictionary *)map error:(NSError **)error { NSString *name = map[@"name"]; if (!name) { @@ -72,6 +82,13 @@ - (FIRExprBridge *)parseExpression:(NSDictionary *)map error:(NS if (error) *error = parseError(@"Constant requires 'value' argument"); return nil; } + if ([value isKindOfClass:[NSDictionary class]]) { + NSString *path = ((NSDictionary *)value)[@"path"]; + if ([path isKindOfClass:[NSString class]] && self.firestore) { + FIRDocumentReference *docRef = [self.firestore documentWithPath:path]; + return [[FIRConstantBridge alloc] init:docRef]; + } + } return [[FIRConstantBridge alloc] init:value]; } @@ -428,7 +445,8 @@ + (NSString *)keyForExpressionMap:(NSDictionary *)em error:(NSError **)error { parseStagesWithFirestore:(FIRFirestore *)firestore stages:(NSArray *> *)stages error:(NSError **)error { - FLTPipelineExpressionParser *exprParser = [[FLTPipelineExpressionParser alloc] init]; + FLTPipelineExpressionParser *exprParser = + [[FLTPipelineExpressionParser alloc] initWithFirestore:firestore]; NSMutableArray *stageBridges = [NSMutableArray array]; NSError *parseErr = nil; @@ -741,9 +759,9 @@ + (FIRAggregateFunctionBridge *)aggregateFunctionFromMap:(NSDictionary *)funcMap return [[FIRAggregateFunctionBridge alloc] initWithName:iosName Args:argsArray]; } -+ (FIRStageBridge *)parseAggregateStageLegacyArgs:(NSDictionary *)args - exprParser:(FLTPipelineExpressionParser *)exprParser - error:(NSError **)error { ++ (FIRStageBridge *)parseAggregateStageWithArgs:(NSDictionary *)args + exprParser:(FLTPipelineExpressionParser *)exprParser + error:(NSError **)error { NSArray *accumulatorMaps = args[@"aggregate_functions"]; if (![accumulatorMaps isKindOfClass:[NSArray class]] || accumulatorMaps.count == 0) { if (error) *error = parseError(@"aggregate requires aggregate_functions"); diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart index 6f740f548c54..1a4d0a33d702 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart @@ -1254,11 +1254,13 @@ class Constant extends Expression { @override Map toMap() { + Object? serializedValue = value; + if (value is DocumentReference) { + serializedValue = {'path': (value! as DocumentReference).path}; + } return { 'name': name, - 'args': { - 'value': value, - }, + 'args': {'value': serializedValue}, }; } } 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 9a4ed9de4d3f..40512aa290a7 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 @@ -354,7 +354,7 @@ extension type PipelinesJsImpl._(JSObject _) implements JSObject { // --- Expression builders --- external ExpressionJsImpl field(JSString path); - external ExpressionJsImpl constant(JSAny value); + external ExpressionJsImpl constant(JSAny? value); // --- Boolean / comparison --- external JSAny equal(JSAny left, JSAny right); @@ -1235,5 +1235,20 @@ extension type ReplaceWithStageOptionsJsImpl._(JSObject _) implements JSObject { ReplaceWithStageOptionsJsImpl() : this._(JSObject.new()); // ignore: avoid_setters_without_getters - external set expression(JSAny value); + external set map(JSAny value); +} + +extension type FindNearestStageOptionsJsImpl._(JSObject _) implements JSObject { + FindNearestStageOptionsJsImpl() : this._(JSObject.new()); + + // ignore: avoid_setters_without_getters + external set field(JSAny value); + // ignore: avoid_setters_without_getters + external set vectorValue(JSAny value); + // ignore: avoid_setters_without_getters + external set distanceMeasure(JSString value); + // ignore: avoid_setters_without_getters + external set limit(JSNumber value); + // ignore: avoid_setters_without_getters + external set distanceField(JSString value); } diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_builder_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_builder_web.dart index fdaf1cb7b7fc..8faede5c1c24 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_builder_web.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_builder_web.dart @@ -7,7 +7,7 @@ import 'dart:js_interop'; import 'package:cloud_firestore_web/src/interop/firestore_interop.dart' as interop; -import 'package:cloud_firestore_web/src/pipeline_expression_converter_web.dart'; +import 'package:cloud_firestore_web/src/pipeline_expression_parser_web.dart'; /// Builds a JS Pipeline from serialized [stages] and returns it ready to execute. /// Keeps [executePipeline] thin: build → execute → convert. @@ -26,7 +26,7 @@ interop.PipelineJsImpl buildPipelineFromStages( interop.PipelineJsImpl pipeline = _applySourceStage( source as interop.PipelineSourceJsImpl, jsFirestore, stageName, first); - final converter = PipelineExpressionConverterWeb(interop.pipelines); + final converter = PipelineExpressionParserWeb(interop.pipelines, jsFirestore); // Apply remaining stages for (var i = 1; i < stages.length; i++) { @@ -77,7 +77,7 @@ interop.PipelineJsImpl _applySourceStage( interop.PipelineJsImpl _applyStage( interop.PipelineJsImpl pipeline, Map stage, - PipelineExpressionConverterWeb converter, + PipelineExpressionParserWeb converter, interop.FirestoreJsImpl jsFirestore, ) { final name = stage['stage'] as String?; @@ -139,8 +139,10 @@ interop.PipelineJsImpl _applyStage( case 'find_nearest': return pipeline.findNearest(converter.toFindNearestOptions(map)); case 'union': - // Union requires another Pipeline; not yet supported from serialized stages. - return pipeline; + final pipelineStages = map['pipeline'] as List>; + final otherPipeline = + buildPipelineFromStages(jsFirestore, pipelineStages); + return pipeline.union(otherPipeline); default: // Ignore unknown stages return pipeline; diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_converter_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_parser_web.dart similarity index 83% rename from packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_converter_web.dart rename to packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_parser_web.dart index 4459a4504bb3..5f4768c488cb 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_converter_web.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_parser_web.dart @@ -4,8 +4,10 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:js_interop'; -import 'dart:js_util' show setProperty; +import 'dart:typed_data'; +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart' + show Blob, GeoPoint, Timestamp, VectorValue; import 'package:cloud_firestore_web/src/interop/firestore_interop.dart' as interop; import 'package:cloud_firestore_web/src/interop/utils/utils.dart'; @@ -13,10 +15,11 @@ import 'package:cloud_firestore_web/src/interop/utils/utils.dart'; /// Converts Dart serialized pipeline expressions/stage args into JS pipeline /// types by calling the pipelines interop API (field, constant, equal, and, /// ascending, etc.) that mirrors the Firebase JS SDK. -class PipelineExpressionConverterWeb { - PipelineExpressionConverterWeb(this._pipelines); +class PipelineExpressionParserWeb { + PipelineExpressionParserWeb(this._pipelines, this._jsFirestore); final interop.PipelinesJsImpl _pipelines; + final interop.FirestoreJsImpl _jsFirestore; static const _kName = 'name'; static const _kArgs = 'args'; @@ -48,7 +51,7 @@ class PipelineExpressionConverterWeb { return _binaryArithmetic(argsMap, (l, r) => l.modulo(r)); case 'constant': case 'null': - return _pipelines.constant(jsify(argsMap[_kValue])!); + return _pipelines.constant(_constantValueToJs(argsMap[_kValue])); default: throw UnsupportedError('Unsupported expression: $name'); } @@ -204,7 +207,6 @@ class PipelineExpressionConverterWeb { /// Converts unnest stage args to JS UnnestStageOptions. JSAny toUnnestOptions(Map map) { - print('toUnnestOptions: ${map.toString()}'); final expression = map[_kExpression] as Map; final indexField = map['index_field'] as String?; final sel = toSelectable(expression); @@ -228,32 +230,78 @@ class PipelineExpressionConverterWeb { /// Converts replace_with expression to JS ReplaceWithStageOptions. JSAny toReplaceWithOptions(Map expression) { return interop.ReplaceWithStageOptionsJsImpl() - ..expression = toExpression(expression); + ..map = toExpression(expression); } /// Converts find_nearest args to JS FindNearestStageOptions. - JSAny toFindNearestOptions(Map map) { + interop.FindNearestStageOptionsJsImpl toFindNearestOptions( + Map map) { final vectorField = (map['vector_field'] as String?) ?? (map[_kField] as String?); final vectorValue = map['vector_value'] as List?; final distanceMeasure = (map['distance_measure'] as String?) ?? 'cosine'; final limit = map['limit'] as int?; + final distanceField = map['distance_field'] as String?; if (vectorField == null || vectorValue == null) { throw UnsupportedError( 'Pipeline findNearest() on web requires vector_field and vector_value.', ); } - final obj = JSObject.new; - setProperty(obj, 'vectorField', vectorField.toJS); - setProperty(obj, 'vectorValue', - jsify(vectorValue.map((e) => (e as num).toDouble()).toList())); - setProperty(obj, 'distanceMeasure', distanceMeasure.toJS); - if (limit != null) setProperty(obj, 'limit', limit.toJS); - return obj as JSAny; + final doubles = vectorValue.map((e) => (e as num).toDouble()).toList(); + final opts = interop.FindNearestStageOptionsJsImpl() + ..field = _pipelines.field(vectorField.toJS) + ..vectorValue = interop.vector(doubles.jsify()! as JSArray) + ..distanceMeasure = distanceMeasure.toJS; + if (limit != null) opts.limit = limit.toJS; + if (distanceField != null) opts.distanceField = distanceField.toJS; + return opts; } // ── Private helpers ─────────────────────────────────────────────────────── + /// Converts a [Constant] value to the correct JS type for the pipelines API. + /// + /// Each Dart type accepted by [Constant] is mapped to the corresponding + /// Firestore JS SDK interop type so that the JS SDK receives a properly typed + /// value (e.g. a JS `Timestamp`, `GeoPoint`, or `Bytes` object) rather than + /// a plain JS primitive or an unrecognised object. + JSAny? _constantValueToJs(Object? value) { + if (value == null) return null; + if (value is String) return value.toJS; + if (value is bool) return value.toJS; + if (value is int) return value.toJS; + if (value is double) return value.toJS; + if (value is DateTime) { + return interop.TimestampJsImpl.fromMillis( + value.millisecondsSinceEpoch.toJS) as JSAny; + } + + if (value is Timestamp) { + // Use seconds + nanoseconds directly to preserve sub-millisecond precision. + return interop.TimestampJsImpl(value.seconds.toJS, value.nanoseconds.toJS) + as JSAny; + } + if (value is GeoPoint) { + return interop.GeoPointJsImpl(value.latitude.toJS, value.longitude.toJS) + as JSAny; + } + if (value is Blob) { + return interop.BytesJsImpl.fromUint8Array(value.bytes.toJS) as JSAny; + } + if (value is List) { + return interop.BytesJsImpl.fromUint8Array(Uint8List.fromList(value).toJS) + as JSAny; + } + if (value is VectorValue) { + return interop.vector(value.toArray().jsify()! as JSArray) as JSAny; + } + if (value is Map) { + final path = value['path'] as String; + return interop.doc(_jsFirestore as JSAny, path.toJS) as JSAny; + } + return jsify(value); + } + /// Extracts and safe-casts the 'args' sub-map from an expression map. static Map _argsOf(Map map) { final a = map[_kArgs]; From 760f9629b8e42887c53e6212bc1cc3cdfc8e4fd6 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Sun, 8 Mar 2026 18:16:00 +0000 Subject: [PATCH 71/72] chore: add support for xor --- .../firestore/utils/ExpressionHelpers.java | 25 +++++++++++++++++++ .../firestore/utils/ExpressionParsers.java | 10 ++++++++ .../cloud_firestore/FLTPipelineParser.m | 7 +++--- .../lib/src/interop/firestore_interop.dart | 1 + .../src/pipeline_expression_parser_web.dart | 21 ++++++++++++++++ 5 files changed, 61 insertions(+), 3 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java index 7e2c4020aa6d..86c0877a902c 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java @@ -72,6 +72,31 @@ static BooleanExpression parseOrExpression( return Expression.or(first, rest); } + /** + * Parses a "xor" expression from a list of expression maps. + * + * @param exprMaps List of expression maps to combine with XOR + * @param parser Reference to ExpressionParsers for recursive parsing + */ + @SuppressWarnings("unchecked") + static BooleanExpression parseXorExpression( + @NonNull List> exprMaps, @NonNull ExpressionParsers parser) { + if (exprMaps == null || exprMaps.isEmpty()) { + throw new IllegalArgumentException("'xor' requires at least one expression"); + } + + BooleanExpression first = parser.parseBooleanExpression(exprMaps.get(0)); + if (exprMaps.size() == 1) { + return first; + } + + BooleanExpression[] rest = new BooleanExpression[exprMaps.size() - 1]; + for (int i = 1; i < exprMaps.size(); i++) { + rest[i - 1] = parser.parseBooleanExpression(exprMaps.get(i)); + } + return Expression.xor(first, rest); + } + /** * Parses a constant value based on its type to match Android SDK constant() overloads. Valid * types: String, Number, Boolean, Date, Timestamp, GeoPoint, byte[], Blob, DocumentReference, diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java index 9d52b9f188e5..c937008dc3e2 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java @@ -122,6 +122,11 @@ Expression parseExpression(@NonNull Map expressionMap) { List> exprMaps = (List>) args.get("expressions"); return ExpressionHelpers.parseOrExpression(exprMaps, this); } + case "xor": + { + List> exprMaps = (List>) args.get("expressions"); + return ExpressionHelpers.parseXorExpression(exprMaps, this); + } case "not": { Map exprMap = (Map) args.get("expression"); @@ -209,6 +214,11 @@ BooleanExpression parseBooleanExpression(@NonNull Map expression } return result; } + case "xor": + { + List> exprMaps = (List>) args.get("expressions"); + return ExpressionHelpers.parseXorExpression(exprMaps, this); + } case "not": { Map exprMap = (Map) args.get("expression"); diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m index 03f795ba9987..4dd518d7929a 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m @@ -115,7 +115,7 @@ - (FIRExprBridge *)parseExpression:(NSDictionary *)map error:(NS dispatch_once(&onceToken, ^{ binaryNames = @[ @"equal", @"not_equal", @"greater_than", @"greater_than_or_equal", @"less_than", - @"less_than_or_equal", @"add", @"subtract", @"multiply", @"divide", @"modulo", @"xor" + @"less_than_or_equal", @"add", @"subtract", @"multiply", @"divide", @"modulo" ]; }); if ([binaryNames containsObject:sdkName] || [name isEqualToString:@"bit_xor"]) { @@ -152,9 +152,10 @@ - (FIRExprBridge *)parseExpression:(NSDictionary *)map error:(NS } // ------------------------------------------------------------------------- - // N-ary logical (expressions array): and, or + // N-ary logical (expressions array): and, or, xor // ------------------------------------------------------------------------- - if ([name isEqualToString:@"and"] || [name isEqualToString:@"or"]) { + if ([name isEqualToString:@"and"] || [name isEqualToString:@"or"] || + [name isEqualToString:@"xor"]) { NSArray *exprMaps = args[@"expressions"]; if (![exprMaps isKindOfClass:[NSArray class]] || exprMaps.count == 0) { if (error) 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 40512aa290a7..5ac0014a4800 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 @@ -365,6 +365,7 @@ extension type PipelinesJsImpl._(JSObject _) implements JSObject { external JSAny lessThanOrEqual(JSAny left, JSAny right); external JSAny and(JSAny a, JSAny b); external JSAny or(JSAny a, JSAny b); + external JSAny xor(JSAny a, JSAny b); external JSAny not(JSAny expr); // --- Existence / type checks --- diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_parser_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_parser_web.dart index 5f4768c488cb..4f0690c3eae6 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_parser_web.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_parser_web.dart @@ -84,6 +84,27 @@ class PipelineExpressionParserWeb { case 'less_than_or_equal': return _pipelines.lessThanOrEqual( _expr(argsMap, _kLeft), _expr(argsMap, _kRight)); + case 'and': + case 'or': + case 'xor': + final exprMaps = argsMap['expressions'] as List?; + if (exprMaps == null || exprMaps.isEmpty) return null; + final exprs = exprMaps + .map((e) => toBooleanExpression(e as Map)) + .whereType() + .toList(); + if (exprs.isEmpty) return null; + var result = exprs.first; + for (var i = 1; i < exprs.length; i++) { + if (name == 'and') { + result = _pipelines.and(result, exprs[i]); + } else if (name == 'or') { + result = _pipelines.or(result, exprs[i]); + } else { + result = _pipelines.xor(result, exprs[i]); + } + } + return result; case 'not': return _pipelines.not(_expr(argsMap, _kExpression)); case 'exists': From e3ad0f8a8d33a4484fc763aee7e07753f0521bc8 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Sun, 8 Mar 2026 23:34:27 +0000 Subject: [PATCH 72/72] chore: add arrayContainsAny support and enhance expression handling --- .../lib/src/pipeline_expression.dart | 300 +++++++++++++++++- .../lib/src/interop/firestore_interop.dart | 1 + .../src/pipeline_expression_parser_web.dart | 8 + 3 files changed, 303 insertions(+), 6 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart index 1a4d0a33d702..ee0b7be60c61 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart @@ -11,8 +11,13 @@ mixin PipelineSerializable { /// Helper function to convert values to Expression (wraps in Constant if needed) Expression _toExpression(Object? value) { + if (value == null) return Constant(null); if (value is Expression) return value; - return Constant(value!); + if (value is List) return Expression.array(value.cast()); + if (value is Map) { + return Expression.map(value.cast()); + } + return Constant(value); } /// Base class for all pipeline expressions @@ -440,6 +445,12 @@ abstract class Expression implements PipelineSerializable { return _ArrayContainsExpression(this, _toExpression(element)); } + /// Checks if this array contains any of the given values or expressions + BooleanExpression arrayContainsAny(List values) { + return _ArrayContainsAnyExpression( + this, values.map(_toExpression).toList()); + } + /// Returns the length of this array expression // ignore: use_to_and_as_if_applicable Expression arrayLength() { @@ -764,6 +775,204 @@ abstract class Expression implements PipelineSerializable { return _NotExpression(expression); } + /// Combines boolean expressions with a logical XOR + static BooleanExpression xor( + BooleanExpression expression1, [ + BooleanExpression? expression2, + BooleanExpression? expression3, + BooleanExpression? expression4, + BooleanExpression? expression5, + BooleanExpression? expression6, + BooleanExpression? expression7, + BooleanExpression? expression8, + BooleanExpression? expression9, + BooleanExpression? expression10, + BooleanExpression? expression11, + BooleanExpression? expression12, + BooleanExpression? expression13, + BooleanExpression? expression14, + BooleanExpression? expression15, + BooleanExpression? expression16, + BooleanExpression? expression17, + BooleanExpression? expression18, + BooleanExpression? expression19, + BooleanExpression? expression20, + BooleanExpression? expression21, + BooleanExpression? expression22, + BooleanExpression? expression23, + BooleanExpression? expression24, + BooleanExpression? expression25, + BooleanExpression? expression26, + BooleanExpression? expression27, + BooleanExpression? expression28, + BooleanExpression? expression29, + BooleanExpression? expression30, + ]) { + final expressions = [expression1]; + if (expression2 != null) expressions.add(expression2); + if (expression3 != null) expressions.add(expression3); + if (expression4 != null) expressions.add(expression4); + if (expression5 != null) expressions.add(expression5); + if (expression6 != null) expressions.add(expression6); + if (expression7 != null) expressions.add(expression7); + if (expression8 != null) expressions.add(expression8); + if (expression9 != null) expressions.add(expression9); + if (expression10 != null) expressions.add(expression10); + if (expression11 != null) expressions.add(expression11); + if (expression12 != null) expressions.add(expression12); + if (expression13 != null) expressions.add(expression13); + if (expression14 != null) expressions.add(expression14); + if (expression15 != null) expressions.add(expression15); + if (expression16 != null) expressions.add(expression16); + if (expression17 != null) expressions.add(expression17); + if (expression18 != null) expressions.add(expression18); + if (expression19 != null) expressions.add(expression19); + if (expression20 != null) expressions.add(expression20); + if (expression21 != null) expressions.add(expression21); + if (expression22 != null) expressions.add(expression22); + if (expression23 != null) expressions.add(expression23); + if (expression24 != null) expressions.add(expression24); + if (expression25 != null) expressions.add(expression25); + if (expression26 != null) expressions.add(expression26); + if (expression27 != null) expressions.add(expression27); + if (expression28 != null) expressions.add(expression28); + if (expression29 != null) expressions.add(expression29); + if (expression30 != null) expressions.add(expression30); + return _XorExpression(expressions); + } + + /// Combines boolean expressions with a logical AND + static BooleanExpression and( + BooleanExpression expression1, [ + BooleanExpression? expression2, + BooleanExpression? expression3, + BooleanExpression? expression4, + BooleanExpression? expression5, + BooleanExpression? expression6, + BooleanExpression? expression7, + BooleanExpression? expression8, + BooleanExpression? expression9, + BooleanExpression? expression10, + BooleanExpression? expression11, + BooleanExpression? expression12, + BooleanExpression? expression13, + BooleanExpression? expression14, + BooleanExpression? expression15, + BooleanExpression? expression16, + BooleanExpression? expression17, + BooleanExpression? expression18, + BooleanExpression? expression19, + BooleanExpression? expression20, + BooleanExpression? expression21, + BooleanExpression? expression22, + BooleanExpression? expression23, + BooleanExpression? expression24, + BooleanExpression? expression25, + BooleanExpression? expression26, + BooleanExpression? expression27, + BooleanExpression? expression28, + BooleanExpression? expression29, + BooleanExpression? expression30, + ]) { + final expressions = [expression1]; + if (expression2 != null) expressions.add(expression2); + if (expression3 != null) expressions.add(expression3); + if (expression4 != null) expressions.add(expression4); + if (expression5 != null) expressions.add(expression5); + if (expression6 != null) expressions.add(expression6); + if (expression7 != null) expressions.add(expression7); + if (expression8 != null) expressions.add(expression8); + if (expression9 != null) expressions.add(expression9); + if (expression10 != null) expressions.add(expression10); + if (expression11 != null) expressions.add(expression11); + if (expression12 != null) expressions.add(expression12); + if (expression13 != null) expressions.add(expression13); + if (expression14 != null) expressions.add(expression14); + if (expression15 != null) expressions.add(expression15); + if (expression16 != null) expressions.add(expression16); + if (expression17 != null) expressions.add(expression17); + if (expression18 != null) expressions.add(expression18); + if (expression19 != null) expressions.add(expression19); + if (expression20 != null) expressions.add(expression20); + if (expression21 != null) expressions.add(expression21); + if (expression22 != null) expressions.add(expression22); + if (expression23 != null) expressions.add(expression23); + if (expression24 != null) expressions.add(expression24); + if (expression25 != null) expressions.add(expression25); + if (expression26 != null) expressions.add(expression26); + if (expression27 != null) expressions.add(expression27); + if (expression28 != null) expressions.add(expression28); + if (expression29 != null) expressions.add(expression29); + if (expression30 != null) expressions.add(expression30); + return _AndExpression(expressions); + } + + /// Combines boolean expressions with a logical OR + static BooleanExpression or( + BooleanExpression expression1, [ + BooleanExpression? expression2, + BooleanExpression? expression3, + BooleanExpression? expression4, + BooleanExpression? expression5, + BooleanExpression? expression6, + BooleanExpression? expression7, + BooleanExpression? expression8, + BooleanExpression? expression9, + BooleanExpression? expression10, + BooleanExpression? expression11, + BooleanExpression? expression12, + BooleanExpression? expression13, + BooleanExpression? expression14, + BooleanExpression? expression15, + BooleanExpression? expression16, + BooleanExpression? expression17, + BooleanExpression? expression18, + BooleanExpression? expression19, + BooleanExpression? expression20, + BooleanExpression? expression21, + BooleanExpression? expression22, + BooleanExpression? expression23, + BooleanExpression? expression24, + BooleanExpression? expression25, + BooleanExpression? expression26, + BooleanExpression? expression27, + BooleanExpression? expression28, + BooleanExpression? expression29, + BooleanExpression? expression30, + ]) { + final expressions = [expression1]; + if (expression2 != null) expressions.add(expression2); + if (expression3 != null) expressions.add(expression3); + if (expression4 != null) expressions.add(expression4); + if (expression5 != null) expressions.add(expression5); + if (expression6 != null) expressions.add(expression6); + if (expression7 != null) expressions.add(expression7); + if (expression8 != null) expressions.add(expression8); + if (expression9 != null) expressions.add(expression9); + if (expression10 != null) expressions.add(expression10); + if (expression11 != null) expressions.add(expression11); + if (expression12 != null) expressions.add(expression12); + if (expression13 != null) expressions.add(expression13); + if (expression14 != null) expressions.add(expression14); + if (expression15 != null) expressions.add(expression15); + if (expression16 != null) expressions.add(expression16); + if (expression17 != null) expressions.add(expression17); + if (expression18 != null) expressions.add(expression18); + if (expression19 != null) expressions.add(expression19); + if (expression20 != null) expressions.add(expression20); + if (expression21 != null) expressions.add(expression21); + if (expression22 != null) expressions.add(expression22); + if (expression23 != null) expressions.add(expression23); + if (expression24 != null) expressions.add(expression24); + if (expression25 != null) expressions.add(expression25); + if (expression26 != null) expressions.add(expression26); + if (expression27 != null) expressions.add(expression27); + if (expression28 != null) expressions.add(expression28); + if (expression29 != null) expressions.add(expression29); + if (expression30 != null) expressions.add(expression30); + return _OrExpression(expressions); + } + /// Joins array elements with a delimiter static Expression joinStatic( Expression arrayExpression, @@ -1490,7 +1699,7 @@ class PipelineFilter extends BooleanExpression { final Object? isGreaterThan; final Object? isGreaterThanOrEqualTo; final Object? arrayContains; - final List? arrayContainsAny; + final List? filterArrayContainsAny; final List? whereIn; final List? whereNotIn; final bool? isNull; @@ -1507,7 +1716,7 @@ class PipelineFilter extends BooleanExpression { this.isGreaterThan, this.isGreaterThanOrEqualTo, this.arrayContains, - this.arrayContainsAny, + this.filterArrayContainsAny, this.whereIn, this.whereNotIn, this.isNull, @@ -1526,7 +1735,7 @@ class PipelineFilter extends BooleanExpression { isGreaterThan = null, isGreaterThanOrEqualTo = null, arrayContains = null, - arrayContainsAny = null, + filterArrayContainsAny = null, whereIn = null, whereNotIn = null, isNull = null, @@ -1733,8 +1942,8 @@ class PipelineFilter extends BooleanExpression { args['isGreaterThanOrEqualTo'] = isGreaterThanOrEqualTo; } if (arrayContains != null) args['arrayContains'] = arrayContains; - if (arrayContainsAny != null) { - args['arrayContainsAny'] = arrayContainsAny; + if (filterArrayContainsAny != null) { + args['arrayContainsAny'] = filterArrayContainsAny; } if (whereIn != null) args['whereIn'] = whereIn; if (whereNotIn != null) args['whereNotIn'] = whereNotIn; @@ -2100,6 +2309,28 @@ class _ArrayContainsExpression extends BooleanExpression { } } +/// Represents an arrayContainsAny function expression +class _ArrayContainsAnyExpression extends BooleanExpression { + final Expression array; + final List values; + + _ArrayContainsAnyExpression(this.array, this.values); + + @override + String get name => 'array_contains_any'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'array': array.toMap(), + 'values': values.map((v) => v.toMap()).toList(), + }, + }; + } +} + /// Represents an array length function expression class _ArrayLengthExpression extends FunctionExpression { final Expression expression; @@ -2312,6 +2543,63 @@ class _NotExpression extends BooleanExpression { } } +class _XorExpression extends BooleanExpression { + final List expressions; + + _XorExpression(this.expressions); + + @override + String get name => 'xor'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expressions': expressions.map((e) => e.toMap()).toList(), + }, + }; + } +} + +class _AndExpression extends BooleanExpression { + final List expressions; + + _AndExpression(this.expressions); + + @override + String get name => 'and'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expressions': expressions.map((e) => e.toMap()).toList(), + }, + }; + } +} + +class _OrExpression extends BooleanExpression { + final List expressions; + + _OrExpression(this.expressions); + + @override + String get name => 'or'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expressions': expressions.map((e) => e.toMap()).toList(), + }, + }; + } +} + /// Represents a conditional (ternary) function expression class _ConditionalExpression extends FunctionExpression { final BooleanExpression condition; 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 5ac0014a4800..e8d7d69d687f 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 @@ -375,6 +375,7 @@ extension type PipelinesJsImpl._(JSObject _) implements JSObject { // --- Array --- external JSAny arrayContains(JSAny array, JSAny element); + external JSAny arrayContainsAny(JSAny array, JSArray values); // --- Ordering (for sort stage) --- external JSAny ascending(JSAny expr); diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_parser_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_parser_web.dart index 4f0690c3eae6..d7ff79df173f 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_parser_web.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/pipeline_expression_parser_web.dart @@ -116,6 +116,14 @@ class PipelineExpressionParserWeb { case 'array_contains': return _pipelines.arrayContains( _expr(argsMap, 'array'), _expr(argsMap, 'element')); + case 'array_contains_any': + final valuesMaps = argsMap['values'] as List?; + if (valuesMaps == null || valuesMaps.isEmpty) return null; + final valuesJs = valuesMaps + .map((v) => toExpression(v as Map)) + .toList() + .toJS; + return _pipelines.arrayContainsAny(_expr(argsMap, 'array'), valuesJs); case 'filter': return _buildFilterExpression(argsMap); default: