diff --git a/packages/firebase_database/firebase_database_web/lib/firebase_database_web.dart b/packages/firebase_database/firebase_database_web/lib/firebase_database_web.dart index 8a9b681c787d..1062e9ca655e 100755 --- a/packages/firebase_database/firebase_database_web/lib/firebase_database_web.dart +++ b/packages/firebase_database/firebase_database_web/lib/firebase_database_web.dart @@ -6,12 +6,13 @@ library firebase_database_web; import 'dart:async'; import 'dart:js_interop'; - +import 'package:collection/collection.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core_web/firebase_core_web.dart'; import 'package:firebase_core_web/firebase_core_web_interop.dart' as core_interop; import 'package:firebase_database_platform_interface/firebase_database_platform_interface.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'src/interop/database.dart' as database_interop; diff --git a/packages/firebase_database/firebase_database_web/lib/src/interop/database.dart b/packages/firebase_database/firebase_database_web/lib/src/interop/database.dart index da65bbd518ee..1c222461bf4a 100755 --- a/packages/firebase_database/firebase_database_web/lib/src/interop/database.dart +++ b/packages/firebase_database/firebase_database_web/lib/src/interop/database.dart @@ -251,37 +251,64 @@ class Query extends JsObjectWrapper { /// DatabaseReference to the Query's location. DatabaseReference get ref => DatabaseReference.getInstance(jsObject.ref); - late final Stream _onValue = _createStream('value'); + Stream _onValue(String appName, int hashCode) => _createStream( + 'value', + appName, + hashCode, + ); /// Stream for a value event. Event is triggered once with the initial /// data stored at location, and then again each time the data changes. - Stream get onValue => _onValue; - - late final Stream _onChildAdded = _createStream('child_added'); + Stream onValue(String appName, int hashCode) => + _onValue(appName, hashCode); + + Stream _onChildAdded(String appName, int hashCode) => + _createStream( + 'child_added', + appName, + hashCode, + ); /// Stream for a child_added event. Event is triggered once for each /// initial child at location, and then again every time a new child is added. - Stream get onChildAdded => _onChildAdded; - - late final Stream _onChildRemoved = - _createStream('child_removed'); + Stream onChildAdded(String appName, int hashCode) => + _onChildAdded(appName, hashCode); + + Stream _onChildRemoved(String appName, int hashCode) => + _createStream( + 'child_removed', + appName, + hashCode, + ); /// Stream for a child_removed event. Event is triggered once every time /// a child is removed. - Stream get onChildRemoved => _onChildRemoved; - - late final Stream _onChildChanged = - _createStream('child_changed'); + Stream onChildRemoved(String appName, int hashCode) => + _onChildRemoved(appName, hashCode); + + Stream _onChildChanged(String appName, int hashCode) => + _createStream( + 'child_changed', + appName, + hashCode, + ); /// Stream for a child_changed event. Event is triggered when the data /// stored in a child (or any of its descendants) changes. /// Single child_changed event may represent multiple changes to the child. - Stream get onChildChanged => _onChildChanged; - late final Stream _onChildMoved = _createStream('child_moved'); + Stream onChildChanged(String appName, int hashCode) => + _onChildChanged(appName, hashCode); + Stream _onChildMoved(String appName, int hashCode) => + _createStream( + 'child_moved', + appName, + hashCode, + ); /// Stream for a child_moved event. Event is triggered when a child's priority /// changes such that its position relative to its siblings changes. - Stream get onChildMoved => _onChildMoved; + Stream onChildMoved(String appName, int hashCode) => + _onChildMoved(appName, hashCode); /// Creates a new Query from a [jsObject]. Query.fromJsObject(T jsObject) : super.fromJsObject(jsObject); @@ -377,66 +404,86 @@ class Query extends JsObjectWrapper { ); } - Stream _createStream(String eventType) { - late StreamController streamController; + String _streamWindowsKey(String appName, String eventType, int hashCode) => + 'flutterfire-${appName}_${eventType}_${hashCode}_snapshot'; + Stream _createStream( + String eventType, + String appName, + int hashCode, + ) { + late StreamController streamController; + unsubscribeWindowsListener(_streamWindowsKey(appName, eventType, hashCode)); final callbackWrap = (( database_interop.DataSnapshotJsImpl data, [ - String? string, + String? prevChild, ]) { - streamController.add(QueryEvent(DataSnapshot.getInstance(data), string)); + streamController + .add(QueryEvent(DataSnapshot.getInstance(data), prevChild)); }); final void Function(JSObject) cancelCallbackWrap = ((JSObject error) { streamController.addError(convertFirebaseDatabaseException(error)); - streamController.close(); }); + late JSFunction onUnsubscribe; + void startListen() { if (eventType == 'child_added') { - database_interop.onChildAdded( + onUnsubscribe = database_interop.onChildAdded( jsObject, callbackWrap.toJS, cancelCallbackWrap.toJS, ); } if (eventType == 'value') { - database_interop.onValue( + onUnsubscribe = database_interop.onValue( jsObject, callbackWrap.toJS, cancelCallbackWrap.toJS, ); } if (eventType == 'child_removed') { - database_interop.onChildRemoved( + onUnsubscribe = database_interop.onChildRemoved( jsObject, callbackWrap.toJS, cancelCallbackWrap.toJS, ); } if (eventType == 'child_changed') { - database_interop.onChildChanged( + onUnsubscribe = database_interop.onChildChanged( jsObject, callbackWrap.toJS, cancelCallbackWrap.toJS, ); } if (eventType == 'child_moved') { - database_interop.onChildMoved( + onUnsubscribe = database_interop.onChildMoved( jsObject, callbackWrap.toJS, cancelCallbackWrap.toJS, ); } + setWindowsListener( + _streamWindowsKey(appName, eventType, hashCode), + onUnsubscribe, + ); } void stopListen() { - database_interop.off(jsObject, eventType.toJS, callbackWrap.toJS); + onUnsubscribe.callAsFunction(); + streamController.close(); + removeWindowsListener(_streamWindowsKey( + appName, + eventType, + hashCode, + )); } streamController = StreamController.broadcast( onListen: startListen, onCancel: stopListen, + sync: true, ); return streamController.stream; } @@ -447,8 +494,8 @@ class Query extends JsObjectWrapper { database_interop.onValue( jsObject, - ((database_interop.DataSnapshotJsImpl snapshot, [String? string]) { - c.complete(QueryEvent(DataSnapshot.getInstance(snapshot), string)); + ((database_interop.DataSnapshotJsImpl snapshot, [String? prevChild]) { + c.complete(QueryEvent(DataSnapshot.getInstance(snapshot), prevChild)); }).toJS, ((JSAny error) { c.completeError(convertFirebaseDatabaseException(error)); diff --git a/packages/firebase_database/firebase_database_web/lib/src/interop/database_interop.dart b/packages/firebase_database/firebase_database_web/lib/src/interop/database_interop.dart index fcc610e10a3d..a2c588adfeed 100755 --- a/packages/firebase_database/firebase_database_web/lib/src/interop/database_interop.dart +++ b/packages/firebase_database/firebase_database_web/lib/src/interop/database_interop.dart @@ -70,16 +70,7 @@ external JSAny increment(JSNumber delta); @JS() @staticInterop -external void off([ - QueryJsImpl query, - JSString eventType, - JSFunction callback, - /*JSAny Function(DataSnapshotJsImpl, [JSString previousChildName]) callback*/ -]); - -@JS() -@staticInterop -external QueryConstraintJsImpl onChildAdded( +external JSFunction onChildAdded( QueryJsImpl query, JSFunction callback, // JSAny Function(DataSnapshotJsImpl, [JSString previousChildName]) callback, @@ -89,7 +80,7 @@ external QueryConstraintJsImpl onChildAdded( @JS() @staticInterop -external QueryConstraintJsImpl onChildChanged( +external JSFunction onChildChanged( QueryJsImpl query, JSFunction callback, // JSAny Function(DataSnapshotJsImpl, [JSString previousChildName]) callback, @@ -99,7 +90,7 @@ external QueryConstraintJsImpl onChildChanged( @JS() @staticInterop -external QueryConstraintJsImpl onChildMoved( +external JSFunction onChildMoved( QueryJsImpl query, JSFunction callback, // JSAny Function(DataSnapshotJsImpl, [JSString previousChildName]) callback, @@ -109,7 +100,7 @@ external QueryConstraintJsImpl onChildMoved( @JS() @staticInterop -external QueryConstraintJsImpl onChildRemoved( +external JSFunction onChildRemoved( QueryJsImpl query, JSFunction callback, // JSAny Function(DataSnapshotJsImpl, [JSString previousChildName]) callback, @@ -123,13 +114,15 @@ external OnDisconnectJsImpl onDisconnect(ReferenceJsImpl ref); @JS() @staticInterop -external void onValue( - QueryJsImpl query, - JSFunction callback, - // JSAny Function(DataSnapshotJsImpl, [JSString previousChildName]) callback, - JSFunction cancelCallback, - // JSAny Function(FirebaseError error) cancelCallback, - [ListenOptions options]); +external JSFunction onValue( + QueryJsImpl query, + JSFunction callback, + // JSAny Function(DataSnapshotJsImpl, [JSString previousChildName]) callback, + JSFunction cancelCallback, + // JSAny Function(FirebaseError error) cancelCallback, + [ + ListenOptions options, +]); @JS() @staticInterop diff --git a/packages/firebase_database/firebase_database_web/lib/src/query_web.dart b/packages/firebase_database/firebase_database_web/lib/src/query_web.dart index 6bfeb999b3b2..4811fd306256 100755 --- a/packages/firebase_database/firebase_database_web/lib/src/query_web.dart +++ b/packages/firebase_database/firebase_database_web/lib/src/query_web.dart @@ -94,29 +94,64 @@ class QueryWeb extends QueryPlatform { QueryModifiers modifiers, DatabaseEventType eventType) { database_interop.Query instance = _getQueryDelegateInstance(modifiers); + int hashCode = 0; + final appName = + _database.app != null ? _database.app!.name : Firebase.app().name; + if (kDebugMode) { + // Purely for unsubscribing purposes in debug mode on "hot restart" + // if not running in debug mode, hashCode won't be used + hashCode = Object.hashAll([ + appName, + path, + ...modifiers + .toList() + .map((e) => const DeepCollectionEquality().hash(e)) + .toList(), + eventType.index, + ]); + } + switch (eventType) { case DatabaseEventType.childAdded: return _webStreamToPlatformStream( eventType, - instance.onChildAdded, + instance.onChildAdded( + appName, + hashCode, + ), ); case DatabaseEventType.childChanged: return _webStreamToPlatformStream( eventType, - instance.onChildChanged, + instance.onChildChanged( + appName, + hashCode, + ), ); case DatabaseEventType.childMoved: return _webStreamToPlatformStream( eventType, - instance.onChildMoved, + instance.onChildMoved( + appName, + hashCode, + ), ); case DatabaseEventType.childRemoved: return _webStreamToPlatformStream( eventType, - instance.onChildRemoved, + instance.onChildRemoved( + appName, + hashCode, + ), ); case DatabaseEventType.value: - return _webStreamToPlatformStream(eventType, instance.onValue); + return _webStreamToPlatformStream( + eventType, + instance.onValue( + appName, + hashCode, + ), + ); default: throw Exception("Invalid event type: $eventType"); } diff --git a/packages/firebase_database/firebase_database_web/pubspec.yaml b/packages/firebase_database/firebase_database_web/pubspec.yaml index 091100bf48d8..81b185f58829 100644 --- a/packages/firebase_database/firebase_database_web/pubspec.yaml +++ b/packages/firebase_database/firebase_database_web/pubspec.yaml @@ -8,6 +8,7 @@ environment: flutter: '>=3.3.0' dependencies: + collection: ^1.18.0 firebase_core: ^3.0.0 firebase_core_web: ^2.17.1 firebase_database_platform_interface: ^0.2.5+36