Skip to content

Commit e23347b

Browse files
authored
fix(database,android): fix a regression where rapidly opening and closing query streams on the same path could throw (#18262)
1 parent bea425d commit e23347b

2 files changed

Lines changed: 42 additions & 5 deletions

File tree

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
package io.flutter.plugins.firebase.database
66

7-
import android.util.Log
87
import androidx.annotation.NonNull
98
import com.google.android.gms.tasks.Task
109
import com.google.android.gms.tasks.TaskCompletionSource
@@ -485,7 +484,7 @@ class FirebaseDatabasePlugin :
485484
}
486485

487486
private fun removeEventStreamHandlers() {
488-
for ((eventChannel, streamHandler) in streamHandlers) {
487+
for ((eventChannel, streamHandler) in streamHandlers.toMap()) {
489488
streamHandler?.onCancel(null)
490489
eventChannel.setStreamHandler(null)
491490
}
@@ -851,20 +850,21 @@ class FirebaseDatabasePlugin :
851850

852851
override fun queryObserve(app: DatabasePigeonFirebaseApp, request: QueryRequest, callback: (KotlinResult<String>) -> Unit) {
853852
try {
854-
Log.d("FirebaseDatabase", "🔍 Kotlin: Setting up query observe for path=${request.path}")
855853
val database = getDatabaseFromPigeonApp(app)
856854
val reference = database.getReference(request.path)
857855
val query = queryFromModifiers(reference, request.modifiers)
858856

859857
// Generate a unique channel name
860-
val channelName = "firebase_database_query_${System.currentTimeMillis()}_${request.path.hashCode()}"
858+
val channelName =
859+
synchronized(this) { "firebase_database_query_${listenerCount++}" }
861860

862861
// Set up the event channel
863862
val eventChannel = EventChannel(messenger, channelName)
864863
val streamHandler = EventStreamHandler(query, object : OnDispose {
865864
override fun run() {
866865
// Clean up when the stream is disposed
867-
eventChannel.setStreamHandler(null)
866+
eventChannel.setStreamHandler(null)
867+
streamHandlers.remove(eventChannel)
868868
}
869869
})
870870
eventChannel.setStreamHandler(streamHandler)

tests/integration_test/firebase_database/query_e2e.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,43 @@ void setupQueryTests() {
608608
);
609609
});
610610

611+
test(
612+
'cancels overlapping query streams without missing plugin',
613+
() async {
614+
const subscriptionCount = 128;
615+
final queryRef = ref.child('overlapping-query-streams');
616+
await queryRef.set({'value': 1});
617+
618+
final errors = <Object>[];
619+
final subscriptions = <StreamSubscription<DatabaseEvent>>[];
620+
final firstEventsReceived = Completer<void>();
621+
var firstEventCount = 0;
622+
623+
for (var i = 0; i < subscriptionCount; i++) {
624+
subscriptions.add(
625+
queryRef.onValue.listen(
626+
(_) {
627+
firstEventCount++;
628+
if (firstEventCount >= subscriptionCount &&
629+
!firstEventsReceived.isCompleted) {
630+
firstEventsReceived.complete();
631+
}
632+
},
633+
onError: errors.add,
634+
),
635+
);
636+
}
637+
638+
await firstEventsReceived.future.timeout(const Duration(seconds: 10));
639+
await Future.wait(
640+
subscriptions.map((subscription) => subscription.cancel()),
641+
);
642+
643+
expect(errors, isEmpty);
644+
},
645+
skip: defaultTargetPlatform != TargetPlatform.android,
646+
);
647+
611648
test(
612649
'throw a `permission-denied` exception when accessing restricted data',
613650
() async {

0 commit comments

Comments
 (0)