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. + * + *

The isolate is configured as follows: + * + *

+ * + *

Preconditions: + * + *

+ */ + 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}. + * + *

The isolate is configured as follows: + * + *

+ * + *

Preconditions: + * + *

+ */ + 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 + + + + + + + + + + + + \ 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> setSMSNumber({required String smsNumber, String? smsAuthHashToken}) async { - Map results = - await _channel.invokeMethod("OneSignal#setSMSNumber", {'smsNumber': smsNumber, 'smsAuthHashToken': smsAuthHashToken}); + Future> setSMSNumber( + {required String smsNumber, String? smsAuthHashToken}) async { + Map results = await _channel.invokeMethod( + "OneSignal#setSMSNumber", + {'smsNumber': smsNumber, 'smsAuthHashToken': smsAuthHashToken}); return results.cast(); } @@ -316,9 +385,11 @@ class OneSignal { /// OneSignal allows you to set a custom ID for your users. This makes it so that /// if your app has its own user ID's, you can use your own custom user ID's with /// our API instead of having to save their OneSignal user ID's. - Future> setExternalUserId(String externalId, [String? authHashToken]) async { - Map results = - await (_channel.invokeMethod("OneSignal#setExternalUserId", {'externalUserId' : externalId, 'authHashToken' : authHashToken})); + Future> setExternalUserId(String externalId, + [String? authHashToken]) async { + Map results = await (_channel.invokeMethod( + "OneSignal#setExternalUserId", + {'externalUserId': externalId, 'authHashToken': authHashToken})); return results.cast(); } @@ -330,52 +401,58 @@ class OneSignal { } Future> setLanguage(String language) async { - Map results = - await (_channel.invokeMethod("OneSignal#setLanguage", {'language' : language})); + Map results = await (_channel + .invokeMethod("OneSignal#setLanguage", {'language': language})); return results.cast(); } /// Adds a single key, value trigger, which will trigger an in app message /// if one exists matching the specific trigger added Future addTrigger(String key, Object value) async { - return await _inAppMessagesChannel.invokeMethod("OneSignal#addTrigger", {key : value}); + return await _inAppMessagesChannel + .invokeMethod("OneSignal#addTrigger", {key: value}); } /// Adds one or more key, value triggers, which will trigger in app messages /// (one at a time) if any exist matching the specific triggers added Future addTriggers(Map triggers) async { - return await _inAppMessagesChannel.invokeMethod("OneSignal#addTriggers", triggers); + return await _inAppMessagesChannel.invokeMethod( + "OneSignal#addTriggers", triggers); } /// Remove a single key, value trigger to prevent an in app message from /// showing with that trigger Future removeTriggerForKey(String key) async { - return await _inAppMessagesChannel.invokeMethod("OneSignal#removeTriggerForKey", key); + return await _inAppMessagesChannel.invokeMethod( + "OneSignal#removeTriggerForKey", key); } /// Remove one or more key, value triggers to prevent any in app messages /// from showing with those triggers Future removeTriggersForKeys(List keys) async { - return await _inAppMessagesChannel.invokeMethod("OneSignal#removeTriggersForKeys", keys); + return await _inAppMessagesChannel.invokeMethod( + "OneSignal#removeTriggersForKeys", keys); } /// Get the trigger value associated with the key provided Future getTriggerValueForKey(String key) async { - return await _inAppMessagesChannel.invokeMethod("OneSignal#getTriggerValueForKey", key); + return await _inAppMessagesChannel.invokeMethod( + "OneSignal#getTriggerValueForKey", key); } /// Toggles the showing of all in app messages Future pauseInAppMessages(bool pause) async { - return await _inAppMessagesChannel.invokeMethod("OneSignal#pauseInAppMessages", pause); + return await _inAppMessagesChannel.invokeMethod( + "OneSignal#pauseInAppMessages", pause); } /// Send a normal outcome event for the current session and notifications with the attribution window /// Counted each time sent successfully, failed ones will be cached and reattempted in future Future sendOutcome(String name) async { - var json = await _outcomesChannel.invokeMethod("OneSignal#sendOutcome", name); + var json = + await _outcomesChannel.invokeMethod("OneSignal#sendOutcome", name); - if (json == null) - return new OSOutcomeEvent(); + if (json == null) return new OSOutcomeEvent(); return new OSOutcomeEvent.fromMap(json.cast()); } @@ -383,10 +460,10 @@ class OneSignal { /// Send a unique outcome event for the current session and notifications with the attribution window /// Counted once per notification when sent successfully, failed ones will be cached and reattempted in future Future sendUniqueOutcome(String name) async { - var json = await _outcomesChannel.invokeMethod("OneSignal#sendUniqueOutcome", name); + var json = await _outcomesChannel.invokeMethod( + "OneSignal#sendUniqueOutcome", name); - if (json == null) - return new OSOutcomeEvent(); + if (json == null) return new OSOutcomeEvent(); return new OSOutcomeEvent.fromMap(json.cast()); } @@ -394,10 +471,11 @@ class OneSignal { /// Send an outcome event with a value for the current session and notifications with the attribution window /// Counted each time sent successfully, failed ones will be cached and reattempted in future Future sendOutcomeWithValue(String name, double value) async { - var json = await _outcomesChannel.invokeMethod("OneSignal#sendOutcomeWithValue", {"outcome_name" : name, "outcome_value" : value}); + var json = await _outcomesChannel.invokeMethod( + "OneSignal#sendOutcomeWithValue", + {"outcome_name": name, "outcome_value": value}); - if (json == null) - return new OSOutcomeEvent(); + if (json == null) return new OSOutcomeEvent(); return new OSOutcomeEvent.fromMap(json.cast()); } @@ -415,20 +493,25 @@ class OneSignal { } else if (call.method == 'OneSignal#permissionChanged' && this._onPermissionChangedHandler != null) { this._onPermissionChangedHandler!( - OSPermissionStateChanges(call.arguments.cast())); + OSPermissionStateChanges(call.arguments.cast())); } else if (call.method == 'OneSignal#emailSubscriptionChanged' && this._onEmailSubscriptionChangedHandler != null) { - this._onEmailSubscriptionChangedHandler!( - OSEmailSubscriptionStateChanges(call.arguments.cast())); + this._onEmailSubscriptionChangedHandler!(OSEmailSubscriptionStateChanges( + call.arguments.cast())); } else if (call.method == 'OneSignal#smsSubscriptionChanged' && this._onSMSSubscriptionChangedHandler != null) { - this._onSMSSubscriptionChangedHandler!( - OSSMSSubscriptionStateChanges(call.arguments.cast())); + this._onSMSSubscriptionChangedHandler!(OSSMSSubscriptionStateChanges( + call.arguments.cast())); } else if (call.method == 'OneSignal#handleClickedInAppMessage' && this._onInAppMessageClickedHandler != null) { this._onInAppMessageClickedHandler!( OSInAppMessageAction(call.arguments.cast())); - } else if (call.method == 'OneSignal#handleNotificationWillShowInForeground' && + } else if (call.method == + 'OneSignal#handleNotificationWillShowInForeground' && + this._onNotificationWillShowInForegroundHandler != null) { + this._onNotificationWillShowInForegroundHandler!( + OSNotificationReceivedEvent(call.arguments.cast())); + } else if (call.method == 'OneSignal#handleNotificationWillShow' && this._onNotificationWillShowInForegroundHandler != null) { this._onNotificationWillShowInForegroundHandler!( OSNotificationReceivedEvent(call.arguments.cast()));