Skip to content

Commit 60f5258

Browse files
authored
Merge pull request #107 from OneSignal/android_main_thread_fixes
Fixed remaining Android main thread crashes
2 parents 15e0b89 + c582540 commit 60f5258

File tree

7 files changed

+143
-109
lines changed

7 files changed

+143
-109
lines changed

android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
group 'com.onesignal.flutter'
2-
version '1.0-SNAPSHOT'
2+
version '2.0.0'
33

44
buildscript {
55
repositories {
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.onesignal.flutter;
2+
3+
import android.app.Activity;
4+
import android.support.annotation.NonNull;
5+
6+
import java.util.HashMap;
7+
8+
import io.flutter.plugin.common.MethodChannel;
9+
import io.flutter.plugin.common.PluginRegistry;
10+
11+
abstract class FlutterRegistrarResponder {
12+
protected MethodChannel channel;
13+
protected PluginRegistry.Registrar flutterRegistrar;
14+
15+
/**
16+
* MethodChannel class is home to success() method used by Result class
17+
* It has the @UiThread annotation and must be run on UI thread, otherwise a RuntimeException will be thrown
18+
* This will communicate success back to Dart
19+
*/
20+
protected void replySuccess(final MethodChannel.Result reply, final Object response) {
21+
runOnMainThread(new Runnable() {
22+
@Override
23+
public void run() {
24+
reply.success(response);
25+
}
26+
});
27+
}
28+
29+
/**
30+
* MethodChannel class is home to error() method used by Result class
31+
* It has the @UiThread annotation and must be run on UI thread, otherwise a RuntimeException will be thrown
32+
* This will communicate error back to Dart
33+
*/
34+
protected void replyError(final MethodChannel.Result reply, final String tag, final String message, final Object response) {
35+
runOnMainThread(new Runnable() {
36+
@Override
37+
public void run() {
38+
reply.error(tag, message, response);
39+
}
40+
});
41+
}
42+
43+
/**
44+
* MethodChannel class is home to notImplemented() method used by Result class
45+
* It has the @UiThread annotation and must be run on UI thread, otherwise a RuntimeException will be thrown
46+
* This will communicate not implemented back to Dart
47+
*/
48+
protected void replyNotImplemented(final MethodChannel.Result reply) {
49+
runOnMainThread(new Runnable() {
50+
@Override
51+
public void run() {
52+
reply.notImplemented();
53+
}
54+
});
55+
}
56+
57+
protected void runOnMainThread(@NonNull final Runnable runnable) {
58+
((Activity)flutterRegistrar.activeContext()).runOnUiThread(runnable);
59+
}
60+
61+
protected void invokeMethodOnUiThread(@NonNull final String methodName, @NonNull final HashMap map) {
62+
final MethodChannel channel = this.channel;
63+
runOnMainThread(new Runnable() {
64+
@Override
65+
public void run() {
66+
channel.invokeMethod(methodName, map);
67+
}
68+
});
69+
}
70+
}

android/src/main/java/com/onesignal/flutter/OneSignalPlugin.java

Lines changed: 43 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.onesignal.flutter;
22

3-
import android.app.Activity;
43
import android.content.Context;
4+
import android.support.annotation.NonNull;
55

66
import com.onesignal.OSEmailSubscriptionObserver;
77
import com.onesignal.OSEmailSubscriptionStateChanges;
@@ -25,7 +25,6 @@
2525
import org.json.JSONObject;
2626

2727
import java.util.Collection;
28-
import java.util.HashMap;
2928
import java.util.Map;
3029

3130
import io.flutter.plugin.common.MethodCall;
@@ -35,12 +34,17 @@
3534
import io.flutter.plugin.common.PluginRegistry.Registrar;
3635

3736
/** OnesignalPlugin */
38-
public class OneSignalPlugin implements MethodCallHandler, NotificationReceivedHandler, NotificationOpenedHandler,
39-
InAppMessageClickHandler, OSSubscriptionObserver, OSEmailSubscriptionObserver, OSPermissionObserver {
37+
public class OneSignalPlugin
38+
extends FlutterRegistrarResponder
39+
implements MethodCallHandler,
40+
NotificationReceivedHandler,
41+
NotificationOpenedHandler,
42+
InAppMessageClickHandler,
43+
OSSubscriptionObserver,
44+
OSEmailSubscriptionObserver,
45+
OSPermissionObserver {
4046

4147
/** Plugin registration. */
42-
private Registrar flutterRegistrar;
43-
private MethodChannel channel;
4448
private OSNotificationOpenResult coldStartNotificationResult;
4549
private OSInAppMessageAction inAppMessageClickedResult;
4650
private boolean hasSetNotificationOpenedHandler = false;
@@ -54,18 +58,15 @@ public static void registerWith(Registrar registrar) {
5458
OneSignalPlugin plugin = new OneSignalPlugin();
5559

5660
plugin.waitingForUserPrivacyConsent = false;
57-
5861
plugin.channel = new MethodChannel(registrar.messenger(), "OneSignal");
59-
6062
plugin.channel.setMethodCallHandler(plugin);
61-
6263
plugin.flutterRegistrar = registrar;
6364

6465
OneSignalTagsController.registerWith(registrar);
6566
}
6667

6768
@Override
68-
public void onMethodCall(MethodCall call, Result result) {
69+
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
6970
if (call.method.contentEquals("OneSignal#init"))
7071
initOneSignal(call, result);
7172
else if (call.method.contentEquals("OneSignal#setLogLevel"))
@@ -105,34 +106,13 @@ else if (call.method.contentEquals("OneSignal#setExternalUserId"))
105106
else if (call.method.contentEquals("OneSignal#removeExternalUserId"))
106107
this.removeExternalUserId(result);
107108
else if (call.method.contentEquals("OneSignal#addTrigger")) {
108-
// call.arguments is being casted to a Map<String, Object> so a try-catch with
109-
// a ClassCastException will be thrown
110-
try {
111-
OneSignal.addTriggers((Map<String, Object>) call.arguments);
112-
} catch (ClassCastException e) {
113-
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.ERROR, "Add trigger failed with error: " + e.getMessage());
114-
e.printStackTrace();
115-
}
109+
addTriggers(call.arguments);
116110
} else if (call.method.contentEquals("OneSignal#addTriggers")) {
117-
// call.arguments is being casted to a Map<String, Object> so a try-catch with
118-
// a ClassCastException will be thrown
119-
try {
120-
OneSignal.addTriggers((Map<String, Object>) call.arguments);
121-
} catch (ClassCastException e) {
122-
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.ERROR, "Add triggers failed with error: " + e.getMessage());
123-
e.printStackTrace();
124-
}
111+
addTriggers(call.arguments);
125112
} else if (call.method.contentEquals("OneSignal#removeTriggerForKey"))
126113
OneSignal.removeTriggerForKey((String) call.arguments);
127114
else if (call.method.contentEquals("OneSignal#removeTriggerForKeys")) {
128-
// call.arguments is being casted to a Collection<String> a try-catch with
129-
// a ClassCastException will be thrown
130-
try {
131-
OneSignal.removeTriggersForKeys((Collection<String>) call.arguments);
132-
} catch (ClassCastException e) {
133-
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.ERROR, "Remove trigger for keys failed with error: " + e.getMessage());
134-
e.printStackTrace();
135-
}
115+
removeTriggersForKeys(call.arguments);
136116
} else if (call.method.contentEquals("OneSignal#getTriggerValueForKey"))
137117
getTriggerValueForKey(result, (String) call.arguments);
138118
else if (call.method.contentEquals("OneSignal#pauseInAppMessages"))
@@ -143,6 +123,28 @@ else if (call.method.contentEquals("OneSignal#initInAppMessageClickedHandlerPara
143123
replyNotImplemented(result);
144124
}
145125

126+
private void addTriggers(Object arguments) {
127+
// call.arguments is being casted to a Map<String, Object> so a try-catch with
128+
// a ClassCastException will be thrown
129+
try {
130+
OneSignal.addTriggers((Map<String, Object>) arguments);
131+
} catch (ClassCastException e) {
132+
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.ERROR, "Add triggers failed with error: " + e.getMessage());
133+
e.printStackTrace();
134+
}
135+
}
136+
137+
private void removeTriggersForKeys(Object arguments) {
138+
// call.arguments is being casted to a Collection<String> a try-catch with
139+
// a ClassCastException will be thrown
140+
try {
141+
OneSignal.removeTriggersForKeys((Collection<String>) arguments);
142+
} catch (ClassCastException e) {
143+
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.ERROR, "Remove trigger for keys failed with error: " + e.getMessage());
144+
e.printStackTrace();
145+
}
146+
}
147+
146148
private void getTriggerValueForKey(Result reply, String key) {
147149
Object triggerValue = OneSignal.getTriggerValueForKey(key);
148150
replySuccess(reply, triggerValue);
@@ -334,27 +336,27 @@ private void removeExternalUserId(Result result) {
334336

335337
@Override
336338
public void onOSSubscriptionChanged(OSSubscriptionStateChanges stateChanges) {
337-
this.channel.invokeMethod("OneSignal#subscriptionChanged", OneSignalSerializer.convertSubscriptionStateChangesToMap(stateChanges));
339+
invokeMethodOnUiThread("OneSignal#subscriptionChanged", OneSignalSerializer.convertSubscriptionStateChangesToMap(stateChanges));
338340
}
339341

340342
@Override
341343
public void onOSEmailSubscriptionChanged(OSEmailSubscriptionStateChanges stateChanges) {
342-
this.channel.invokeMethod("OneSignal#emailSubscriptionChanged", OneSignalSerializer.convertEmailSubscriptionStateChangesToMap(stateChanges));
344+
invokeMethodOnUiThread("OneSignal#emailSubscriptionChanged", OneSignalSerializer.convertEmailSubscriptionStateChangesToMap(stateChanges));
343345
}
344346

345347
@Override
346348
public void onOSPermissionChanged(OSPermissionStateChanges stateChanges) {
347-
this.channel.invokeMethod("OneSignal#permissionChanged", OneSignalSerializer.convertPermissionStateChangesToMap(stateChanges));
349+
invokeMethodOnUiThread("OneSignal#permissionChanged", OneSignalSerializer.convertPermissionStateChangesToMap(stateChanges));
348350
}
349351

350352
@Override
351353
public void notificationReceived(OSNotification notification) {
352354
try {
353-
this.channel.invokeMethod("OneSignal#handleReceivedNotification", OneSignalSerializer.convertNotificationToMap(notification));
355+
invokeMethodOnUiThread("OneSignal#handleReceivedNotification", OneSignalSerializer.convertNotificationToMap(notification));
354356
} catch (JSONException e) {
355357
e.printStackTrace();
356358
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.ERROR,
357-
"Encountered an error attempting to convert OSNotification object to hash map: " + e.getMessage());
359+
"Encountered an error attempting to convert OSNotification object to hash map: " + e.getMessage());
358360
}
359361
}
360362

@@ -366,7 +368,7 @@ public void notificationOpened(OSNotificationOpenResult result) {
366368
}
367369

368370
try {
369-
this.channel.invokeMethod("OneSignal#handleOpenedNotification", OneSignalSerializer.convertNotificationOpenResultToMap(result));
371+
invokeMethodOnUiThread("OneSignal#handleOpenedNotification", OneSignalSerializer.convertNotificationOpenResultToMap(result));
370372
} catch (JSONException e) {
371373
e.getStackTrace();
372374
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.ERROR,
@@ -381,49 +383,6 @@ public void inAppMessageClicked(OSInAppMessageAction action) {
381383
return;
382384
}
383385

384-
this.channel.invokeMethod("OneSignal#handleClickedInAppMessage", OneSignalSerializer.convertInAppMessageClickedActionToMap(action));
386+
invokeMethodOnUiThread("OneSignal#handleClickedInAppMessage", OneSignalSerializer.convertInAppMessageClickedActionToMap(action));
385387
}
386-
387-
/**
388-
* MethodChannel class is home to success() method used by Result class
389-
* It has the @UiThread annotation and must be run on UI thread, otherwise a RuntimeException will be thrown
390-
* This will communicate success back to Dart
391-
*/
392-
private void replySuccess(final Result reply, final Object response) {
393-
((Activity) flutterRegistrar.activeContext()).runOnUiThread(new Runnable() {
394-
@Override
395-
public void run() {
396-
reply.success(response);
397-
}
398-
});
399-
}
400-
401-
/**
402-
* MethodChannel class is home to error() method used by Result class
403-
* It has the @UiThread annotation and must be run on UI thread, otherwise a RuntimeException will be thrown
404-
* This will communicate error back to Dart
405-
*/
406-
private void replyError(final Result reply, final String tag, final String message, final Object response) {
407-
((Activity) flutterRegistrar.activeContext()).runOnUiThread(new Runnable() {
408-
@Override
409-
public void run() {
410-
reply.error(tag, message, response);
411-
}
412-
});
413-
}
414-
415-
/**
416-
* MethodChannel class is home to notImplemented() method used by Result class
417-
* It has the @UiThread annotation and must be run on UI thread, otherwise a RuntimeException will be thrown
418-
* This will communicate not implemented back to Dart
419-
*/
420-
private void replyNotImplemented(final Result reply) {
421-
((Activity) flutterRegistrar.activeContext()).runOnUiThread(new Runnable() {
422-
@Override
423-
public void run() {
424-
reply.notImplemented();
425-
}
426-
});
427-
}
428-
429388
}
Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package com.onesignal.flutter;
22

3+
import android.support.annotation.NonNull;
4+
35
import io.flutter.plugin.common.MethodCall;
46
import io.flutter.plugin.common.MethodChannel;
57
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
68
import io.flutter.plugin.common.MethodChannel.Result;
9+
import io.flutter.plugin.common.PluginRegistry;
710
import io.flutter.plugin.common.PluginRegistry.Registrar;
811

912
import com.onesignal.OneSignal;
@@ -22,15 +25,17 @@
2225
* Created by bradhesse on 7/17/18.
2326
*/
2427

25-
class OSFlutterChangeTagsHandler implements ChangeTagsUpdateHandler, GetTagsHandler {
28+
class OSFlutterChangeTagsHandler extends FlutterRegistrarResponder implements ChangeTagsUpdateHandler, GetTagsHandler {
2629
private Result result;
2730

2831
// the tags callbacks can in some instances be called more than once
2932
// ie. cached vs. server response.
3033
// this property guarantees the callback will never be called more than once.
31-
private AtomicBoolean replySubmitted = new AtomicBoolean(false);
34+
@NonNull private AtomicBoolean replySubmitted = new AtomicBoolean(false);
3235

33-
OSFlutterChangeTagsHandler(Result res) {
36+
OSFlutterChangeTagsHandler(PluginRegistry.Registrar flutterRegistrar, MethodChannel channel, Result res) {
37+
this.flutterRegistrar = flutterRegistrar;
38+
this.channel = channel;
3439
this.result = res;
3540
}
3641

@@ -40,9 +45,9 @@ public void onSuccess(JSONObject tags) {
4045
return;
4146

4247
try {
43-
this.result.success(OneSignalSerializer.convertJSONObjectToHashMap(tags));
48+
replySuccess(result, OneSignalSerializer.convertJSONObjectToHashMap(tags));
4449
} catch (JSONException exception) {
45-
this.result.error("onesignal", "Encountered an error serializing tags into hashmap: " + exception.getMessage() + "\n" + exception.getStackTrace(), null);
50+
replyError(result, "onesignal", "Encountered an error serializing tags into hashmap: " + exception.getMessage() + "\n" + exception.getStackTrace(), null);
4651
}
4752
}
4853

@@ -51,7 +56,7 @@ public void onFailure(SendTagsError error) {
5156
if (this.replySubmitted.getAndSet(true))
5257
return;
5358

54-
this.result.error("onesignal", "Encountered an error updating tags (" + String.valueOf(error.getCode()) + "): " + error.getMessage(), null);
59+
replyError(result,"onesignal","Encountered an error updating tags (" + error.getCode() + "): " + error.getMessage(), null);
5560
}
5661

5762
@Override
@@ -60,31 +65,31 @@ public void tagsAvailable(JSONObject jsonObject) {
6065
return;
6166

6267
try {
63-
this.result.success(OneSignalSerializer.convertJSONObjectToHashMap(jsonObject));
68+
replySuccess(result, OneSignalSerializer.convertJSONObjectToHashMap(jsonObject));
6469
} catch (JSONException exception) {
65-
this.result.error("onesignal", "Encountered an error serializing tags into hashmap: " + exception.getMessage() + "\n" + exception.getStackTrace(), null);
70+
replyError(result, "onesignal", "Encountered an error serializing tags into hashmap: " + exception.getMessage() + "\n" + exception.getStackTrace(), null);
6671
}
6772
}
6873
}
6974

7075
public class OneSignalTagsController implements MethodCallHandler {
7176
private MethodChannel channel;
77+
private Registrar registrar;
7278

73-
public static void registerWith(Registrar registrar) {
79+
static void registerWith(Registrar registrar) {
7480
OneSignalTagsController controller = new OneSignalTagsController();
81+
controller.registrar = registrar;
7582
controller.channel = new MethodChannel(registrar.messenger(), "OneSignal#tags");
7683
controller.channel.setMethodCallHandler(controller);
7784
}
7885

7986
@Override
80-
public void onMethodCall(MethodCall call, Result result) {
81-
if (call.method.contentEquals("OneSignal#getTags")) {
82-
OneSignal.getTags(new OSFlutterChangeTagsHandler(result));
83-
} else if (call.method.contentEquals("OneSignal#sendTags")) {
84-
OneSignal.sendTags(new JSONObject((Map<String, Object>) call.arguments), new OSFlutterChangeTagsHandler(result));
85-
} else if (call.method.contentEquals("OneSignal#deleteTags")
86-
) {
87-
OneSignal.deleteTags((List<String>)call.arguments, new OSFlutterChangeTagsHandler(result));
88-
}
87+
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
88+
if (call.method.contentEquals("OneSignal#getTags"))
89+
OneSignal.getTags(new OSFlutterChangeTagsHandler(registrar, channel, result));
90+
else if (call.method.contentEquals("OneSignal#sendTags"))
91+
OneSignal.sendTags(new JSONObject((Map<String, Object>) call.arguments), new OSFlutterChangeTagsHandler(registrar, channel, result));
92+
else if (call.method.contentEquals("OneSignal#deleteTags"))
93+
OneSignal.deleteTags((List<String>)call.arguments, new OSFlutterChangeTagsHandler(registrar, channel, result));
8994
}
9095
}

0 commit comments

Comments
 (0)