Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed remaining Android main thread crashes #107

Merged
merged 2 commits into from Jul 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion android/build.gradle
@@ -1,5 +1,5 @@
group 'com.onesignal.flutter'
version '1.0-SNAPSHOT'
version '2.0.0'

buildscript {
repositories {
Expand Down
@@ -0,0 +1,70 @@
package com.onesignal.flutter;

import android.app.Activity;
import android.support.annotation.NonNull;

import java.util.HashMap;

import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;

abstract class FlutterRegistrarResponder {
protected MethodChannel channel;
protected PluginRegistry.Registrar flutterRegistrar;

/**
* MethodChannel class is home to success() method used by Result class
* It has the @UiThread annotation and must be run on UI thread, otherwise a RuntimeException will be thrown
* This will communicate success back to Dart
*/
protected void replySuccess(final MethodChannel.Result reply, final Object response) {
runOnMainThread(new Runnable() {
@Override
public void run() {
reply.success(response);
}
});
}

/**
* MethodChannel class is home to error() method used by Result class
* It has the @UiThread annotation and must be run on UI thread, otherwise a RuntimeException will be thrown
* This will communicate error back to Dart
*/
protected void replyError(final MethodChannel.Result reply, final String tag, final String message, final Object response) {
runOnMainThread(new Runnable() {
@Override
public void run() {
reply.error(tag, message, response);
}
});
}

/**
* MethodChannel class is home to notImplemented() method used by Result class
* It has the @UiThread annotation and must be run on UI thread, otherwise a RuntimeException will be thrown
* This will communicate not implemented back to Dart
*/
protected void replyNotImplemented(final MethodChannel.Result reply) {
runOnMainThread(new Runnable() {
@Override
public void run() {
reply.notImplemented();
}
});
}

protected void runOnMainThread(@NonNull final Runnable runnable) {
((Activity)flutterRegistrar.activeContext()).runOnUiThread(runnable);
}

protected void invokeMethodOnUiThread(@NonNull final String methodName, @NonNull final HashMap map) {
final MethodChannel channel = this.channel;
runOnMainThread(new Runnable() {
@Override
public void run() {
channel.invokeMethod(methodName, map);
}
});
}
}
127 changes: 43 additions & 84 deletions android/src/main/java/com/onesignal/flutter/OneSignalPlugin.java
@@ -1,7 +1,7 @@
package com.onesignal.flutter;

import android.app.Activity;
import android.content.Context;
import android.support.annotation.NonNull;

import com.onesignal.OSEmailSubscriptionObserver;
import com.onesignal.OSEmailSubscriptionStateChanges;
Expand All @@ -25,7 +25,6 @@
import org.json.JSONObject;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import io.flutter.plugin.common.MethodCall;
Expand All @@ -35,12 +34,17 @@
import io.flutter.plugin.common.PluginRegistry.Registrar;

/** OnesignalPlugin */
public class OneSignalPlugin implements MethodCallHandler, NotificationReceivedHandler, NotificationOpenedHandler,
InAppMessageClickHandler, OSSubscriptionObserver, OSEmailSubscriptionObserver, OSPermissionObserver {
public class OneSignalPlugin
extends FlutterRegistrarResponder
implements MethodCallHandler,
NotificationReceivedHandler,
NotificationOpenedHandler,
InAppMessageClickHandler,
OSSubscriptionObserver,
OSEmailSubscriptionObserver,
OSPermissionObserver {

/** Plugin registration. */
private Registrar flutterRegistrar;
private MethodChannel channel;
private OSNotificationOpenResult coldStartNotificationResult;
private OSInAppMessageAction inAppMessageClickedResult;
private boolean hasSetNotificationOpenedHandler = false;
Expand All @@ -54,18 +58,15 @@ public static void registerWith(Registrar registrar) {
OneSignalPlugin plugin = new OneSignalPlugin();

plugin.waitingForUserPrivacyConsent = false;

plugin.channel = new MethodChannel(registrar.messenger(), "OneSignal");

plugin.channel.setMethodCallHandler(plugin);

plugin.flutterRegistrar = registrar;

OneSignalTagsController.registerWith(registrar);
}

@Override
public void onMethodCall(MethodCall call, Result result) {
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
if (call.method.contentEquals("OneSignal#init"))
initOneSignal(call, result);
else if (call.method.contentEquals("OneSignal#setLogLevel"))
Expand Down Expand Up @@ -105,34 +106,13 @@ else if (call.method.contentEquals("OneSignal#setExternalUserId"))
else if (call.method.contentEquals("OneSignal#removeExternalUserId"))
this.removeExternalUserId(result);
else if (call.method.contentEquals("OneSignal#addTrigger")) {
// call.arguments is being casted to a Map<String, Object> so a try-catch with
// a ClassCastException will be thrown
try {
OneSignal.addTriggers((Map<String, Object>) call.arguments);
} catch (ClassCastException e) {
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.ERROR, "Add trigger failed with error: " + e.getMessage());
e.printStackTrace();
}
addTriggers(call.arguments);
} else if (call.method.contentEquals("OneSignal#addTriggers")) {
// call.arguments is being casted to a Map<String, Object> so a try-catch with
// a ClassCastException will be thrown
try {
OneSignal.addTriggers((Map<String, Object>) call.arguments);
} catch (ClassCastException e) {
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.ERROR, "Add triggers failed with error: " + e.getMessage());
e.printStackTrace();
}
addTriggers(call.arguments);
} else if (call.method.contentEquals("OneSignal#removeTriggerForKey"))
OneSignal.removeTriggerForKey((String) call.arguments);
else if (call.method.contentEquals("OneSignal#removeTriggerForKeys")) {
// call.arguments is being casted to a Collection<String> a try-catch with
// a ClassCastException will be thrown
try {
OneSignal.removeTriggersForKeys((Collection<String>) call.arguments);
} catch (ClassCastException e) {
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.ERROR, "Remove trigger for keys failed with error: " + e.getMessage());
e.printStackTrace();
}
removeTriggersForKeys(call.arguments);
} else if (call.method.contentEquals("OneSignal#getTriggerValueForKey"))
getTriggerValueForKey(result, (String) call.arguments);
else if (call.method.contentEquals("OneSignal#pauseInAppMessages"))
Expand All @@ -143,6 +123,28 @@ else if (call.method.contentEquals("OneSignal#initInAppMessageClickedHandlerPara
replyNotImplemented(result);
}

private void addTriggers(Object arguments) {
// call.arguments is being casted to a Map<String, Object> so a try-catch with
// a ClassCastException will be thrown
try {
OneSignal.addTriggers((Map<String, Object>) arguments);
} catch (ClassCastException e) {
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.ERROR, "Add triggers failed with error: " + e.getMessage());
e.printStackTrace();
}
}

private void removeTriggersForKeys(Object arguments) {
// call.arguments is being casted to a Collection<String> a try-catch with
// a ClassCastException will be thrown
try {
OneSignal.removeTriggersForKeys((Collection<String>) arguments);
} catch (ClassCastException e) {
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.ERROR, "Remove trigger for keys failed with error: " + e.getMessage());
e.printStackTrace();
}
}

private void getTriggerValueForKey(Result reply, String key) {
Object triggerValue = OneSignal.getTriggerValueForKey(key);
replySuccess(reply, triggerValue);
Expand Down Expand Up @@ -334,27 +336,27 @@ private void removeExternalUserId(Result result) {

@Override
public void onOSSubscriptionChanged(OSSubscriptionStateChanges stateChanges) {
this.channel.invokeMethod("OneSignal#subscriptionChanged", OneSignalSerializer.convertSubscriptionStateChangesToMap(stateChanges));
invokeMethodOnUiThread("OneSignal#subscriptionChanged", OneSignalSerializer.convertSubscriptionStateChangesToMap(stateChanges));
}

@Override
public void onOSEmailSubscriptionChanged(OSEmailSubscriptionStateChanges stateChanges) {
this.channel.invokeMethod("OneSignal#emailSubscriptionChanged", OneSignalSerializer.convertEmailSubscriptionStateChangesToMap(stateChanges));
invokeMethodOnUiThread("OneSignal#emailSubscriptionChanged", OneSignalSerializer.convertEmailSubscriptionStateChangesToMap(stateChanges));
}

@Override
public void onOSPermissionChanged(OSPermissionStateChanges stateChanges) {
this.channel.invokeMethod("OneSignal#permissionChanged", OneSignalSerializer.convertPermissionStateChangesToMap(stateChanges));
invokeMethodOnUiThread("OneSignal#permissionChanged", OneSignalSerializer.convertPermissionStateChangesToMap(stateChanges));
}

@Override
public void notificationReceived(OSNotification notification) {
try {
this.channel.invokeMethod("OneSignal#handleReceivedNotification", OneSignalSerializer.convertNotificationToMap(notification));
invokeMethodOnUiThread("OneSignal#handleReceivedNotification", OneSignalSerializer.convertNotificationToMap(notification));
} catch (JSONException e) {
e.printStackTrace();
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.ERROR,
"Encountered an error attempting to convert OSNotification object to hash map: " + e.getMessage());
"Encountered an error attempting to convert OSNotification object to hash map: " + e.getMessage());
}
}

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

try {
this.channel.invokeMethod("OneSignal#handleOpenedNotification", OneSignalSerializer.convertNotificationOpenResultToMap(result));
invokeMethodOnUiThread("OneSignal#handleOpenedNotification", OneSignalSerializer.convertNotificationOpenResultToMap(result));
} catch (JSONException e) {
e.getStackTrace();
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.ERROR,
Expand All @@ -381,49 +383,6 @@ public void inAppMessageClicked(OSInAppMessageAction action) {
return;
}

this.channel.invokeMethod("OneSignal#handleClickedInAppMessage", OneSignalSerializer.convertInAppMessageClickedActionToMap(action));
invokeMethodOnUiThread("OneSignal#handleClickedInAppMessage", OneSignalSerializer.convertInAppMessageClickedActionToMap(action));
}

/**
* MethodChannel class is home to success() method used by Result class
* It has the @UiThread annotation and must be run on UI thread, otherwise a RuntimeException will be thrown
* This will communicate success back to Dart
*/
private void replySuccess(final Result reply, final Object response) {
((Activity) flutterRegistrar.activeContext()).runOnUiThread(new Runnable() {
@Override
public void run() {
reply.success(response);
}
});
}

/**
* MethodChannel class is home to error() method used by Result class
* It has the @UiThread annotation and must be run on UI thread, otherwise a RuntimeException will be thrown
* This will communicate error back to Dart
*/
private void replyError(final Result reply, final String tag, final String message, final Object response) {
((Activity) flutterRegistrar.activeContext()).runOnUiThread(new Runnable() {
@Override
public void run() {
reply.error(tag, message, response);
}
});
}

/**
* MethodChannel class is home to notImplemented() method used by Result class
* It has the @UiThread annotation and must be run on UI thread, otherwise a RuntimeException will be thrown
* This will communicate not implemented back to Dart
*/
private void replyNotImplemented(final Result reply) {
((Activity) flutterRegistrar.activeContext()).runOnUiThread(new Runnable() {
@Override
public void run() {
reply.notImplemented();
}
});
}

}
@@ -1,9 +1,12 @@
package com.onesignal.flutter;

import android.support.annotation.NonNull;

import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.PluginRegistry.Registrar;

import com.onesignal.OneSignal;
Expand All @@ -22,15 +25,17 @@
* Created by bradhesse on 7/17/18.
*/

class OSFlutterChangeTagsHandler implements ChangeTagsUpdateHandler, GetTagsHandler {
class OSFlutterChangeTagsHandler extends FlutterRegistrarResponder implements ChangeTagsUpdateHandler, GetTagsHandler {
private Result result;

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

OSFlutterChangeTagsHandler(Result res) {
OSFlutterChangeTagsHandler(PluginRegistry.Registrar flutterRegistrar, MethodChannel channel, Result res) {
this.flutterRegistrar = flutterRegistrar;
this.channel = channel;
this.result = res;
}

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

try {
this.result.success(OneSignalSerializer.convertJSONObjectToHashMap(tags));
replySuccess(result, OneSignalSerializer.convertJSONObjectToHashMap(tags));
} catch (JSONException exception) {
this.result.error("onesignal", "Encountered an error serializing tags into hashmap: " + exception.getMessage() + "\n" + exception.getStackTrace(), null);
replyError(result, "onesignal", "Encountered an error serializing tags into hashmap: " + exception.getMessage() + "\n" + exception.getStackTrace(), null);
}
}

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

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

@Override
Expand All @@ -60,31 +65,31 @@ public void tagsAvailable(JSONObject jsonObject) {
return;

try {
this.result.success(OneSignalSerializer.convertJSONObjectToHashMap(jsonObject));
replySuccess(result, OneSignalSerializer.convertJSONObjectToHashMap(jsonObject));
} catch (JSONException exception) {
this.result.error("onesignal", "Encountered an error serializing tags into hashmap: " + exception.getMessage() + "\n" + exception.getStackTrace(), null);
replyError(result, "onesignal", "Encountered an error serializing tags into hashmap: " + exception.getMessage() + "\n" + exception.getStackTrace(), null);
}
}
}

public class OneSignalTagsController implements MethodCallHandler {
private MethodChannel channel;
private Registrar registrar;

public static void registerWith(Registrar registrar) {
static void registerWith(Registrar registrar) {
OneSignalTagsController controller = new OneSignalTagsController();
controller.registrar = registrar;
controller.channel = new MethodChannel(registrar.messenger(), "OneSignal#tags");
controller.channel.setMethodCallHandler(controller);
}

@Override
public void onMethodCall(MethodCall call, Result result) {
if (call.method.contentEquals("OneSignal#getTags")) {
OneSignal.getTags(new OSFlutterChangeTagsHandler(result));
} else if (call.method.contentEquals("OneSignal#sendTags")) {
OneSignal.sendTags(new JSONObject((Map<String, Object>) call.arguments), new OSFlutterChangeTagsHandler(result));
} else if (call.method.contentEquals("OneSignal#deleteTags")
) {
OneSignal.deleteTags((List<String>)call.arguments, new OSFlutterChangeTagsHandler(result));
}
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
if (call.method.contentEquals("OneSignal#getTags"))
OneSignal.getTags(new OSFlutterChangeTagsHandler(registrar, channel, result));
else if (call.method.contentEquals("OneSignal#sendTags"))
OneSignal.sendTags(new JSONObject((Map<String, Object>) call.arguments), new OSFlutterChangeTagsHandler(registrar, channel, result));
else if (call.method.contentEquals("OneSignal#deleteTags"))
OneSignal.deleteTags((List<String>)call.arguments, new OSFlutterChangeTagsHandler(registrar, channel, result));
}
}