diff --git a/android/build.gradle b/android/build.gradle
index 9375113e..794b0725 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -31,6 +31,10 @@ android {
lintOptions {
disable 'InvalidPackage'
}
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
}
dependencies {
diff --git a/android/gradlew b/android/gradlew
old mode 100644
new mode 100755
diff --git a/android/src/main/java/com/onesignal/flutter/BackgroundExecutor.java b/android/src/main/java/com/onesignal/flutter/BackgroundExecutor.java
new file mode 100644
index 00000000..26204825
--- /dev/null
+++ b/android/src/main/java/com/onesignal/flutter/BackgroundExecutor.java
@@ -0,0 +1,305 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package com.onesignal.flutter;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.AssetManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import io.flutter.embedding.engine.FlutterEngine;
+import io.flutter.embedding.engine.FlutterShellArgs;
+import io.flutter.embedding.engine.dart.DartExecutor;
+import io.flutter.embedding.engine.dart.DartExecutor.DartCallback;
+import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry;
+import io.flutter.plugin.common.BinaryMessenger;
+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.view.FlutterCallbackInformation;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * An background execution abstraction which handles initializing a background isolate running a
+ * callback dispatcher, used to invoke Dart callbacks while backgrounded.
+ */
+public class BackgroundExecutor implements MethodCallHandler {
+ private static final String TAG = "OneSignal - BackgroundExecutor";
+ private static final String CHANNEL = "OneSignalBackground";
+ private static final String OSK = "one_signal_key";
+ private static final String CALLBACK_HANDLE_KEY = "callback_handle";
+ private static final String USER_CALLBACK_HANDLE_KEY = "user_callback_handle";
+
+ private static io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback
+ pluginRegistrantCallback;
+ private final AtomicBoolean isCallbackDispatcherReady = new AtomicBoolean(false);
+ /**
+ * The {@link MethodChannel} that connects the Android side of this plugin with the background
+ * Dart isolate that was created by this plugin.
+ */
+ private MethodChannel backgroundChannel;
+
+ private FlutterEngine backgroundFlutterEngine;
+
+ /**
+ * Sets the {@code io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback} used to
+ * register plugins with the newly spawned isolate.
+ *
+ *
Note: this is only necessary for applications using the V1 engine embedding API as plugins
+ * are automatically registered via reflection in the V2 engine embedding API. If not set,
+ * background message callbacks will not be able to utilize functionality from other plugins.
+ */
+ public static void setPluginRegistrant(
+ io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback callback) {
+ pluginRegistrantCallback = callback;
+ }
+
+ /**
+ * Sets the Dart callback handle for the Dart method that is responsible for initializing the
+ * background Dart isolate, preparing it to receive Dart callback tasks requests.
+ */
+ public static void setCallbackDispatcher(long callbackHandle) {
+ Context context = ContextHolder.getApplicationContext();
+ SharedPreferences prefs =
+ context.getSharedPreferences(OSK, 0);
+ prefs.edit().putLong(CALLBACK_HANDLE_KEY, callbackHandle).apply();
+ }
+
+ @Override
+ public void onMethodCall(MethodCall call, @NonNull Result result) {
+ String method = call.method;
+ try {
+ if (method.equals("OneSignal#backgroundHandlerInitialized")) {
+ // This message is sent by the background method channel as soon as the background isolate
+ // is running. From this point forward, the Android side of this plugin can send
+ // callback handles through the background method channel, and the Dart side will execute
+ // the Dart methods corresponding to those callback handles.
+ Log.i(TAG, "Background channel ready");
+
+ result.success(true);
+ } else {
+ result.notImplemented();
+ }
+ } catch (Exception e) {
+ result.error("error", "OneSignal error: " + e.getMessage(), null);
+ }
+ }
+
+ /**
+ * Returns true when the background isolate has started and is ready to handle background
+ * messages.
+ */
+ public boolean isNotRunning() {
+ return !isCallbackDispatcherReady.get();
+ }
+
+ private void onInitialized() {
+ isCallbackDispatcherReady.set(true);
+ }
+
+ /**
+ * Starts running a background Dart isolate within a new {@link FlutterEngine} using a previously
+ * used entrypoint.
+ *
+ *
Entrypoint: The Dart method used the last time this plugin was initialized in the
+ * foreground.
+ *
Run args: none.
+ *
+ *
+ *
Preconditions:
+ *
+ *
+ *
The given callback must correspond to a registered Dart callback. If the handle does not
+ * resolve to a Dart callback then this method does nothing.
+ *
A static {@link #pluginRegistrantCallback} must exist, otherwise a {@link
+ * PluginRegistrantException} will be thrown.
+ *
+ */
+ public void startBackgroundIsolate(IsolateStatusHandler isolate) {
+ Log.i(TAG, "Starting background isolate.");
+ if (isNotRunning()) {
+ long callbackHandle = getPluginCallbackHandle();
+ if (callbackHandle != 0) {
+ startBackgroundIsolate(callbackHandle, null, isolate);
+ }
+ } else {
+ Log.i(TAG, "Background isolate already started. Skipping..");
+ Handler mainHandler = new Handler(Looper.getMainLooper());
+ Runnable myRunnable = () -> { isolate.done(); };
+ mainHandler.post(myRunnable);
+ }
+ }
+
+ /**
+ * Starts running a background Dart isolate within a new {@link FlutterEngine}.
+ *
+ *
Entrypoint: The Dart method represented by {@code callbackHandle}.
+ *
Run args: none.
+ *
+ *
+ *
Preconditions:
+ *
+ *
+ *
The given {@code callbackHandle} must correspond to a registered Dart callback. If the
+ * handle does not resolve to a Dart callback then this method does nothing.
+ *
A static {@link #pluginRegistrantCallback} must exist, otherwise a {@link
+ * PluginRegistrantException} will be thrown.
+ *
+ */
+ public void startBackgroundIsolate(long callbackHandle, FlutterShellArgs shellArgs, IsolateStatusHandler isolate) {
+ if (ContextHolder.getApplicationContext() == null) {
+ Log.i(TAG, "ApplicationContext null when starting isolation");
+ return;
+ }
+ if (backgroundFlutterEngine != null) {
+ Log.i(TAG, "Background isolate already started.");
+ Handler mainHandler = new Handler(Looper.getMainLooper());
+ Runnable myRunnable = () -> { isolate.done(); };
+ mainHandler.post(myRunnable);
+ return;
+ }
+ if (!isNotRunning()) {
+ return;
+ }
+
+ onInitialized();
+
+ Handler mainHandler = new Handler(Looper.getMainLooper());
+ Runnable myRunnable =
+ () -> {
+ io.flutter.view.FlutterMain.startInitialization(ContextHolder.getApplicationContext());
+ io.flutter.view.FlutterMain.ensureInitializationCompleteAsync(
+ ContextHolder.getApplicationContext(),
+ null,
+ mainHandler,
+ () -> {
+ String appBundlePath = io.flutter.view.FlutterMain.findAppBundlePath();
+ AssetManager assets = ContextHolder.getApplicationContext().getAssets();
+ if (shellArgs != null) {
+ Log.i(
+ TAG,
+ "Creating background FlutterEngine instance, with args: "
+ + Arrays.toString(shellArgs.toArray()));
+ backgroundFlutterEngine =
+ new FlutterEngine(
+ ContextHolder.getApplicationContext(), shellArgs.toArray());
+ } else {
+ Log.i(TAG, "Creating background FlutterEngine instance.");
+ backgroundFlutterEngine =
+ new FlutterEngine(ContextHolder.getApplicationContext());
+ }
+ // We need to create an instance of `FlutterEngine` before looking up the
+ // callback. If we don't, the callback cache won't be initialized and the
+ // lookup will fail.
+ FlutterCallbackInformation flutterCallback = FlutterCallbackInformation.lookupCallbackInformation(callbackHandle);
+ DartExecutor executor = backgroundFlutterEngine.getDartExecutor();
+ initializeMethodChannel(executor);
+ DartCallback dartCallback = new DartCallback(assets, appBundlePath, flutterCallback);
+
+ executor.executeDartCallback(dartCallback);
+
+ // The pluginRegistrantCallback should only be set in the V1 embedding as
+ // plugin registration is done via reflection in the V2 embedding.
+ if (pluginRegistrantCallback != null) {
+ pluginRegistrantCallback.registerWith(
+ new ShimPluginRegistry(backgroundFlutterEngine));
+ }
+
+ isolate.done();
+ });
+ };
+ mainHandler.post(myRunnable);
+}
+
+ boolean isDartBackgroundHandlerRegistered() {
+ return getPluginCallbackHandle() != 0;
+ }
+
+ /**
+ * Executes the desired Dart callback in a background Dart isolate.
+ *
+ *
The given {@code intent} should contain a {@code long} extra called "callbackHandle", which
+ * corresponds to a callback registered with the Dart VM.
+ */
+ public void executeDartCallbackInBackgroundIsolate(final HashMap receivedMap) {
+ if (backgroundFlutterEngine == null) {
+ Log.i(
+ TAG,
+ "A background message could not be handled in Dart as no onBackgroundMessage handler has been registered.");
+ return;
+ }
+ if (receivedMap != null) {
+ Log.i(TAG, "Invoking OneSignal#onBackgroundNotification");
+ backgroundChannel.invokeMethod(
+ "OneSignal#onBackgroundNotification",
+ new HashMap() {
+ {
+ put("notificationCallbackHandle", getUserCallbackHandle());
+ put("message", receivedMap);
+ }
+ });
+ } else {
+ Log.e(TAG, "Notification not found.");
+ }
+ }
+
+ /**
+ * Get the users registered Dart callback handle for background messaging. Returns 0 if not set.
+ */
+ private long getUserCallbackHandle() {
+ if (ContextHolder.getApplicationContext() == null) {
+ return 0;
+ }
+ SharedPreferences prefs =
+ ContextHolder.getApplicationContext()
+ .getSharedPreferences(OSK, 0);
+ return prefs.getLong(USER_CALLBACK_HANDLE_KEY, 0);
+ }
+
+ /**
+ * Sets the Dart callback handle for the users Dart handler that is responsible for handling
+ * messaging events in the background.
+ */
+ public static void setUserCallbackHandle(long callbackHandle) {
+ Context context = ContextHolder.getApplicationContext();
+ SharedPreferences prefs =
+ context.getSharedPreferences(OSK, 0);
+ prefs.edit().putLong(USER_CALLBACK_HANDLE_KEY, callbackHandle).apply();
+ }
+
+ /** Get the registered Dart callback handle for the messaging plugin. Returns 0 if not set. */
+ private long getPluginCallbackHandle() {
+ if (ContextHolder.getApplicationContext() == null) {
+ return 0;
+ }
+ SharedPreferences prefs =
+ ContextHolder.getApplicationContext()
+ .getSharedPreferences(OSK, 0);
+ return prefs.getLong(CALLBACK_HANDLE_KEY, 0);
+ }
+
+ // This channel is responsible for sending requests from Android to Dart to execute Dart
+ // callbacks in the background isolate.
+ private void initializeMethodChannel(BinaryMessenger isolate) {
+ backgroundChannel = new MethodChannel(isolate, CHANNEL);
+ backgroundChannel.setMethodCallHandler(this);
+ }
+}
\ No newline at end of file
diff --git a/android/src/main/java/com/onesignal/flutter/ContextHolder.java b/android/src/main/java/com/onesignal/flutter/ContextHolder.java
new file mode 100644
index 00000000..58c434fc
--- /dev/null
+++ b/android/src/main/java/com/onesignal/flutter/ContextHolder.java
@@ -0,0 +1,15 @@
+package com.onesignal.flutter;
+
+import android.content.Context;
+
+public class ContextHolder {
+ private static Context applicationContext;
+
+ public static Context getApplicationContext() {
+ return applicationContext;
+ }
+
+ public static void setApplicationContext(Context applicationContext) {
+ ContextHolder.applicationContext = applicationContext;
+ }
+}
\ No newline at end of file
diff --git a/android/src/main/java/com/onesignal/flutter/IsolateStatusHandler.java b/android/src/main/java/com/onesignal/flutter/IsolateStatusHandler.java
new file mode 100644
index 00000000..a883dd65
--- /dev/null
+++ b/android/src/main/java/com/onesignal/flutter/IsolateStatusHandler.java
@@ -0,0 +1,5 @@
+package com.onesignal.flutter;
+
+public interface IsolateStatusHandler {
+ public void done();
+}
\ No newline at end of file
diff --git a/android/src/main/java/com/onesignal/flutter/OneSignalPlugin.java b/android/src/main/java/com/onesignal/flutter/OneSignalPlugin.java
index 129868fa..d78ddb85 100644
--- a/android/src/main/java/com/onesignal/flutter/OneSignalPlugin.java
+++ b/android/src/main/java/com/onesignal/flutter/OneSignalPlugin.java
@@ -1,6 +1,8 @@
package com.onesignal.flutter;
+import android.app.Activity;
import android.content.Context;
+import android.util.Log;
import com.onesignal.OSDeviceState;
import com.onesignal.OSEmailSubscriptionObserver;
@@ -25,6 +27,7 @@
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
+import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
@@ -52,6 +55,8 @@ public class OneSignalPlugin
private boolean hasSetRequiresPrivacyConsent = false;
private boolean waitingForUserPrivacyConsent = false;
+ private Activity mainActivity;
+
private HashMap notificationReceivedEventCache = new HashMap<>();
public static void registerWith(Registrar registrar) {
@@ -63,6 +68,11 @@ public static void registerWith(Registrar registrar) {
plugin.channel = new MethodChannel(registrar.messenger(), "OneSignal");
plugin.channel.setMethodCallHandler(plugin);
plugin.flutterRegistrar = registrar;
+ plugin.mainActivity = registrar.activity();
+
+ if (ContextHolder.getApplicationContext() == null) {
+ ContextHolder.setApplicationContext(plugin.mainActivity.getApplicationContext());
+ }
// Create a callback for the flutterRegistrar to connect the applications onDestroy
plugin.flutterRegistrar.addViewDestroyListener(new PluginRegistry.ViewDestroyListener() {
@@ -128,6 +138,8 @@ else if (call.method.contentEquals("OneSignal#initInAppMessageClickedHandlerPara
this.initInAppMessageClickedHandlerParams();
else if (call.method.contentEquals("OneSignal#initNotificationWillShowInForegroundHandlerParams"))
this.initNotificationWillShowInForegroundHandlerParams();
+ else if (call.method.contentEquals("OneSignal#initNotificationWillShowHandlerParams"))
+ this.initNotificationWillShowHandlerParams(call, result);
else if (call.method.contentEquals("OneSignal#completeNotification"))
this.completeNotification(call, result);
else if (call.method.contentEquals("OneSignal#clearOneSignalNotifications"))
@@ -301,6 +313,56 @@ private void initInAppMessageClickedHandlerParams() {
}
}
+ private void initNotificationWillShowHandlerParams(MethodCall call, Result result) {
+ this.hasSetNotificationWillShowInForegroundHandler = true;
+
+ @SuppressWarnings("unchecked")
+ Map arguments = ((Map) call.arguments);
+
+ long pluginCallbackHandle = 0;
+ long notificationCallbackHandle = 0;
+
+ Object arg1 = arguments.get("pluginCallbackHandle");
+ Object arg2 = arguments.get("notificationCallbackHandle");
+
+ if (arg1 instanceof Long) {
+ pluginCallbackHandle = (Long) arg1;
+ } else {
+ pluginCallbackHandle = Long.valueOf((Integer) arg1);
+ }
+
+ if (arg2 instanceof Long) {
+ notificationCallbackHandle = (Long) arg2;
+ } else {
+ notificationCallbackHandle = Long.valueOf((Integer) arg2);
+ }
+
+ FlutterShellArgs shellArgs = null;
+ if (mainActivity != null) {
+ // Supports both Flutter Activity types:
+ // io.flutter.embedding.android.FlutterFragmentActivity
+ // io.flutter.embedding.android.FlutterActivity
+ // We could use `getFlutterShellArgs()` but this is only available on `FlutterActivity`.
+ shellArgs = FlutterShellArgs.fromIntent(mainActivity.getIntent());
+ }
+
+ if (XNotificationServiceExtension.be != null) {
+ Log.i("OneSignal", "Attempted to start a duplicate background isolate. Returning...");
+ return;
+ }
+
+ XNotificationServiceExtension.be = new BackgroundExecutor();
+ XNotificationServiceExtension.be.setCallbackDispatcher(pluginCallbackHandle);
+ XNotificationServiceExtension.be.setUserCallbackHandle(notificationCallbackHandle);
+ Log.i("OneSignal", "Starting background isolate. Returning...");
+ XNotificationServiceExtension.be.startBackgroundIsolate(pluginCallbackHandle, shellArgs, new IsolateStatusHandler() {
+ @Override
+ public void done() {
+ // nothing to do here
+ }
+ });
+ }
+
private void initNotificationWillShowInForegroundHandlerParams() {
this.hasSetNotificationWillShowInForegroundHandler = true;
}
@@ -323,6 +385,10 @@ private void completeNotification(MethodCall call, final Result reply) {
boolean shouldDisplay = call.argument("shouldDisplay");
OSNotificationReceivedEvent notificationReceivedEvent = notificationReceivedEventCache.get(notificationId);
+ if (notificationReceivedEvent == null && XNotificationServiceExtension.notificationReceivedEventCache != null) {
+ notificationReceivedEvent = XNotificationServiceExtension.notificationReceivedEventCache.get(notificationId);
+ }
+
if (notificationReceivedEvent == null) {
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.ERROR, "Could not find notification completion block with id: " + notificationId);
return;
diff --git a/android/src/main/java/com/onesignal/flutter/OneSignalSerializer.java b/android/src/main/java/com/onesignal/flutter/OneSignalSerializer.java
index 24d36e2a..97269fe2 100644
--- a/android/src/main/java/com/onesignal/flutter/OneSignalSerializer.java
+++ b/android/src/main/java/com/onesignal/flutter/OneSignalSerializer.java
@@ -28,7 +28,7 @@
import java.util.Iterator;
import java.util.List;
-class OneSignalSerializer {
+public class OneSignalSerializer {
private static HashMap convertSubscriptionStateToMap(OSSubscriptionState state) {
HashMap hash = new HashMap<>();
@@ -232,7 +232,7 @@ static HashMap convertInAppMessageClickedActionToMap(OSInAppMess
return hash;
}
- static HashMap convertNotificationReceivedEventToMap(OSNotificationReceivedEvent notificationReceivedEvent) throws JSONException {
+ public static HashMap convertNotificationReceivedEventToMap(OSNotificationReceivedEvent notificationReceivedEvent) throws JSONException {
return convertNotificationToMap(notificationReceivedEvent.getNotification());
}
diff --git a/android/src/main/java/com/onesignal/flutter/XNotificationServiceExtension.java b/android/src/main/java/com/onesignal/flutter/XNotificationServiceExtension.java
new file mode 100644
index 00000000..3125f313
--- /dev/null
+++ b/android/src/main/java/com/onesignal/flutter/XNotificationServiceExtension.java
@@ -0,0 +1,57 @@
+package com.onesignal.flutter;
+
+import android.os.Looper;
+import android.os.Handler;
+import android.content.Context;
+import android.util.Log;
+import org.json.JSONObject;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.onesignal.flutter.OneSignalSerializer;
+import com.onesignal.flutter.BackgroundExecutor;
+import com.onesignal.flutter.IsolateStatusHandler;
+import io.flutter.plugin.common.MethodChannel;
+import io.flutter.embedding.engine.FlutterEngine;
+import com.onesignal.OSNotification;
+import com.onesignal.OSMutableNotification;
+import com.onesignal.OSNotificationReceivedEvent;
+import com.onesignal.OneSignal.OSRemoteNotificationReceivedHandler;
+
+public class XNotificationServiceExtension implements OSRemoteNotificationReceivedHandler {
+
+ private static final String TAG = "OneSignal - XNotificationServiceExtension";
+ public static BackgroundExecutor be;
+ public static HashMap notificationReceivedEventCache = new HashMap<>();
+
+ @Override
+ public void remoteNotificationReceived(Context context, OSNotificationReceivedEvent notificationReceivedEvent) {
+ OSNotification notification = notificationReceivedEvent.getNotification();
+ XNotificationServiceExtension.notificationReceivedEventCache.put(notification.getNotificationId(), notificationReceivedEvent);
+ JSONObject data = notification.getAdditionalData();
+ Log.i(TAG, "Received Notification Data: " + data.toString());
+
+ if (ContextHolder.getApplicationContext() == null) {
+ ContextHolder.setApplicationContext(context.getApplicationContext());
+ }
+ if (XNotificationServiceExtension.be == null) {
+ XNotificationServiceExtension.be = new BackgroundExecutor();
+ }
+ Log.i(TAG, "Checking isolated BackgroundExecutor.");
+ try {
+ HashMap receivedMap = OneSignalSerializer.convertNotificationReceivedEventToMap(notificationReceivedEvent);
+ XNotificationServiceExtension.be.startBackgroundIsolate(new IsolateStatusHandler() {
+ @Override
+ public void done() {
+ if (XNotificationServiceExtension.be != null) {
+ Log.i(TAG, "Executing dart code in isolate.");
+ XNotificationServiceExtension.be.executeDartCallbackInBackgroundIsolate(receivedMap);
+ }
+ }
+ });
+ } catch (Exception e) {
+ Log.i(TAG, "Exception while executing dart code in isolate", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/example/.flutter-plugins-dependencies b/example/.flutter-plugins-dependencies
new file mode 100644
index 00000000..3db6583e
--- /dev/null
+++ b/example/.flutter-plugins-dependencies
@@ -0,0 +1 @@
+{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"onesignal_flutter","path":"/Users/efraespada/landa-workspace/OneSignal-Flutter-SDK/","dependencies":[]}],"android":[{"name":"onesignal_flutter","path":"/Users/efraespada/landa-workspace/OneSignal-Flutter-SDK/","dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"onesignal_flutter","dependencies":[]}],"date_created":"2021-08-12 01:52:39.082971","version":"2.2.3"}
\ No newline at end of file
diff --git a/example/.idea/libraries/Dart_SDK.xml b/example/.idea/libraries/Dart_SDK.xml
index 5a16d312..62be7eab 100644
--- a/example/.idea/libraries/Dart_SDK.xml
+++ b/example/.idea/libraries/Dart_SDK.xml
@@ -1,17 +1,26 @@
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/.idea/workspace.xml b/example/.idea/workspace.xml
index 5b3388cc..eb69491f 100644
--- a/example/.idea/workspace.xml
+++ b/example/.idea/workspace.xml
@@ -1,36 +1,62 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
+
+
+
-
+
+
+
-
+
+
+
+
+ 1628559160573
+
+
+ 1628559160573
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
index b8b6e592..77890719 100644
--- a/example/android/app/build.gradle
+++ b/example/android/app/build.gradle
@@ -26,11 +26,16 @@ if (flutterRoot == null) {
}
apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 28
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
+ }
+
lintOptions {
disable 'InvalidPackage'
}
@@ -52,6 +57,11 @@ android {
signingConfig signingConfigs.debug
}
}
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
}
flutter {
@@ -62,4 +72,5 @@ dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
index fba2fd08..56a3b49d 100644
--- a/example/android/app/src/main/AndroidManifest.xml
+++ b/example/android/app/src/main/AndroidManifest.xml
@@ -1,5 +1,6 @@
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.onesignal.onesignalexample">
-
+
+ android:name="io.flutter.embedding.android.NormalTheme"
+ android:resource="@style/NormalTheme"
+ />
+
+
+
+
+
+
+
diff --git a/example/android/app/src/main/java/com/onesignal/onesignalexample/MainActivity.java b/example/android/app/src/main/java/com/onesignal/onesignalexample/MainActivity.java
deleted file mode 100644
index c8ac8c4a..00000000
--- a/example/android/app/src/main/java/com/onesignal/onesignalexample/MainActivity.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.onesignal.onesignalexample;
-
-import android.os.Bundle;
-import io.flutter.app.FlutterActivity;
-import io.flutter.plugins.GeneratedPluginRegistrant;
-
-public class MainActivity extends FlutterActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- GeneratedPluginRegistrant.registerWith(this);
- }
-}
diff --git a/example/android/app/src/main/kotlin/com/onesignal/onesignalexample/MainActivity.kt b/example/android/app/src/main/kotlin/com/onesignal/onesignalexample/MainActivity.kt
new file mode 100644
index 00000000..85a273d6
--- /dev/null
+++ b/example/android/app/src/main/kotlin/com/onesignal/onesignalexample/MainActivity.kt
@@ -0,0 +1,8 @@
+package com.onesignal.onesignalexample
+
+import io.flutter.embedding.android.FlutterActivity
+import io.flutter.embedding.engine.FlutterEngine
+
+open class MainActivity: FlutterActivity() {
+
+}
diff --git a/example/android/app/src/main/kotlin/com/onesignal/onesignalexample/NotificationServiceExtension.kt b/example/android/app/src/main/kotlin/com/onesignal/onesignalexample/NotificationServiceExtension.kt
new file mode 100644
index 00000000..9c173162
--- /dev/null
+++ b/example/android/app/src/main/kotlin/com/onesignal/onesignalexample/NotificationServiceExtension.kt
@@ -0,0 +1,5 @@
+package com.onesignal.onesignalexample
+
+import com.onesignal.flutter.XNotificationServiceExtension
+
+class NotificationServiceExtension : XNotificationServiceExtension()
\ No newline at end of file
diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml
index 00fa4417..d74aa35c 100644
--- a/example/android/app/src/main/res/values/styles.xml
+++ b/example/android/app/src/main/res/values/styles.xml
@@ -1,8 +1,18 @@
-
+
+
diff --git a/example/android/build.gradle b/example/android/build.gradle
index e0d7ae2c..cca94918 100644
--- a/example/android/build.gradle
+++ b/example/android/build.gradle
@@ -1,4 +1,6 @@
buildscript {
+ ext.kotlin_version = '1.4.21'
+
repositories {
google()
jcenter()
@@ -6,6 +8,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
diff --git a/example/android/settings_aar.gradle b/example/android/settings_aar.gradle
new file mode 100644
index 00000000..e7b4def4
--- /dev/null
+++ b/example/android/settings_aar.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/example/ios/Flutter/flutter_export_environment.sh b/example/ios/Flutter/flutter_export_environment.sh
new file mode 100755
index 00000000..db748bff
--- /dev/null
+++ b/example/ios/Flutter/flutter_export_environment.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+# This is a generated file; do not edit or check into version control.
+export "FLUTTER_ROOT=/Users/efraespada/flutter"
+export "FLUTTER_APPLICATION_PATH=/Users/efraespada/landa-workspace/OneSignal-Flutter-SDK/example"
+export "COCOAPODS_PARALLEL_CODE_SIGN=true"
+export "FLUTTER_TARGET=lib/main.dart"
+export "FLUTTER_BUILD_DIR=build"
+export "SYMROOT=${SOURCE_ROOT}/../build/ios"
+export "FLUTTER_BUILD_NAME=1.0.0"
+export "FLUTTER_BUILD_NUMBER=1"
+export "DART_OBFUSCATION=false"
+export "TRACK_WIDGET_CREATION=false"
+export "TREE_SHAKE_ICONS=false"
+export "PACKAGE_CONFIG=.packages"
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 62c50cb8..09735396 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -4,6 +4,13 @@ import 'dart:async';
//import OneSignal
import 'package:onesignal_flutter/onesignal_flutter.dart';
+Future _backgroundHandler(OSNotificationReceivedEvent event) async {
+ print('NEW FOREGROUND HANDLER CALLED WITH: $event');
+
+ /// Display Notification, send null to not display
+ event.complete(event.notification);
+}
+
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
@@ -37,28 +44,18 @@ class _MyAppState extends State {
OneSignal.shared
.setNotificationOpenedHandler((OSNotificationOpenedResult result) {
- print('NOTIFICATION OPENED HANDLER CALLED WITH: ${result}');
- this.setState(() {
- _debugLabelString =
- "Opened notification: \n${result.notification.jsonRepresentation().replaceAll("\\n", "\n")}";
+ print('NOTIFICATION OPENED HANDLER CALLED WITH: ${result}');
+ this.setState(() {
+ _debugLabelString =
+ "Opened notification: \n${result.notification.jsonRepresentation().replaceAll("\\n", "\n")}";
});
});
- OneSignal.shared
- .setNotificationWillShowInForegroundHandler((OSNotificationReceivedEvent event) {
- print('FOREGROUND HANDLER CALLED WITH: ${event}');
- /// Display Notification, send null to not display
- event.complete(null);
-
- this.setState(() {
- _debugLabelString =
- "Notification received in foreground notification: \n${event.notification.jsonRepresentation().replaceAll("\\n", "\n")}";
- });
- });
+ OneSignal.shared.setNotificationWillShowHandler(_backgroundHandler);
OneSignal.shared
.setInAppMessageClickedHandler((OSInAppMessageAction action) {
- this.setState(() {
+ this.setState(() {
_debugLabelString =
"In App Message Clicked: \n${action.jsonRepresentation().replaceAll("\\n", "\n")}";
});
@@ -78,14 +75,13 @@ class _MyAppState extends State {
print("EMAIL SUBSCRIPTION STATE CHANGED ${changes.jsonRepresentation()}");
});
- OneSignal.shared.setSMSSubscriptionObserver(
- (OSSMSSubscriptionStateChanges changes) {
+ OneSignal.shared
+ .setSMSSubscriptionObserver((OSSMSSubscriptionStateChanges changes) {
print("SMS SUBSCRIPTION STATE CHANGED ${changes.jsonRepresentation()}");
});
// NOTE: Replace with your own app ID from https://www.onesignal.com
- await OneSignal.shared
- .setAppId("380dc082-5231-4cc2-ab51-a03da5a0e4c2");
+ await OneSignal.shared.setAppId("380dc082-5231-4cc2-ab51-a03da5a0e4c2");
bool requiresConsent = await OneSignal.shared.requiresUserPrivacyConsent();
@@ -101,7 +97,8 @@ class _MyAppState extends State {
// Some examples of how to use Outcome Events public methods with OneSignal SDK
oneSignalOutcomeEventsExamples();
- bool userProvidedPrivacyConsent = await OneSignal.shared.userProvidedPrivacyConsent();
+ bool userProvidedPrivacyConsent =
+ await OneSignal.shared.userProvidedPrivacyConsent();
print("USER PROVIDED PRIVACY CONSENT: $userProvidedPrivacyConsent");
}
@@ -148,7 +145,8 @@ class _MyAppState extends State {
OneSignal.shared.getDeviceState().then((deviceState) {
print("DeviceState: ${deviceState?.jsonRepresentation()}");
this.setState(() {
- _debugLabelString = deviceState?.jsonRepresentation() ?? "Device state null";
+ _debugLabelString =
+ deviceState?.jsonRepresentation() ?? "Device state null";
});
});
}
@@ -175,7 +173,7 @@ class _MyAppState extends State {
});
}
- void _handleSetSMSNumber() {
+ void _handleSetSMSNumber() {
if (_smsNumber == null) return;
print("Setting SMS Number");
@@ -233,29 +231,28 @@ class _MyAppState extends State {
if (_externalUserId == null) return;
OneSignal.shared.setExternalUserId(_externalUserId!).then((results) {
- if (results == null) return;
+ if (results == null) return;
- this.setState(() {
- _debugLabelString = "External user id set: $results";
- });
+ this.setState(() {
+ _debugLabelString = "External user id set: $results";
+ });
});
}
void _handleRemoveExternalUserId() {
OneSignal.shared.removeExternalUserId().then((results) {
- if (results == null) return;
+ if (results == null) return;
- this.setState(() {
- _debugLabelString = "External user id removed: $results";
- });
+ this.setState(() {
+ _debugLabelString = "External user id removed: $results";
+ });
});
}
void _handleSendNotification() async {
var deviceState = await OneSignal.shared.getDeviceState();
- if (deviceState == null || deviceState.userId == null)
- return;
+ if (deviceState == null || deviceState.userId == null) return;
var playerId = deviceState.userId!;
@@ -283,8 +280,7 @@ class _MyAppState extends State {
void _handleSendSilentNotification() async {
var deviceState = await OneSignal.shared.getDeviceState();
- if (deviceState == null || deviceState.userId == null)
- return;
+ if (deviceState == null || deviceState.userId == null) return;
var playerId = deviceState.userId!;
@@ -317,7 +313,8 @@ class _MyAppState extends State {
OneSignal.shared.removeTriggerForKey("trigger_2");
// Get the value for a trigger by its key
- Object? triggerValue = await OneSignal.shared.getTriggerValueForKey("trigger_3");
+ Object? triggerValue =
+ await OneSignal.shared.getTriggerValueForKey("trigger_3");
print("'trigger_3' key trigger value: ${triggerValue?.toString()}");
// Create a list and bulk remove triggers based on keys supplied
@@ -352,8 +349,8 @@ class _MyAppState extends State {
}
Future outcomeAwaitExample() async {
- var outcomeEvent = await OneSignal.shared.sendOutcome("await_normal_1");
- print(outcomeEvent.jsonRepresentation());
+ var outcomeEvent = await OneSignal.shared.sendOutcome("await_normal_1");
+ print(outcomeEvent.jsonRepresentation());
}
@override
@@ -382,10 +379,8 @@ class _MyAppState extends State {
_handlePromptForPushPermission, !_enableConsentButton)
]),
new TableRow(children: [
- new OneSignalButton(
- "Print Device State",
- _handleGetDeviceState,
- !_enableConsentButton)
+ new OneSignalButton("Print Device State",
+ _handleGetDeviceState, !_enableConsentButton)
]),
new TableRow(children: [
new TextField(
@@ -436,12 +431,12 @@ class _MyAppState extends State {
)
]),
new TableRow(children: [
- new OneSignalButton(
- "Set SMS Number", _handleSetSMSNumber, !_enableConsentButton)
+ new OneSignalButton("Set SMS Number", _handleSetSMSNumber,
+ !_enableConsentButton)
]),
new TableRow(children: [
- new OneSignalButton("Logout SMS Number", _handleLogoutSMSNumber,
- !_enableConsentButton)
+ new OneSignalButton("Logout SMS Number",
+ _handleLogoutSMSNumber, !_enableConsentButton)
]),
new TableRow(children: [
new OneSignalButton("Provide GDPR Consent", _handleConsent,
@@ -484,12 +479,12 @@ class _MyAppState extends State {
)
]),
new TableRow(children: [
- new OneSignalButton(
- "Set External User ID", _handleSetExternalUserId, !_enableConsentButton)
+ new OneSignalButton("Set External User ID",
+ _handleSetExternalUserId, !_enableConsentButton)
]),
new TableRow(children: [
- new OneSignalButton(
- "Remove External User ID", _handleRemoveExternalUserId, !_enableConsentButton)
+ new OneSignalButton("Remove External User ID",
+ _handleRemoveExternalUserId, !_enableConsentButton)
]),
new TableRow(children: [
new Container(
diff --git a/example/onesignal_example.iml b/example/onesignal_example.iml
index e5c83719..0fa0165e 100644
Binary files a/example/onesignal_example.iml and b/example/onesignal_example.iml differ
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index 14b31e7f..979ddb2d 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -16,7 +16,8 @@ dev_dependencies:
flutter_test:
sdk: flutter
- onesignal_flutter: ^3.1.0
+ onesignal_flutter:
+ path: ../
# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec
diff --git a/lib/onesignal_flutter.dart b/lib/onesignal_flutter.dart
index 0dd9e1b8..de0d133d 100644
--- a/lib/onesignal_flutter.dart
+++ b/lib/onesignal_flutter.dart
@@ -1,4 +1,8 @@
import 'dart:async';
+import 'dart:io';
+import 'dart:ui';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:onesignal_flutter/src/permission.dart';
import 'package:onesignal_flutter/src/subscription.dart';
@@ -21,11 +25,55 @@ export 'src/outcome_event.dart';
typedef void ReceivedNotificationHandler(OSNotification notification);
typedef void OpenedNotificationHandler(OSNotificationOpenedResult openedResult);
typedef void SubscriptionChangedHandler(OSSubscriptionStateChanges changes);
-typedef void EmailSubscriptionChangeHandler(OSEmailSubscriptionStateChanges changes);
-typedef void SMSSubscriptionChangeHandler(OSSMSSubscriptionStateChanges changes);
+typedef void EmailSubscriptionChangeHandler(
+ OSEmailSubscriptionStateChanges changes);
+typedef void SMSSubscriptionChangeHandler(
+ OSSMSSubscriptionStateChanges changes);
typedef void PermissionChangeHandler(OSPermissionStateChanges changes);
typedef void InAppMessageClickedHandler(OSInAppMessageAction action);
-typedef void NotificationWillShowInForegroundHandler(OSNotificationReceivedEvent event);
+typedef void NotificationWillShowInForegroundHandler(
+ OSNotificationReceivedEvent event);
+
+void _callbackDispatcher() {
+ // Initialize state necessary for MethodChannels.
+ WidgetsFlutterBinding.ensureInitialized();
+
+ const MethodChannel _channel = MethodChannel('OneSignalBackground');
+
+ // This is where we handle background events from the native portion of the plugin.
+ _channel.setMethodCallHandler((MethodCall call) async {
+ if (call.method == 'OneSignal#onBackgroundNotification') {
+ final CallbackHandle notificationCallbackHandle =
+ CallbackHandle.fromRawHandle(
+ call.arguments['notificationCallbackHandle']);
+
+ // PluginUtilities.getCallbackFromHandle performs a lookup based on the
+ // callback handle and returns a tear-off of the original callback.
+ final closure =
+ PluginUtilities.getCallbackFromHandle(notificationCallbackHandle)!
+ as Future Function(OSNotificationReceivedEvent);
+
+ try {
+ Map messageMap =
+ Map.from(call.arguments['message']);
+ final notification = OSNotificationReceivedEvent(messageMap);
+ await closure(notification);
+ } catch (e) {
+ // ignore: avoid_print
+ print(
+ 'OneSignal: An error occurred in your background messaging handler:');
+ // ignore: avoid_print
+ print(e);
+ }
+ } else {
+ throw UnimplementedError('${call.method} has not been implemented');
+ }
+ });
+
+ // Once we've finished initializing, let the native portion of the plugin
+ // know that it can start scheduling alarms.
+ _channel.invokeMethod('OneSignal#backgroundHandlerInitialized');
+}
class OneSignal {
/// A singleton representing the OneSignal SDK.
@@ -37,7 +85,8 @@ class OneSignal {
// private channels used to bridge to ObjC/Java
MethodChannel _channel = const MethodChannel('OneSignal');
MethodChannel _tagsChannel = const MethodChannel('OneSignal#tags');
- MethodChannel _inAppMessagesChannel = const MethodChannel('OneSignal#inAppMessages');
+ MethodChannel _inAppMessagesChannel =
+ const MethodChannel('OneSignal#inAppMessages');
MethodChannel _outcomesChannel = const MethodChannel('OneSignal#outcomes');
// event handlers
@@ -47,7 +96,8 @@ class OneSignal {
SMSSubscriptionChangeHandler? _onSMSSubscriptionChangedHandler;
PermissionChangeHandler? _onPermissionChangedHandler;
InAppMessageClickedHandler? _onInAppMessageClickedHandler;
- NotificationWillShowInForegroundHandler? _onNotificationWillShowInForegroundHandler;
+ NotificationWillShowInForegroundHandler?
+ _onNotificationWillShowInForegroundHandler;
// constructor method
OneSignal() {
@@ -60,8 +110,7 @@ class OneSignal {
_onesignalLog(OSLogLevel.verbose,
"Initializing the OneSignal Flutter SDK ($sdkVersion)");
- await _channel.invokeMethod(
- 'OneSignal#setAppId', {'appId': appId});
+ await _channel.invokeMethod('OneSignal#setAppId', {'appId': appId});
}
/// Sets the log level for the SDK. The first parameter (logLevel) controls
@@ -116,9 +165,28 @@ class OneSignal {
/// The notification foreground handler is called whenever a notification arrives
/// and the application is in foreground
- void setNotificationWillShowInForegroundHandler(NotificationWillShowInForegroundHandler handler) {
+ void setNotificationWillShowInForegroundHandler(
+ NotificationWillShowInForegroundHandler handler) {
_onNotificationWillShowInForegroundHandler = handler;
- _channel.invokeMethod("OneSignal#initNotificationWillShowInForegroundHandlerParams");
+ _channel.invokeMethod(
+ "OneSignal#initNotificationWillShowInForegroundHandlerParams");
+ }
+
+ void setNotificationWillShowHandler(
+ NotificationWillShowInForegroundHandler handler) {
+ if (Platform.isIOS) {
+ setNotificationWillShowInForegroundHandler(handler);
+ return;
+ }
+ final CallbackHandle bgHandle =
+ PluginUtilities.getCallbackHandle(_callbackDispatcher)!;
+ final CallbackHandle notificationHandler =
+ PluginUtilities.getCallbackHandle(handler)!;
+ _channel
+ .invokeMapMethod("OneSignal#initNotificationWillShowHandlerParams", {
+ 'notificationCallbackHandle': notificationHandler.toRawHandle(),
+ 'pluginCallbackHandle': bgHandle.toRawHandle(),
+ });
}
/// The notification foreground handler is called whenever a notification arrives
@@ -223,11 +291,9 @@ class OneSignal {
/// Returns an `OSDeviceState` object, which contains the current device state
Future getDeviceState() async {
- var json =
- await _channel.invokeMethod("OneSignal#getDeviceState");
+ var json = await _channel.invokeMethod("OneSignal#getDeviceState");
- if ((json.cast()).isEmpty)
- return null;
+ if ((json.cast()).isEmpty) return null;
return OSDeviceState(json.cast());
}
@@ -263,8 +329,8 @@ class OneSignal {
/// Allows you to manually cancel a single OneSignal notification based on its Android notification integer ID
void removeNotification(int notificationId) {
- _channel.invokeMethod("OneSignal#removeNotification",
- {'notificationId': notificationId});
+ _channel.invokeMethod(
+ "OneSignal#removeNotification", {'notificationId': notificationId});
}
/// Allows you to prompt the user for permission to use location services
@@ -283,7 +349,8 @@ class OneSignal {
/// Identity Verification. The email auth hash is a hash of your app's API key and the
/// user ID. We recommend you generate this token from your backend server, do NOT
/// store your API key in your app as this is highly insecure.
- Future setEmail({required String email, String? emailAuthHashToken}) async {
+ Future setEmail(
+ {required String email, String? emailAuthHashToken}) async {
return await _channel.invokeMethod("OneSignal#setEmail",
{'email': email, 'emailAuthHashToken': emailAuthHashToken});
}
@@ -299,9 +366,11 @@ class OneSignal {
/// Identity Verification. The SMS auth hash is a hash of your app's API key and the
/// user ID. We recommend you generate this token from your backend server, do NOT
/// store your API key in your app as this is highly insecure.
- Future