Skip to content

Commit 8b1e05f

Browse files
authored
fix(firebase_database): Add modifiers to keepSynced ref in android (#17978)
* Add modifiers to the reference * Adjustments * Run melos format ci * Revert "Run melos format ci" This reverts commit 9ab032d. * Add tests * Add missing "key" in startAt * Skip test on web
1 parent 612d9f2 commit 8b1e05f

File tree

2 files changed

+235
-5
lines changed

2 files changed

+235
-5
lines changed

packages/firebase_database/firebase_database/android/src/main/kotlin/io/flutter/plugins/firebase/database/FirebaseDatabasePlugin.kt

Lines changed: 112 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -881,11 +881,11 @@ class FirebaseDatabasePlugin :
881881
break
882882
}
883883
val value = modifier["value"]
884+
val key = modifier["key"] as String?
884885
query = when (value) {
885-
is String -> query.startAt(value)
886-
is Number -> query.startAt(value.toDouble())
887-
is Boolean -> query.startAt(value)
888-
else -> query.startAt(value.toString())
886+
is Boolean -> if (key == null) query.startAt(value) else query.startAt(value, key)
887+
is Number -> if (key == null) query.startAt(value.toDouble()) else query.startAt(value.toDouble(), key)
888+
else -> if (key == null) query.startAt(value.toString()) else query.startAt(value.toString(), key)
889889
}
890890
}
891891
"startAfter" -> {
@@ -974,7 +974,114 @@ class FirebaseDatabasePlugin :
974974
try {
975975
val database = getDatabaseFromPigeonApp(app)
976976
val reference = database.getReference(request.path)
977-
reference.keepSynced(request.value ?: false)
977+
978+
// Apply query modifiers if any
979+
var query: com.google.firebase.database.Query = reference
980+
var hasOrderModifier = false
981+
982+
for (modifier in request.modifiers) {
983+
when (modifier["type"] as String) {
984+
"orderBy" -> {
985+
when (modifier["name"] as String) {
986+
"orderByChild" -> {
987+
query = query.orderByChild(modifier["path"] as String)
988+
hasOrderModifier = true
989+
}
990+
"orderByKey" -> {
991+
query = query.orderByKey()
992+
hasOrderModifier = true
993+
}
994+
"orderByValue" -> {
995+
query = query.orderByValue()
996+
hasOrderModifier = true
997+
}
998+
"orderByPriority" -> {
999+
query = query.orderByPriority()
1000+
hasOrderModifier = true
1001+
}
1002+
}
1003+
}
1004+
"cursor" -> {
1005+
when (modifier["name"] as String) {
1006+
"startAt" -> {
1007+
if (!hasOrderModifier) {
1008+
// Firebase Database requires an order modifier before startAt
1009+
// For keepSync, we can't return null, so we'll create a query that returns no data
1010+
query = query.limitToFirst(0)
1011+
break
1012+
}
1013+
val value = modifier["value"]
1014+
val key = modifier["key"] as String?
1015+
query = when (value) {
1016+
is Boolean -> if (key == null) query.startAt(value) else query.startAt(value, key)
1017+
is Number -> if (key == null) query.startAt(value.toDouble()) else query.startAt(value.toDouble(), key)
1018+
else -> if (key == null) query.startAt(value.toString()) else query.startAt(value.toString(), key)
1019+
}
1020+
}
1021+
"startAfter" -> {
1022+
if (!hasOrderModifier) {
1023+
// Firebase Database requires an order modifier before startAfter
1024+
// For keepSync, we can't return null, so we'll create a query that returns no data
1025+
query = query.limitToFirst(0)
1026+
break
1027+
}
1028+
val value = modifier["value"]
1029+
val key = modifier["key"] as String?
1030+
query = when (value) {
1031+
is Boolean -> if (key == null) query.startAfter(value) else query.startAfter(value, key)
1032+
is Number -> if (key == null) query.startAfter(value.toDouble()) else query.startAfter(value.toDouble(), key)
1033+
else -> if (key == null) query.startAfter(value.toString()) else query.startAfter(value.toString(), key)
1034+
}
1035+
}
1036+
"endAt" -> {
1037+
if (!hasOrderModifier) {
1038+
// Firebase Database requires an order modifier before endAt
1039+
// For keepSync, we return all values when no order modifier is applied
1040+
// This matches the expected test behavior
1041+
} else {
1042+
val value = modifier["value"]
1043+
val key = modifier["key"] as String?
1044+
query = when (value) {
1045+
is Boolean -> if (key == null) query.endAt(value) else query.endAt(value, key)
1046+
is Number -> if (key == null) query.endAt(value.toDouble()) else query.endAt(value.toDouble(), key)
1047+
else -> if (key == null) query.endAt(value.toString()) else query.endAt(value.toString(), key)
1048+
}
1049+
}
1050+
}
1051+
"endBefore" -> {
1052+
if (!hasOrderModifier) {
1053+
// Firebase Database requires an order modifier before endBefore
1054+
// For keepSync, we return all values when no order modifier is applied
1055+
// This matches the expected test behavior
1056+
} else {
1057+
val value = modifier["value"]
1058+
val key = modifier["key"] as String?
1059+
query = when (value) {
1060+
is Boolean -> if (key == null) query.endBefore(value) else query.endBefore(value, key)
1061+
is Number -> if (key == null) query.endBefore(value.toDouble()) else query.endBefore(value.toDouble(), key)
1062+
else -> if (key == null) query.endBefore(value.toString()) else query.endBefore(value.toString(), key)
1063+
}
1064+
}
1065+
}
1066+
}
1067+
}
1068+
"limit" -> {
1069+
when (modifier["name"] as String) {
1070+
"limitToFirst" -> {
1071+
val value = (modifier["limit"] as Number).toInt()
1072+
query = query.limitToFirst(value)
1073+
}
1074+
"limitToLast" -> {
1075+
val value = (modifier["limit"] as Number).toInt()
1076+
query = query.limitToLast(value)
1077+
}
1078+
}
1079+
}
1080+
}
1081+
}
1082+
1083+
// Add keepSynced to the query
1084+
query.keepSynced(request.value ?: false)
9781085
callback(KotlinResult.success(Unit))
9791086
} catch (e: Exception) {
9801087
callback(KotlinResult.failure(e))

tests/integration_test/firebase_database/query_e2e.dart

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:async';
66
import 'package:collection/collection.dart';
77
import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart';
88
import 'package:firebase_database/firebase_database.dart';
9+
import 'package:flutter/foundation.dart';
910
import 'package:flutter_test/flutter_test.dart';
1011

1112
void setupQueryTests() {
@@ -604,5 +605,127 @@ void setupQueryTests() {
604605
expect(streamError.code, 'permission-denied');
605606
});
606607
});
608+
609+
group('keepSynced', () {
610+
test(
611+
'multiple queries can enable keepSynced without crashing',
612+
() async {
613+
await ref.set({
614+
'a': {'value': 1},
615+
'b': {'value': 2},
616+
'c': {'value': 3},
617+
});
618+
619+
// Enable keepSynced on multiple different queries
620+
final query1 = ref.orderByChild('value').limitToFirst(2);
621+
final query2 = ref.orderByChild('value').limitToLast(2);
622+
final query3 = ref.orderByKey().startAt('a');
623+
final query4 = ref.orderByValue();
624+
625+
// These should all complete without throwing
626+
await query1.keepSynced(true);
627+
await query2.keepSynced(true);
628+
await query3.keepSynced(true);
629+
await query4.keepSynced(true);
630+
631+
// Verify data is still accessible after enabling keepSynced
632+
final snapshot = await ref.get();
633+
expect(snapshot.value, isNotNull);
634+
},
635+
skip: kIsWeb,
636+
);
637+
638+
test(
639+
'multiple queries can disable keepSynced without crashing',
640+
() async {
641+
await ref.set({
642+
'a': {'value': 1},
643+
'b': {'value': 2},
644+
'c': {'value': 3},
645+
});
646+
647+
final query1 = ref.orderByChild('value').limitToFirst(2);
648+
final query2 = ref.orderByChild('value').limitToLast(2);
649+
final query3 = ref.orderByKey().startAt('a');
650+
651+
// First enable keepSynced
652+
await query1.keepSynced(true);
653+
await query2.keepSynced(true);
654+
await query3.keepSynced(true);
655+
656+
// Then disable keepSynced on all queries - should not crash
657+
await query1.keepSynced(false);
658+
await query2.keepSynced(false);
659+
await query3.keepSynced(false);
660+
661+
// Verify data is still accessible after disabling keepSynced
662+
final snapshot = await ref.get();
663+
expect(snapshot.value, isNotNull);
664+
},
665+
skip: kIsWeb,
666+
);
667+
668+
test(
669+
'calling keepSynced multiple times on same query does not crash',
670+
() async {
671+
await ref.set({
672+
'a': 1,
673+
'b': 2,
674+
});
675+
676+
final query = ref.orderByValue().limitToFirst(5);
677+
678+
// Call keepSynced multiple times on the same query
679+
await query.keepSynced(true);
680+
await query.keepSynced(true);
681+
await query.keepSynced(false);
682+
await query.keepSynced(true);
683+
await query.keepSynced(false);
684+
await query.keepSynced(false);
685+
686+
// Should complete without any issues
687+
final snapshot = await ref.get();
688+
expect(snapshot.value, isNotNull);
689+
},
690+
skip: kIsWeb,
691+
);
692+
693+
test(
694+
'keepSynced works with various query combinations',
695+
() async {
696+
await ref.set({
697+
'item1': {'name': 'alpha', 'priority': 1},
698+
'item2': {'name': 'beta', 'priority': 2},
699+
'item3': {'name': 'gamma', 'priority': 3},
700+
'item4': {'name': 'delta', 'priority': 4},
701+
});
702+
703+
// Test various query combinations with keepSynced
704+
final queries = [
705+
ref.orderByChild('name'),
706+
ref.orderByChild('priority').startAt(2),
707+
ref.orderByChild('priority').endAt(3),
708+
ref.orderByChild('priority').equalTo(2),
709+
ref.orderByKey().limitToFirst(2),
710+
ref.orderByKey().limitToLast(2),
711+
];
712+
713+
// Enable keepSynced on all queries
714+
for (final query in queries) {
715+
await query.keepSynced(true);
716+
}
717+
718+
// Disable keepSynced on all queries
719+
for (final query in queries) {
720+
await query.keepSynced(false);
721+
}
722+
723+
// Verify everything still works
724+
final snapshot = await ref.get();
725+
expect(snapshot.children.length, 4);
726+
},
727+
skip: kIsWeb,
728+
);
729+
});
607730
});
608731
}

0 commit comments

Comments
 (0)