[go: nahoru, domu]

Skip to content

Commit

Permalink
fix(database, web): clean up stream handlers on "hot restart" (#12915)
Browse files Browse the repository at this point in the history
  • Loading branch information
russellwheatley committed Jun 10, 2024
1 parent 5d5ffa3 commit e298cb4
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,37 +251,64 @@ class Query<T extends database_interop.QueryJsImpl> extends JsObjectWrapper<T> {
/// DatabaseReference to the Query's location.
DatabaseReference get ref => DatabaseReference.getInstance(jsObject.ref);

late final Stream<QueryEvent> _onValue = _createStream('value');
Stream<QueryEvent> _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<QueryEvent> get onValue => _onValue;

late final Stream<QueryEvent> _onChildAdded = _createStream('child_added');
Stream<QueryEvent> onValue(String appName, int hashCode) =>
_onValue(appName, hashCode);

Stream<QueryEvent> _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<QueryEvent> get onChildAdded => _onChildAdded;

late final Stream<QueryEvent> _onChildRemoved =
_createStream('child_removed');
Stream<QueryEvent> onChildAdded(String appName, int hashCode) =>
_onChildAdded(appName, hashCode);

Stream<QueryEvent> _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<QueryEvent> get onChildRemoved => _onChildRemoved;

late final Stream<QueryEvent> _onChildChanged =
_createStream('child_changed');
Stream<QueryEvent> onChildRemoved(String appName, int hashCode) =>
_onChildRemoved(appName, hashCode);

Stream<QueryEvent> _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<QueryEvent> get onChildChanged => _onChildChanged;
late final Stream<QueryEvent> _onChildMoved = _createStream('child_moved');
Stream<QueryEvent> onChildChanged(String appName, int hashCode) =>
_onChildChanged(appName, hashCode);
Stream<QueryEvent> _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<QueryEvent> get onChildMoved => _onChildMoved;
Stream<QueryEvent> onChildMoved(String appName, int hashCode) =>
_onChildMoved(appName, hashCode);

/// Creates a new Query from a [jsObject].
Query.fromJsObject(T jsObject) : super.fromJsObject(jsObject);
Expand Down Expand Up @@ -377,66 +404,86 @@ class Query<T extends database_interop.QueryJsImpl> extends JsObjectWrapper<T> {
);
}

Stream<QueryEvent> _createStream(String eventType) {
late StreamController<QueryEvent> streamController;
String _streamWindowsKey(String appName, String eventType, int hashCode) =>
'flutterfire-${appName}_${eventType}_${hashCode}_snapshot';

Stream<QueryEvent> _createStream(
String eventType,
String appName,
int hashCode,
) {
late StreamController<QueryEvent> 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<QueryEvent>.broadcast(
onListen: startListen,
onCancel: stopListen,
sync: true,
);
return streamController.stream;
}
Expand All @@ -447,8 +494,8 @@ class Query<T extends database_interop.QueryJsImpl> extends JsObjectWrapper<T> {

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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit e298cb4

Please sign in to comment.