Skip to content

Commit abbab55

Browse files
enedSalakar
andauthored
fix(database): refactor "removeObserver" to fix memory leak (#2717)
Co-authored-by: Mike Diarmid <[email protected]>
1 parent 95ab31d commit abbab55

File tree

2 files changed

+58
-12
lines changed

2 files changed

+58
-12
lines changed

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

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,33 @@
88
import io.flutter.plugin.common.BinaryMessenger;
99
import io.flutter.plugin.common.MethodChannel;
1010
import io.flutter.plugin.common.PluginRegistry;
11+
import io.flutter.plugin.common.PluginRegistry.ViewDestroyListener;
12+
import io.flutter.view.FlutterNativeView;
1113

1214
/** FirebaseDatabasePlugin */
1315
public class FirebaseDatabasePlugin implements FlutterPlugin {
1416

1517
private MethodChannel channel;
18+
private MethodCallHandlerImpl methodCallHandler;
1619

1720
public static void registerWith(PluginRegistry.Registrar registrar) {
18-
FirebaseDatabasePlugin plugin = new FirebaseDatabasePlugin();
21+
final FirebaseDatabasePlugin plugin = new FirebaseDatabasePlugin();
1922
plugin.setupMethodChannel(registrar.messenger());
23+
24+
registrar.addViewDestroyListener(
25+
new ViewDestroyListener() {
26+
@Override
27+
public boolean onViewDestroy(FlutterNativeView view) {
28+
plugin.cleanup();
29+
return false;
30+
}
31+
});
2032
}
2133

2234
private void setupMethodChannel(BinaryMessenger messenger) {
2335
channel = new MethodChannel(messenger, "plugins.flutter.io/firebase_database");
24-
MethodCallHandlerImpl handler = new MethodCallHandlerImpl(channel);
25-
channel.setMethodCallHandler(handler);
36+
methodCallHandler = new MethodCallHandlerImpl(channel);
37+
channel.setMethodCallHandler(methodCallHandler);
2638
}
2739

2840
@Override
@@ -32,6 +44,12 @@ public void onAttachedToEngine(FlutterPluginBinding binding) {
3244

3345
@Override
3446
public void onDetachedFromEngine(FlutterPluginBinding binding) {
47+
cleanup();
48+
}
49+
50+
private void cleanup() {
51+
methodCallHandler.cleanup();
52+
methodCallHandler = null;
3553
channel.setMethodCallHandler(null);
3654
}
3755
}

packages/firebase_database/android/src/main/java/io/flutter/plugins/firebase/database/MethodCallHandlerImpl.java

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,37 @@ class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler {
5353
this.channel = channel;
5454
}
5555

56+
void cleanup() {
57+
final int size = observers.size();
58+
for (int i = 0; i < size; i++) {
59+
final EventObserver observer = observers.valueAt(i);
60+
final Query query = observer.query;
61+
if (observer.requestedEventType.equals(EVENT_TYPE_VALUE)) {
62+
query.removeEventListener((ValueEventListener) observer);
63+
} else {
64+
query.removeEventListener((ChildEventListener) observer);
65+
}
66+
}
67+
68+
observers.clear();
69+
}
70+
5671
private DatabaseReference getReference(FirebaseDatabase database, Map<String, Object> arguments) {
5772
String path = (String) arguments.get("path");
5873
DatabaseReference reference = database.getReference();
59-
if (path != null) reference = reference.child(path);
74+
if (path != null) {
75+
reference = reference.child(path);
76+
}
6077
return reference;
6178
}
6279

6380
private Query getQuery(FirebaseDatabase database, Map<String, Object> arguments) {
6481
Query query = getReference(database, arguments);
6582
@SuppressWarnings("unchecked")
6683
Map<String, Object> parameters = (Map<String, Object>) arguments.get("parameters");
67-
if (parameters == null) return query;
84+
if (parameters == null) {
85+
return query;
86+
}
6887
Object orderBy = parameters.get("orderBy");
6988
if ("child".equals(orderBy)) {
7089
query = query.orderByChild((String) parameters.get("orderByChildKey"));
@@ -148,6 +167,7 @@ private Query getQuery(FirebaseDatabase database, Map<String, Object> arguments)
148167
}
149168

150169
private class DefaultCompletionListener implements DatabaseReference.CompletionListener {
170+
151171
private final MethodChannel.Result result;
152172

153173
DefaultCompletionListener(MethodChannel.Result result) {
@@ -164,12 +184,17 @@ public void onComplete(@Nullable DatabaseError error, @NonNull DatabaseReference
164184
}
165185
}
166186

167-
private class EventObserver implements ChildEventListener, ValueEventListener {
187+
private static class EventObserver implements ChildEventListener, ValueEventListener {
188+
189+
private MethodChannel channel;
168190
private String requestedEventType;
191+
private Query query;
169192
private int handle;
170193

171-
EventObserver(String requestedEventType, int handle) {
194+
EventObserver(MethodChannel channel, String requestedEventType, Query query, int handle) {
195+
this.channel = channel;
172196
this.requestedEventType = requestedEventType;
197+
this.query = query;
173198
this.handle = handle;
174199
}
175200

@@ -249,6 +274,7 @@ public void onMethodCall(final MethodCall call, @NonNull final MethodChannel.Res
249274
} else {
250275
database = FirebaseDatabase.getInstance();
251276
}
277+
252278
switch (call.method) {
253279
case "FirebaseDatabase#goOnline":
254280
{
@@ -347,7 +373,8 @@ public void onMethodCall(final MethodCall call, @NonNull final MethodChannel.Res
347373
@NonNull
348374
@Override
349375
public Transaction.Result doTransaction(@NonNull MutableData mutableData) {
350-
// Tasks are used to allow native execution of doTransaction to wait while Snapshot is
376+
// Tasks are used to allow native execution of doTransaction to wait while
377+
// Snapshot is
351378
// processed by logic on the Dart side.
352379
final TaskCompletionSource<Map<String, Object>> updateMutableDataTCS =
353380
new TaskCompletionSource<>();
@@ -514,24 +541,25 @@ public void run() {
514541
{
515542
String eventType = call.argument("eventType");
516543
int handle = nextHandle++;
544+
Query query = getQuery(database, arguments);
517545
MethodCallHandlerImpl.EventObserver observer =
518-
new MethodCallHandlerImpl.EventObserver(eventType, handle);
546+
new MethodCallHandlerImpl.EventObserver(channel, eventType, query, handle);
519547
observers.put(handle, observer);
520548
if (EVENT_TYPE_VALUE.equals(eventType)) {
521-
getQuery(database, arguments).addValueEventListener(observer);
549+
query.addValueEventListener(observer);
522550
} else {
523-
getQuery(database, arguments).addChildEventListener(observer);
551+
query.addChildEventListener(observer);
524552
}
525553
result.success(handle);
526554
break;
527555
}
528556

529557
case "Query#removeObserver":
530558
{
531-
Query query = getQuery(database, arguments);
532559
Integer handle = call.argument("handle");
533560
MethodCallHandlerImpl.EventObserver observer = observers.get(handle);
534561
if (observer != null) {
562+
final Query query = observer.query;
535563
if (observer.requestedEventType.equals(EVENT_TYPE_VALUE)) {
536564
query.removeEventListener((ValueEventListener) observer);
537565
} else {

0 commit comments

Comments
 (0)