From 32dd8c86b7267d69a7e393e2f1f9712944afbcb8 Mon Sep 17 00:00:00 2001 From: Sergei Semko <28645140+justSmK@users.noreply.github.com> Date: Tue, 25 Nov 2025 19:17:00 +0300 Subject: [PATCH 1/3] WMSDK-552: Update Example with new public push permission refresh method (#178) * WMSDK-552: Update Example with new public push permission refresh method * WMSDK-552: Remove native push permission request from Example --- .../ios/Runner/AppDelegate.swift | 35 ++++++------------- .../lib/view_model/view_model.dart | 4 +-- 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/example/flutter_example/ios/Runner/AppDelegate.swift b/example/flutter_example/ios/Runner/AppDelegate.swift index f67d7af..f0b1572 100644 --- a/example/flutter_example/ios/Runner/AppDelegate.swift +++ b/example/flutter_example/ios/Runner/AppDelegate.swift @@ -13,10 +13,7 @@ import UserNotifications didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - UIApplication.shared.registerForRemoteNotifications() - - // Calling the notification request method - registerForRemoteNotifications() + UNUserNotificationCenter.current().delegate = self // tracking sources of referrals to the application via push notifications Mindbox.shared.track(.launch(launchOptions)) @@ -72,27 +69,15 @@ import UserNotifications override func userNotificationCenter( _ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, - withCompletionHandler completionHandler: @escaping () -> Void) { - // Send click to Mindbox - Mindbox.shared.pushClicked(response: response) - - // Sending the fact that the application was opened when switching to push notification - Mindbox.shared.track(.push(response)) - completionHandler() - super.userNotificationCenter(center, didReceive: response, withCompletionHandler: completionHandler) - } - - func registerForRemoteNotifications() { - UNUserNotificationCenter.current().delegate = self - DispatchQueue.main.async { - UNUserNotificationCenter.current().requestAuthorization(options: [ .alert, .sound, .badge]) { granted, error in - print("Permission granted: \(granted)") - if let error = error { - print("NotificationsRequestAuthorization failed with error: \(error.localizedDescription)") - } - Mindbox.shared.notificationsRequestAuthorization(granted: granted) - } - } + withCompletionHandler completionHandler: @escaping () -> Void + ) { + // Send click to Mindbox + Mindbox.shared.pushClicked(response: response) + + // Sending the fact that the application was opened when switching to push notification + Mindbox.shared.track(.push(response)) + completionHandler() + super.userNotificationCenter(center, didReceive: response, withCompletionHandler: completionHandler) } func notifyFlutterNewData() { diff --git a/example/flutter_example/lib/view_model/view_model.dart b/example/flutter_example/lib/view_model/view_model.dart index f613f3e..980db72 100644 --- a/example/flutter_example/lib/view_model/view_model.dart +++ b/example/flutter_example/lib/view_model/view_model.dart @@ -66,8 +66,8 @@ class ViewModel { var status = await Permission.notification.status; if (!status.isGranted) { status = await Permission.notification.request(); - Mindbox.instance - .updateNotificationPermissionStatus(granted: status.isGranted); + print("Permission status: $status"); + Mindbox.instance.refreshNotificationPermissionStatus(); } } From fc3dcadee16452dc1d02bba79bff2eeaf62e8491 Mon Sep 17 00:00:00 2001 From: Egor Kitselyuk Date: Wed, 24 Dec 2025 12:32:22 +0300 Subject: [PATCH 2/3] WMSDK-595: Add queue for getDeviceUUID callbacks --- .../mindbox_android/MindboxAndroidPlugin.kt | 79 +++++++++++++++---- .../lib/src/types/mindbox_method_handler.dart | 11 ++- .../types/mindbox_method_handler_test.dart | 67 ++++++++++++++++ 3 files changed, 141 insertions(+), 16 deletions(-) diff --git a/mindbox_android/android/src/main/kotlin/cloud/mindbox/mindbox_android/MindboxAndroidPlugin.kt b/mindbox_android/android/src/main/kotlin/cloud/mindbox/mindbox_android/MindboxAndroidPlugin.kt index f33d0e9..be7d742 100644 --- a/mindbox_android/android/src/main/kotlin/cloud/mindbox/mindbox_android/MindboxAndroidPlugin.kt +++ b/mindbox_android/android/src/main/kotlin/cloud/mindbox/mindbox_android/MindboxAndroidPlugin.kt @@ -18,13 +18,15 @@ import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result import io.flutter.plugin.common.PluginRegistry.NewIntentListener +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicReference /** MindboxAndroidPlugin */ class MindboxAndroidPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, NewIntentListener { private lateinit var context: Activity private var binding: ActivityPluginBinding? = null - private var deviceUuidSubscription: String? = null - private var tokenSubscription: String? = null + private val deviceUuidSubscriptions = mutableListOf() + private val tokenSubscriptions = mutableListOf() private lateinit var channel: MethodChannel inner class InAppCallbackImpl : InAppCallback { @@ -83,27 +85,76 @@ class MindboxAndroidPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Ne } } "getDeviceUUID" -> { - if (deviceUuidSubscription != null) { - Mindbox.disposeDeviceUuidSubscription(deviceUuidSubscription!!) + val subscriptionRef = AtomicReference(null) + val isResultSent = AtomicBoolean(false) + + val subscriptionId = Mindbox.subscribeDeviceUuid { uuid -> + if (isResultSent.compareAndSet(false, true)) { + result.success(uuid) + + val id = subscriptionRef.get() + if (id != null) { + Mindbox.disposeDeviceUuidSubscription(id) + deviceUuidSubscriptions.remove(id) + } + } } - deviceUuidSubscription = Mindbox.subscribeDeviceUuid { uuid -> - result.success(uuid) + + subscriptionRef.set(subscriptionId) + deviceUuidSubscriptions.add(subscriptionId) + + // If callback was synchronous, unsubscribe immediately + if (isResultSent.get()) { + Mindbox.disposeDeviceUuidSubscription(subscriptionId) + deviceUuidSubscriptions.remove(subscriptionId) } } "getToken" -> { - if (tokenSubscription != null) { - Mindbox.disposePushTokenSubscription(tokenSubscription!!) + val subscriptionRef = AtomicReference(null) + val isResultSent = AtomicBoolean(false) + + val subscriptionId = Mindbox.subscribePushToken { token -> + if (isResultSent.compareAndSet(false, true)) { + result.success(token) + + val id = subscriptionRef.get() + if (id != null) { + Mindbox.disposePushTokenSubscription(id) + tokenSubscriptions.remove(id) + } + } } - tokenSubscription = Mindbox.subscribePushToken { token -> - result.success(token) + + subscriptionRef.set(subscriptionId) + tokenSubscriptions.add(subscriptionId) + + if (isResultSent.get()) { + Mindbox.disposePushTokenSubscription(subscriptionId) + tokenSubscriptions.remove(subscriptionId) } } "getTokens" -> { - if (tokenSubscription != null) { - Mindbox.disposePushTokenSubscription(tokenSubscription!!) + val subscriptionRef = AtomicReference(null) + val isResultSent = AtomicBoolean(false) + + val subscriptionId = Mindbox.subscribePushTokens { token -> + if (isResultSent.compareAndSet(false, true)) { + result.success(token) + + val id = subscriptionRef.get() + if (id != null) { + Mindbox.disposePushTokenSubscription(id) + tokenSubscriptions.remove(id) + } + } } - tokenSubscription = Mindbox.subscribePushTokens { token -> - result.success(token) + + subscriptionRef.set(subscriptionId) + tokenSubscriptions.add(subscriptionId) + + if (isResultSent.get()) { + Mindbox.disposePushTokenSubscription(subscriptionId) + tokenSubscriptions.remove(subscriptionId) } } "executeAsyncOperation" -> { diff --git a/mindbox_platform_interface/lib/src/types/mindbox_method_handler.dart b/mindbox_platform_interface/lib/src/types/mindbox_method_handler.dart index 7bd58f1..78c537c 100644 --- a/mindbox_platform_interface/lib/src/types/mindbox_method_handler.dart +++ b/mindbox_platform_interface/lib/src/types/mindbox_method_handler.dart @@ -84,8 +84,13 @@ class MindboxMethodHandler { final pendingCallbackMethodsCopy = List<_PendingCallbackMethod>.from(_pendingCallbackMethods); for (final callbackMethod in pendingCallbackMethodsCopy) { - callbackMethod.callback( - await channel.invokeMethod(callbackMethod.methodName) ?? 'null'); + channel + .invokeMethod(callbackMethod.methodName) + .then((result) { + callbackMethod.callback(result ?? 'null'); + }).catchError((e) { + _logError('Error processing pending method ${callbackMethod.methodName}: $e'); + }); } final pendingOperationsCopy = List<_PendingOperations>.from(_pendingOperations); @@ -99,6 +104,8 @@ class MindboxMethodHandler { if (operation.errorCallback != null) { final mindboxError = _convertPlatformExceptionToMindboxError(e); operation.errorCallback!(mindboxError); + } else { + _logError('Error processing pending operation ${operation.methodName}: $e'); } }); } diff --git a/mindbox_platform_interface/test/src/types/mindbox_method_handler_test.dart b/mindbox_platform_interface/test/src/types/mindbox_method_handler_test.dart index 7bdb5a1..dd04d93 100644 --- a/mindbox_platform_interface/test/src/types/mindbox_method_handler_test.dart +++ b/mindbox_platform_interface/test/src/types/mindbox_method_handler_test.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mindbox_platform_interface/mindbox_platform_interface.dart'; @@ -405,6 +406,72 @@ void main() { expect(() => completer.future, throwsA(isA())); }, ); + + test( + 'Verify that init completes even if pending getDeviceUUID hangs, allowing retries', + () async { + int getDeviceUUIDCallCount = 0; + + // Mock handler that hangs on first getDeviceUUID + Future slowMockMethodCallHandler(MethodCall methodCall) async { + switch (methodCall.method) { + case 'init': + return Future.value(true); + case 'getDeviceUUID': + getDeviceUUIDCallCount++; + if (getDeviceUUIDCallCount == 1) { + // First call hangs (pending one) + return Completer().future; + } else { + // Subsequent calls succeed + return Future.value('retry-uuid'); + } + case 'writeNativeLog': + return Future.value(null); + default: + return 'dummy-response'; + } + } + + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, slowMockMethodCallHandler); + + // 1. Call getDeviceUUID before init. It goes to pending. + bool callback1Called = false; + handler.getDeviceUUID(callback: (uuid) { + callback1Called = true; + }); + + // 2. Call init. + final validConfig = Configuration( + domain: 'domain', + endpointIos: 'endpointIos', + endpointAndroid: 'endpointAndroid', + subscribeCustomerIfCreated: true, + ); + + // This should now complete even though the first getDeviceUUID is hanging + // Adding timeout to fail faster if regression occurs (hangs indefinitely) + await handler + .init(configuration: validConfig) + .timeout(const Duration(seconds: 5)); + + expect(getDeviceUUIDCallCount, equals(1), + reason: 'First call should have been triggered'); + expect(callback1Called, isFalse, + reason: 'First callback is still hanging'); + + // 3. Call getDeviceUUID again (retry). + // This should succeed because init is complete. + final completer = Completer(); + handler.getDeviceUUID(callback: (uuid) => completer.complete(uuid)); + + final result = await completer.future.timeout(const Duration(seconds: 1)); + + expect(result, equals('retry-uuid')); + expect(getDeviceUUIDCallCount, equals(2)); + }, + ); } class StubMindboxMethodHandler { From ca2827f7d7f5dd5d8595e28389a2c0b90e5495ca Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 24 Dec 2025 11:39:39 +0000 Subject: [PATCH 3/3] Bump SDK versions: Flutter=2.14.4, Android=2.14.4, iOS=2.14.4 --- mindbox/CHANGELOG.md | 5 +++++ mindbox/pubspec.yaml | 8 ++++---- mindbox_android/CHANGELOG.md | 4 ++++ mindbox_android/pubspec.yaml | 4 ++-- mindbox_ios/CHANGELOG.md | 4 ++++ mindbox_ios/pubspec.yaml | 4 ++-- mindbox_platform_interface/CHANGELOG.md | 5 +++++ mindbox_platform_interface/pubspec.yaml | 2 +- 8 files changed, 27 insertions(+), 9 deletions(-) diff --git a/mindbox/CHANGELOG.md b/mindbox/CHANGELOG.md index 22ff90c..340bd07 100644 --- a/mindbox/CHANGELOG.md +++ b/mindbox/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.14.4 + +* Upgrade native Android SDK dependency to v2.14.4. +* Upgrade native iOS SDK dependency to v2.14.4. + ## 2.14.3 * Upgrade native Android SDK dependency to v2.14.4. diff --git a/mindbox/pubspec.yaml b/mindbox/pubspec.yaml index 943bf26..d7b231d 100644 --- a/mindbox/pubspec.yaml +++ b/mindbox/pubspec.yaml @@ -1,6 +1,6 @@ name: mindbox description: Flutter Mindbox SDK. Plugin wrapper over of Mindbox iOS/Android SDK. -version: 2.14.3 +version: 2.14.4 homepage: https://mindbox.cloud/ repository: https://github.com/mindbox-cloud/flutter-sdk/tree/master/mindbox documentation: https://developers.mindbox.ru/docs/flutter-sdk-integration @@ -20,9 +20,9 @@ flutter: dependencies: flutter: sdk: flutter - mindbox_android: ^2.14.3 - mindbox_ios: ^2.14.3 - mindbox_platform_interface: ^2.14.3 + mindbox_android: ^2.14.4 + mindbox_ios: ^2.14.4 + mindbox_platform_interface: ^2.14.4 dev_dependencies: flutter_test: diff --git a/mindbox_android/CHANGELOG.md b/mindbox_android/CHANGELOG.md index 4c726c8..7c6304b 100644 --- a/mindbox_android/CHANGELOG.md +++ b/mindbox_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.14.4 + +* Upgrade native Android SDK dependency to v2.14.4. + ## 2.14.3 * Upgrade native Android SDK dependency to v2.14.4. diff --git a/mindbox_android/pubspec.yaml b/mindbox_android/pubspec.yaml index 5b04400..e25388b 100644 --- a/mindbox_android/pubspec.yaml +++ b/mindbox_android/pubspec.yaml @@ -1,6 +1,6 @@ name: mindbox_android description: The implementation of 'mindbox' plugin for the Android platform. -version: 2.14.3 +version: 2.14.4 homepage: https://mindbox.cloud/ repository: https://github.com/mindbox-cloud/flutter-sdk/tree/master/mindbox_android @@ -19,7 +19,7 @@ flutter: dependencies: flutter: sdk: flutter - mindbox_platform_interface: ^2.14.3 + mindbox_platform_interface: ^2.14.4 dev_dependencies: flutter_test: diff --git a/mindbox_ios/CHANGELOG.md b/mindbox_ios/CHANGELOG.md index 3562190..4e3082d 100644 --- a/mindbox_ios/CHANGELOG.md +++ b/mindbox_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.14.4 + +* Upgrade native iOS SDK dependency to v2.14.4. + ## 2.14.3 * Upgrade native iOS SDK dependency to v2.14.4. diff --git a/mindbox_ios/pubspec.yaml b/mindbox_ios/pubspec.yaml index 3df65ca..fcb3d03 100644 --- a/mindbox_ios/pubspec.yaml +++ b/mindbox_ios/pubspec.yaml @@ -1,6 +1,6 @@ name: mindbox_ios description: The implementation of 'mindbox' plugin for the iOS platform. -version: 2.14.3 +version: 2.14.4 homepage: https://mindbox.cloud/ repository: https://github.com/mindbox-cloud/flutter-sdk/tree/master/mindbox_ios @@ -18,7 +18,7 @@ flutter: dependencies: flutter: sdk: flutter - mindbox_platform_interface: ^2.14.3 + mindbox_platform_interface: ^2.14.4 dev_dependencies: flutter_test: diff --git a/mindbox_platform_interface/CHANGELOG.md b/mindbox_platform_interface/CHANGELOG.md index 954e2ce..daf9beb 100644 --- a/mindbox_platform_interface/CHANGELOG.md +++ b/mindbox_platform_interface/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.14.4 + +* Upgrade native Android SDK dependency to v2.14.4. +* Upgrade native iOS SDK dependency to v2.14.4. + ## 2.14.3 * Upgrade native Android SDK dependency to v2.14.4. diff --git a/mindbox_platform_interface/pubspec.yaml b/mindbox_platform_interface/pubspec.yaml index 90a582e..c60a5a1 100644 --- a/mindbox_platform_interface/pubspec.yaml +++ b/mindbox_platform_interface/pubspec.yaml @@ -1,6 +1,6 @@ name: mindbox_platform_interface description: Mindbox platform interface. -version: 2.14.3 +version: 2.14.4 homepage: https://mindbox.cloud/ repository: https://github.com/mindbox-cloud/flutter-sdk/tree/master/mindbox_platform_interface