Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 10 additions & 25 deletions example/flutter_example/ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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() {
Expand Down
4 changes: 2 additions & 2 deletions example/flutter_example/lib/view_model/view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

Expand Down
5 changes: 5 additions & 0 deletions mindbox/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
8 changes: 4 additions & 4 deletions mindbox/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions mindbox_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>()
private val tokenSubscriptions = mutableListOf<String>()
private lateinit var channel: MethodChannel

inner class InAppCallbackImpl : InAppCallback {
Expand Down Expand Up @@ -83,27 +85,76 @@ class MindboxAndroidPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Ne
}
}
"getDeviceUUID" -> {
if (deviceUuidSubscription != null) {
Mindbox.disposeDeviceUuidSubscription(deviceUuidSubscription!!)
val subscriptionRef = AtomicReference<String?>(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<String?>(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<String?>(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" -> {
Expand Down
4 changes: 2 additions & 2 deletions mindbox_android/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions mindbox_ios/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
4 changes: 2 additions & 2 deletions mindbox_ios/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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:
Expand Down
5 changes: 5 additions & 0 deletions mindbox_platform_interface/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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');
}
});
}
Expand Down
2 changes: 1 addition & 1 deletion mindbox_platform_interface/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -405,6 +406,72 @@ void main() {
expect(() => completer.future, throwsA(isA<MindboxInternalError>()));
},
);

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<String>().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<String>();
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 {
Expand Down
Loading