Skip to content

Commit 0f6e2ab

Browse files
authored
feat(firestore, android): Android implementation for Pipeline APIs (#18098)
* feat(firestore): Android implementation for Pipeline APIs * refactor: improve handling of timestamps and array expressions in pipeline * chore: add support for more Expression functions in Android * chore: remove unsupported 'replace' expression from Android Firestore pipeline API * chore: enhance PipelineParser to support execute options for Firestore pipelines * chore: remove commented-out button for stringReplaceAll in pipeline example * trigger CI * trigger CI * trigger CI * refactor: enhance expression parsing logic in ExpressionParsers
1 parent 59d4b14 commit 0f6e2ab

6 files changed

Lines changed: 1631 additions & 7 deletions

File tree

packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import com.google.firebase.firestore.MemoryCacheSettings;
2929
import com.google.firebase.firestore.PersistentCacheIndexManager;
3030
import com.google.firebase.firestore.PersistentCacheSettings;
31+
import com.google.firebase.firestore.Pipeline;
32+
import com.google.firebase.firestore.PipelineResult;
3133
import com.google.firebase.firestore.Query;
3234
import com.google.firebase.firestore.QuerySnapshot;
3335
import com.google.firebase.firestore.SetOptions;
@@ -52,6 +54,7 @@
5254
import io.flutter.plugins.firebase.firestore.streamhandler.TransactionStreamHandler;
5355
import io.flutter.plugins.firebase.firestore.utils.ExceptionConverter;
5456
import io.flutter.plugins.firebase.firestore.utils.PigeonParser;
57+
import io.flutter.plugins.firebase.firestore.utils.PipelineParser;
5558
import java.util.ArrayList;
5659
import java.util.HashMap;
5760
import java.util.List;
@@ -1007,4 +1010,65 @@ public void documentReferenceSnapshot(
10071010
parameters.getServerTimestampBehavior()),
10081011
PigeonParser.parseListenSource(source))));
10091012
}
1013+
1014+
@Override
1015+
public void executePipeline(
1016+
@NonNull GeneratedAndroidFirebaseFirestore.FirestorePigeonFirebaseApp app,
1017+
@NonNull List<Map<String, Object>> stages,
1018+
@Nullable Map<String, Object> options,
1019+
@NonNull
1020+
GeneratedAndroidFirebaseFirestore.Result<
1021+
GeneratedAndroidFirebaseFirestore.PigeonPipelineSnapshot>
1022+
result) {
1023+
cachedThreadPool.execute(
1024+
() -> {
1025+
try {
1026+
FirebaseFirestore firestore = getFirestoreFromPigeon(app);
1027+
1028+
// Execute pipeline using Android Firestore SDK
1029+
Pipeline.Snapshot snapshot = PipelineParser.executePipeline(firestore, stages, options);
1030+
1031+
// Convert Pipeline.Snapshot to PigeonPipelineSnapshot
1032+
List<GeneratedAndroidFirebaseFirestore.PigeonPipelineResult> pipelineResults =
1033+
new ArrayList<>();
1034+
1035+
// Iterate through snapshot results
1036+
for (PipelineResult pipelineResult : snapshot.getResults()) {
1037+
GeneratedAndroidFirebaseFirestore.PigeonPipelineResult.Builder resultBuilder =
1038+
new GeneratedAndroidFirebaseFirestore.PigeonPipelineResult.Builder();
1039+
if (pipelineResult.getRef() != null) {
1040+
resultBuilder.setDocumentPath(pipelineResult.getRef().getPath());
1041+
}
1042+
1043+
if (pipelineResult.getCreateTime() != null) {
1044+
resultBuilder.setCreateTime(pipelineResult.getCreateTime().toDate().getTime());
1045+
}
1046+
if (pipelineResult.getUpdateTime() != null) {
1047+
resultBuilder.setUpdateTime(pipelineResult.getUpdateTime().toDate().getTime());
1048+
}
1049+
1050+
Map<String, Object> data = pipelineResult.getData();
1051+
if (data != null) {
1052+
resultBuilder.setData(data);
1053+
}
1054+
1055+
pipelineResults.add(resultBuilder.build());
1056+
}
1057+
1058+
// Build the snapshot
1059+
GeneratedAndroidFirebaseFirestore.PigeonPipelineSnapshot.Builder snapshotBuilder =
1060+
new GeneratedAndroidFirebaseFirestore.PigeonPipelineSnapshot.Builder();
1061+
snapshotBuilder.setResults(pipelineResults);
1062+
1063+
// Set execution time when available. Do not fabricate a value when null.
1064+
if (snapshot.getExecutionTime() != null) {
1065+
snapshotBuilder.setExecutionTime(snapshot.getExecutionTime().toDate().getTime());
1066+
}
1067+
1068+
result.success(snapshotBuilder.build());
1069+
} catch (Exception e) {
1070+
ExceptionConverter.sendErrorToFlutter(result, e);
1071+
}
1072+
});
1073+
}
10101074
}

packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java

Lines changed: 247 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,197 @@ public ArrayList<Object> toList() {
856856
}
857857
}
858858

859+
/** Generated class from Pigeon that represents data sent in messages. */
860+
public static final class PigeonPipelineResult {
861+
private @Nullable String documentPath;
862+
863+
public @Nullable String getDocumentPath() {
864+
return documentPath;
865+
}
866+
867+
public void setDocumentPath(@Nullable String setterArg) {
868+
this.documentPath = setterArg;
869+
}
870+
871+
private @Nullable Long createTime;
872+
873+
public @Nullable Long getCreateTime() {
874+
return createTime;
875+
}
876+
877+
public void setCreateTime(@Nullable Long setterArg) {
878+
this.createTime = setterArg;
879+
}
880+
881+
private @Nullable Long updateTime;
882+
883+
public @Nullable Long getUpdateTime() {
884+
return updateTime;
885+
}
886+
887+
public void setUpdateTime(@Nullable Long setterArg) {
888+
this.updateTime = setterArg;
889+
}
890+
891+
/** All fields in the result (from PipelineResult.data() on Android). */
892+
private @Nullable Map<String, Object> data;
893+
894+
public @Nullable Map<String, Object> getData() {
895+
return data;
896+
}
897+
898+
public void setData(@Nullable Map<String, Object> setterArg) {
899+
this.data = setterArg;
900+
}
901+
902+
public static final class Builder {
903+
904+
private @Nullable String documentPath;
905+
906+
public @NonNull Builder setDocumentPath(@Nullable String setterArg) {
907+
this.documentPath = setterArg;
908+
return this;
909+
}
910+
911+
private @Nullable Long createTime;
912+
913+
public @NonNull Builder setCreateTime(@Nullable Long setterArg) {
914+
this.createTime = setterArg;
915+
return this;
916+
}
917+
918+
private @Nullable Long updateTime;
919+
920+
public @NonNull Builder setUpdateTime(@Nullable Long setterArg) {
921+
this.updateTime = setterArg;
922+
return this;
923+
}
924+
925+
private @Nullable Map<String, Object> data;
926+
927+
public @NonNull Builder setData(@Nullable Map<String, Object> setterArg) {
928+
this.data = setterArg;
929+
return this;
930+
}
931+
932+
public @NonNull PigeonPipelineResult build() {
933+
PigeonPipelineResult pigeonReturn = new PigeonPipelineResult();
934+
pigeonReturn.setDocumentPath(documentPath);
935+
pigeonReturn.setCreateTime(createTime);
936+
pigeonReturn.setUpdateTime(updateTime);
937+
pigeonReturn.setData(data);
938+
return pigeonReturn;
939+
}
940+
}
941+
942+
@NonNull
943+
public ArrayList<Object> toList() {
944+
ArrayList<Object> toListResult = new ArrayList<Object>(4);
945+
toListResult.add(documentPath);
946+
toListResult.add(createTime);
947+
toListResult.add(updateTime);
948+
toListResult.add(data);
949+
return toListResult;
950+
}
951+
952+
static @NonNull PigeonPipelineResult fromList(@NonNull ArrayList<Object> list) {
953+
PigeonPipelineResult pigeonResult = new PigeonPipelineResult();
954+
Object documentPath = list.get(0);
955+
pigeonResult.setDocumentPath((String) documentPath);
956+
Object createTime = list.get(1);
957+
pigeonResult.setCreateTime(
958+
(createTime == null)
959+
? null
960+
: ((createTime instanceof Integer) ? (Integer) createTime : (Long) createTime));
961+
Object updateTime = list.get(2);
962+
pigeonResult.setUpdateTime(
963+
(updateTime == null)
964+
? null
965+
: ((updateTime instanceof Integer) ? (Integer) updateTime : (Long) updateTime));
966+
Object data = list.get(3);
967+
pigeonResult.setData((Map<String, Object>) data);
968+
return pigeonResult;
969+
}
970+
}
971+
972+
/** Generated class from Pigeon that represents data sent in messages. */
973+
public static final class PigeonPipelineSnapshot {
974+
private @NonNull List<PigeonPipelineResult> results;
975+
976+
public @NonNull List<PigeonPipelineResult> getResults() {
977+
return results;
978+
}
979+
980+
public void setResults(@NonNull List<PigeonPipelineResult> setterArg) {
981+
if (setterArg == null) {
982+
throw new IllegalStateException("Nonnull field \"results\" is null.");
983+
}
984+
this.results = setterArg;
985+
}
986+
987+
private @NonNull Long executionTime;
988+
989+
public @NonNull Long getExecutionTime() {
990+
return executionTime;
991+
}
992+
993+
public void setExecutionTime(@NonNull Long setterArg) {
994+
if (setterArg == null) {
995+
throw new IllegalStateException("Nonnull field \"executionTime\" is null.");
996+
}
997+
this.executionTime = setterArg;
998+
}
999+
1000+
/** Constructor is non-public to enforce null safety; use Builder. */
1001+
PigeonPipelineSnapshot() {}
1002+
1003+
public static final class Builder {
1004+
1005+
private @Nullable List<PigeonPipelineResult> results;
1006+
1007+
public @NonNull Builder setResults(@NonNull List<PigeonPipelineResult> setterArg) {
1008+
this.results = setterArg;
1009+
return this;
1010+
}
1011+
1012+
private @Nullable Long executionTime;
1013+
1014+
public @NonNull Builder setExecutionTime(@NonNull Long setterArg) {
1015+
this.executionTime = setterArg;
1016+
return this;
1017+
}
1018+
1019+
public @NonNull PigeonPipelineSnapshot build() {
1020+
PigeonPipelineSnapshot pigeonReturn = new PigeonPipelineSnapshot();
1021+
pigeonReturn.setResults(results);
1022+
pigeonReturn.setExecutionTime(executionTime);
1023+
return pigeonReturn;
1024+
}
1025+
}
1026+
1027+
@NonNull
1028+
public ArrayList<Object> toList() {
1029+
ArrayList<Object> toListResult = new ArrayList<Object>(2);
1030+
toListResult.add(results);
1031+
toListResult.add(executionTime);
1032+
return toListResult;
1033+
}
1034+
1035+
static @NonNull PigeonPipelineSnapshot fromList(@NonNull ArrayList<Object> list) {
1036+
PigeonPipelineSnapshot pigeonResult = new PigeonPipelineSnapshot();
1037+
Object results = list.get(0);
1038+
pigeonResult.setResults((List<PigeonPipelineResult>) results);
1039+
Object executionTime = list.get(1);
1040+
pigeonResult.setExecutionTime(
1041+
(executionTime == null)
1042+
? null
1043+
: ((executionTime instanceof Integer)
1044+
? (Integer) executionTime
1045+
: (Long) executionTime));
1046+
return pigeonResult;
1047+
}
1048+
}
1049+
8591050
/** Generated class from Pigeon that represents data sent in messages. */
8601051
public static final class PigeonGetOptions {
8611052
private @NonNull Source source;
@@ -1660,12 +1851,16 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) {
16601851
case (byte) 136:
16611852
return PigeonGetOptions.fromList((ArrayList<Object>) readValue(buffer));
16621853
case (byte) 137:
1663-
return PigeonQueryParameters.fromList((ArrayList<Object>) readValue(buffer));
1854+
return PigeonPipelineResult.fromList((ArrayList<Object>) readValue(buffer));
16641855
case (byte) 138:
1665-
return PigeonQuerySnapshot.fromList((ArrayList<Object>) readValue(buffer));
1856+
return PigeonPipelineSnapshot.fromList((ArrayList<Object>) readValue(buffer));
16661857
case (byte) 139:
1667-
return PigeonSnapshotMetadata.fromList((ArrayList<Object>) readValue(buffer));
1858+
return PigeonQueryParameters.fromList((ArrayList<Object>) readValue(buffer));
16681859
case (byte) 140:
1860+
return PigeonQuerySnapshot.fromList((ArrayList<Object>) readValue(buffer));
1861+
case (byte) 141:
1862+
return PigeonSnapshotMetadata.fromList((ArrayList<Object>) readValue(buffer));
1863+
case (byte) 142:
16691864
return PigeonTransactionCommand.fromList((ArrayList<Object>) readValue(buffer));
16701865
default:
16711866
return super.readValueOfType(type, buffer);
@@ -1701,17 +1896,23 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) {
17011896
} else if (value instanceof PigeonGetOptions) {
17021897
stream.write(136);
17031898
writeValue(stream, ((PigeonGetOptions) value).toList());
1704-
} else if (value instanceof PigeonQueryParameters) {
1899+
} else if (value instanceof PigeonPipelineResult) {
17051900
stream.write(137);
1901+
writeValue(stream, ((PigeonPipelineResult) value).toList());
1902+
} else if (value instanceof PigeonPipelineSnapshot) {
1903+
stream.write(138);
1904+
writeValue(stream, ((PigeonPipelineSnapshot) value).toList());
1905+
} else if (value instanceof PigeonQueryParameters) {
1906+
stream.write(139);
17061907
writeValue(stream, ((PigeonQueryParameters) value).toList());
17071908
} else if (value instanceof PigeonQuerySnapshot) {
1708-
stream.write(138);
1909+
stream.write(140);
17091910
writeValue(stream, ((PigeonQuerySnapshot) value).toList());
17101911
} else if (value instanceof PigeonSnapshotMetadata) {
1711-
stream.write(139);
1912+
stream.write(141);
17121913
writeValue(stream, ((PigeonSnapshotMetadata) value).toList());
17131914
} else if (value instanceof PigeonTransactionCommand) {
1714-
stream.write(140);
1915+
stream.write(142);
17151916
writeValue(stream, ((PigeonTransactionCommand) value).toList());
17161917
} else {
17171918
super.writeValue(stream, value);
@@ -1836,6 +2037,12 @@ void persistenceCacheIndexManagerRequest(
18362037
@NonNull PersistenceCacheIndexManagerRequest request,
18372038
@NonNull Result<Void> result);
18382039

2040+
void executePipeline(
2041+
@NonNull FirestorePigeonFirebaseApp app,
2042+
@NonNull List<Map<String, Object>> stages,
2043+
@Nullable Map<String, Object> options,
2044+
@NonNull Result<PigeonPipelineSnapshot> result);
2045+
18392046
/** The codec used by FirebaseFirestoreHostApi. */
18402047
static @NonNull MessageCodec<Object> getCodec() {
18412048
return FirebaseFirestoreHostApiCodec.INSTANCE;
@@ -2624,6 +2831,39 @@ public void error(Throwable error) {
26242831
channel.setMessageHandler(null);
26252832
}
26262833
}
2834+
{
2835+
BasicMessageChannel<Object> channel =
2836+
new BasicMessageChannel<>(
2837+
binaryMessenger,
2838+
"dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline",
2839+
getCodec());
2840+
if (api != null) {
2841+
channel.setMessageHandler(
2842+
(message, reply) -> {
2843+
ArrayList<Object> wrapped = new ArrayList<Object>();
2844+
ArrayList<Object> args = (ArrayList<Object>) message;
2845+
FirestorePigeonFirebaseApp appArg = (FirestorePigeonFirebaseApp) args.get(0);
2846+
List<Map<String, Object>> stagesArg = (List<Map<String, Object>>) args.get(1);
2847+
Map<String, Object> optionsArg = (Map<String, Object>) args.get(2);
2848+
Result<PigeonPipelineSnapshot> resultCallback =
2849+
new Result<PigeonPipelineSnapshot>() {
2850+
public void success(PigeonPipelineSnapshot result) {
2851+
wrapped.add(0, result);
2852+
reply.reply(wrapped);
2853+
}
2854+
2855+
public void error(Throwable error) {
2856+
ArrayList<Object> wrappedError = wrapError(error);
2857+
reply.reply(wrappedError);
2858+
}
2859+
};
2860+
2861+
api.executePipeline(appArg, stagesArg, optionsArg, resultCallback);
2862+
});
2863+
} else {
2864+
channel.setMessageHandler(null);
2865+
}
2866+
}
26272867
}
26282868
}
26292869
}

0 commit comments

Comments
 (0)