diff --git a/CHANGELOG.md b/CHANGELOG.md index b85339e..eb0f47a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## CHANGE LOG -### Version 2.3.2 *(9th May 2024)* +### Version 2.4.0 *(10th May 2024)* ------------------------------------------- **What's new** * **[Web Platform]** @@ -10,6 +10,9 @@ * **[Web Platform]** * Added [JS package](https://pub.dev/packages/js) dependency to handle latest versions. +* **[Android Platform]** + * Fixes [#114](https://github.com/CleverTap/clevertap-flutter/issues/114) - an issue related to callbacks from native to dart when a 3rd party plugin like `flutter_workmanager` is used. + ### Version 2.3.1 *(19th April 2024)* ------------------------------------------- **Bug Fixes** diff --git a/README.md b/README.md index c999b2f..175ac17 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ To get started, sign up [here](https://clevertap.com/live-product-demo/). ```yaml dependencies: -clevertap_plugin: 2.3.2 +clevertap_plugin: 2.4.0 ``` - Run `flutter packages get` to install the SDK diff --git a/android/build.gradle b/android/build.gradle index 3291c63..aa395dc 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -3,7 +3,7 @@ plugins { } group 'com.clevertap.clevertap_plugin' -version '2.3.2' +version '2.4.0' rootProject.allprojects { repositories { diff --git a/android/src/main/java/com/clevertap/clevertap_plugin/CleverTapPlugin.java b/android/src/main/java/com/clevertap/clevertap_plugin/CleverTapPlugin.java index a37c254..ba94915 100644 --- a/android/src/main/java/com/clevertap/clevertap_plugin/CleverTapPlugin.java +++ b/android/src/main/java/com/clevertap/clevertap_plugin/CleverTapPlugin.java @@ -46,6 +46,8 @@ import com.clevertap.clevertap_plugin.CleverTapTypeUtils.LongUtil; import com.clevertap.clevertap_plugin.isolate.IsolateHandlePreferences; +import java.util.HashSet; +import java.util.Set; import org.json.JSONException; import org.json.JSONObject; @@ -93,13 +95,14 @@ public class CleverTapPlugin implements ActivityAware, private MethodChannel dartToNativeMethodChannel; - private static MethodChannel nativeToDartMethodChannel; + private MethodChannel lastNativeToDartMethodChannel; private CleverTapAPI cleverTapAPI; private Context context; public static Map variables = new HashMap<>(); + public static Set nativeToDartMethodChannelSet = new HashSet<>(); /** * Plugin registration. @@ -153,6 +156,7 @@ public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { + Log.d(TAG,"onAttachedToEngine"); setupPlugin(binding.getApplicationContext(), binding.getBinaryMessenger(), null); } @@ -168,8 +172,10 @@ public void onDetachedFromActivityForConfigChanges() { @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + Log.d(TAG,"onDetachedFromEngine"); + nativeToDartMethodChannelSet.remove(this.lastNativeToDartMethodChannel); + this.lastNativeToDartMethodChannel = null; dartToNativeMethodChannel = null; - nativeToDartMethodChannel = null; context = null; } @@ -1395,49 +1401,55 @@ private void initializeInbox(Result result) { } private void invokeMethodOnUiThread(final String methodName, final String cleverTapID) { - final MethodChannel channel = nativeToDartMethodChannel; - if (channel == null) { - Log.d(TAG, "methodChannel in invokeMethodOnUiThread(String) is null"); - return; - } - runOnMainThread(() -> { - if (!cleverTapID.isEmpty()) { - channel.invokeMethod(methodName, cleverTapID); - } else { - channel.invokeMethod(methodName, null); + Log.d(TAG, "methodChannelSet in invokeMethodOnUiThread(String) is of size " + nativeToDartMethodChannelSet.size()); + + for(MethodChannel channel : nativeToDartMethodChannelSet) { + if (channel != null) { + Log.d(TAG, "methodChannelSet in invokeMethodOnUiThread(String) " + channel); + runOnMainThread(() -> { + if (!cleverTapID.isEmpty()) { + channel.invokeMethod(methodName, cleverTapID); + } else { + channel.invokeMethod(methodName, null); + } + }); } - }); + } } @SuppressWarnings("SameParameterValue") private void invokeMethodOnUiThread(final String methodName, final boolean params) { - final MethodChannel channel = nativeToDartMethodChannel; - if (channel == null) { - Log.d(TAG, "params in invokeMethodOnUiThread(boolean) is null"); - return; + Log.d(TAG, "methodChannelSet in invokeMethodOnUiThread(boolean) is of size" + nativeToDartMethodChannelSet.size()); + + for(MethodChannel channel : nativeToDartMethodChannelSet) { + if (channel != null) { + Log.d(TAG, "methodChannelSet in invokeMethodOnUiThread(boolean) " + channel); + runOnMainThread(() -> channel.invokeMethod(methodName, params)); + } } - runOnMainThread(() -> { - channel.invokeMethod(methodName, params); - }); } private void invokeMethodOnUiThread(final String methodName, final Map map) { - final MethodChannel channel = nativeToDartMethodChannel; - if (channel == null) { - Log.d(TAG, "methodChannel in invokeMethodOnUiThread(Map) is null"); - return; + Log.d(TAG, "methodChannelSet in invokeMethodOnUiThread(Map) is of size " + nativeToDartMethodChannelSet.size()); + + for(MethodChannel channel : nativeToDartMethodChannelSet) { + if (channel != null) { + Log.d(TAG, "methodChannel in invokeMethodOnUiThread(Map) " + channel); + runOnMainThread(() -> channel.invokeMethod(methodName, map)); + } } - runOnMainThread(() -> channel.invokeMethod(methodName, map)); } @SuppressWarnings("SameParameterValue") private void invokeMethodOnUiThread(final String methodName, final ArrayList list) { - final MethodChannel channel = nativeToDartMethodChannel; - if (channel == null) { - Log.d(TAG, "methodChannel in invokeMethodOnUiThread(ArrayList) is null"); - return; + Log.d(TAG, "methodChannelSet in invokeMethodOnUiThread(ArrayList) is of size " + nativeToDartMethodChannelSet.size()); + + for(MethodChannel channel : nativeToDartMethodChannelSet) { + if (channel != null) { + Log.d(TAG, "methodChannel in invokeMethodOnUiThread(ArrayList)" + channel); + runOnMainThread(() -> channel.invokeMethod(methodName, list)); + } } - runOnMainThread(() -> channel.invokeMethod(methodName, list)); } private boolean isCleverTapNotNull(CleverTapAPI cleverTapAPI) { @@ -1880,13 +1892,12 @@ private MethodChannel getMethodChannel(String channelName, BinaryMessenger messe private void setupPlugin(Context context, BinaryMessenger messenger, Registrar registrar) { this.dartToNativeMethodChannel = getMethodChannel("clevertap_plugin/dart_to_native", messenger, registrar); - if (nativeToDartMethodChannel == null) { - // set nativeToDartMethodChannel channel once and it has to be static field - // as per https://github.com/firebase/flutterfire/issues/9689 because multiple - // instances of the CleverTap plugin can be created in case onBackgroundMessage handler - // of FCM plugin. - nativeToDartMethodChannel = getMethodChannel("clevertap_plugin/native_to_dart", messenger, registrar); - } + + // lastNativeToDartMethodChannel is added to a set and not kept as a static field to ensure callbacks work when a background isolate is spawned + // Background Isolates are spawned by several libraries like flutter_workmanager and flutter_firebasemessaging + lastNativeToDartMethodChannel = getMethodChannel("clevertap_plugin/native_to_dart", messenger, registrar); + nativeToDartMethodChannelSet.add(lastNativeToDartMethodChannel); + this.dartToNativeMethodChannel.setMethodCallHandler(this); this.context = context.getApplicationContext(); this.cleverTapAPI = CleverTapAPI.getDefaultInstance(this.context); diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index cf0e6d8..fb59186 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -55,13 +55,13 @@ dependencies { //implementation fileTree('libs') implementation "com.clevertap.android:push-templates:1.2.3" implementation 'com.google.firebase:firebase-messaging:23.4.1' - implementation 'androidx.core:core:1.3.0' - implementation 'androidx.fragment:fragment:1.3.6' + implementation 'androidx.core:core:1.13.1' + implementation 'androidx.fragment:fragment:1.7.0' //MANDATORY for App Inbox - implementation 'androidx.appcompat:appcompat:1.3.1' - implementation 'androidx.recyclerview:recyclerview:1.2.1' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.recyclerview:recyclerview:1.3.2' implementation 'androidx.viewpager:viewpager:1.0.0' - implementation 'com.google.android.material:material:1.4.0' + implementation 'com.google.android.material:material:1.11.0' implementation 'com.github.bumptech.glide:glide:4.12.0' //Optional ExoPlayer Libraries for Audio/Video Inbox Messages. Audio/Video messages will be dropped without these dependencies diff --git a/example/lib/main.dart b/example/lib/main.dart index 4120988..03b885b 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,13 +1,16 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io' show Platform; +import 'dart:io' show Platform, sleep; import 'package:example/notification_button.dart'; +import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:clevertap_plugin/clevertap_plugin.dart'; import 'package:example/deeplink_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_styled_toast/flutter_styled_toast.dart'; +import 'package:workmanager/workmanager.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; @pragma('vm:entry-point') void onKilledStateNotificationClickedHandler(Map map) async { @@ -15,8 +18,47 @@ void onKilledStateNotificationClickedHandler(Map map) async { print("Notification Payload received: " + map.toString()); } +@pragma('vm:entry-point') // Mandatory if the App is obfuscated or using Flutter 3.1+ +void callbackDispatcher() { + // This is a dummy work manager to test usecases with background isolates + + Workmanager().executeTask((task, inputData) { + print("Native started background task: $task"); + sleep(Duration(seconds: 30)); + print("Native called background task: $task"); //simpleTask will be emitted here. + return Future.value(true); + }); +} + +Future _firebaseBackgroundMessageHandler(RemoteMessage message) async { + // This is a dummy firebase integration to test usecases with background isolates + await Firebase.initializeApp(); + print("_firebaseBackgroundMessageHandler Background"); + // CleverTapPlugin.createNotification(jsonEncode(message.data)); +} + +/// Handles foreground messages of FCM +void _firebaseForegroundMessageHandler(RemoteMessage remoteMessage) { + print('_firebaseForegroundMessageHandler called'); + // CleverTapPlugin.createNotification(jsonEncode(remoteMessage.data)); +} + + void main() async { WidgetsFlutterBinding.ensureInitialized(); + Workmanager().initialize( + callbackDispatcher, // The top level function, aka callbackDispatcher + isInDebugMode: true // If enabled it will post a notification whenever the task is running. Handy for debugging tasks + ); + Workmanager().registerOneOffTask( + "periodic-task-identifier", + "simplePeriodicTask" + ); + + await Firebase.initializeApp(); + FirebaseMessaging.onMessage.listen(_firebaseForegroundMessageHandler); + FirebaseMessaging.onBackgroundMessage(_firebaseBackgroundMessageHandler); + CleverTapPlugin.onKilledStateNotificationClicked( onKilledStateNotificationClickedHandler); runApp(MaterialApp( @@ -53,6 +95,7 @@ class _MyAppState extends State { @override void initState() { + print("initState"); super.initState(); initPlatformState(); activateCleverTapFlutterPluginHandlers(); diff --git a/example/pubspec.yaml b/example/pubspec.yaml index b71dd89..e37bad8 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -24,6 +24,9 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.3 + workmanager: ^0.5.2 + firebase_messaging: ^14.9.2 + firebase_core: ^2.31.0 dev_dependencies: flutter_test: diff --git a/ios/clevertap_plugin.podspec b/ios/clevertap_plugin.podspec index 1f73b09..f346743 100644 --- a/ios/clevertap_plugin.podspec +++ b/ios/clevertap_plugin.podspec @@ -3,7 +3,7 @@ # Pod::Spec.new do |s| s.name = 'clevertap_plugin' - s.version = '2.3.2' + s.version = '2.4.0' s.summary = 'CleverTap Flutter plugin.' s.description = 'The CleverTap iOS SDK for App Analytics and Engagement.' s.homepage = 'https://github.com/CleverTap/clevertap-ios-sdk' diff --git a/lib/clevertap_plugin.dart b/lib/clevertap_plugin.dart index ff069af..62e349a 100644 --- a/lib/clevertap_plugin.dart +++ b/lib/clevertap_plugin.dart @@ -60,7 +60,7 @@ class CleverTapPlugin { static const libName = 'Flutter'; static const libVersion = - 20302; // If the current version is X.X.X then pass as X0X0X + 20400; // If the current version is X.X.X then pass as X0X0X CleverTapPlugin._internal() { /// Set the CleverTap Flutter library name and the current version for version tracking diff --git a/pubspec.yaml b/pubspec.yaml index 290f338..9f1e343 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: clevertap_plugin description: The CleverTap Flutter SDK for Mobile Customer Engagement,Analytics and Retention solutions. -version: 2.3.2 +version: 2.4.0 homepage: https://github.com/CleverTap/clevertap-flutter environment: