Skip to content

Commit 0e659d6

Browse files
authored
Merge pull request #180 from mindbox-cloud/feature/WMSDK-595
WMSDK-595: Add queue for getDeviceUUID callbacks
2 parents 32dd8c8 + fc3dcad commit 0e659d6

3 files changed

Lines changed: 141 additions & 16 deletions

File tree

mindbox_android/android/src/main/kotlin/cloud/mindbox/mindbox_android/MindboxAndroidPlugin.kt

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ import io.flutter.plugin.common.MethodChannel
1818
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
1919
import io.flutter.plugin.common.MethodChannel.Result
2020
import io.flutter.plugin.common.PluginRegistry.NewIntentListener
21+
import java.util.concurrent.atomic.AtomicBoolean
22+
import java.util.concurrent.atomic.AtomicReference
2123

2224
/** MindboxAndroidPlugin */
2325
class MindboxAndroidPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, NewIntentListener {
2426
private lateinit var context: Activity
2527
private var binding: ActivityPluginBinding? = null
26-
private var deviceUuidSubscription: String? = null
27-
private var tokenSubscription: String? = null
28+
private val deviceUuidSubscriptions = mutableListOf<String>()
29+
private val tokenSubscriptions = mutableListOf<String>()
2830
private lateinit var channel: MethodChannel
2931

3032
inner class InAppCallbackImpl : InAppCallback {
@@ -83,27 +85,76 @@ class MindboxAndroidPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Ne
8385
}
8486
}
8587
"getDeviceUUID" -> {
86-
if (deviceUuidSubscription != null) {
87-
Mindbox.disposeDeviceUuidSubscription(deviceUuidSubscription!!)
88+
val subscriptionRef = AtomicReference<String?>(null)
89+
val isResultSent = AtomicBoolean(false)
90+
91+
val subscriptionId = Mindbox.subscribeDeviceUuid { uuid ->
92+
if (isResultSent.compareAndSet(false, true)) {
93+
result.success(uuid)
94+
95+
val id = subscriptionRef.get()
96+
if (id != null) {
97+
Mindbox.disposeDeviceUuidSubscription(id)
98+
deviceUuidSubscriptions.remove(id)
99+
}
100+
}
88101
}
89-
deviceUuidSubscription = Mindbox.subscribeDeviceUuid { uuid ->
90-
result.success(uuid)
102+
103+
subscriptionRef.set(subscriptionId)
104+
deviceUuidSubscriptions.add(subscriptionId)
105+
106+
// If callback was synchronous, unsubscribe immediately
107+
if (isResultSent.get()) {
108+
Mindbox.disposeDeviceUuidSubscription(subscriptionId)
109+
deviceUuidSubscriptions.remove(subscriptionId)
91110
}
92111
}
93112
"getToken" -> {
94-
if (tokenSubscription != null) {
95-
Mindbox.disposePushTokenSubscription(tokenSubscription!!)
113+
val subscriptionRef = AtomicReference<String?>(null)
114+
val isResultSent = AtomicBoolean(false)
115+
116+
val subscriptionId = Mindbox.subscribePushToken { token ->
117+
if (isResultSent.compareAndSet(false, true)) {
118+
result.success(token)
119+
120+
val id = subscriptionRef.get()
121+
if (id != null) {
122+
Mindbox.disposePushTokenSubscription(id)
123+
tokenSubscriptions.remove(id)
124+
}
125+
}
96126
}
97-
tokenSubscription = Mindbox.subscribePushToken { token ->
98-
result.success(token)
127+
128+
subscriptionRef.set(subscriptionId)
129+
tokenSubscriptions.add(subscriptionId)
130+
131+
if (isResultSent.get()) {
132+
Mindbox.disposePushTokenSubscription(subscriptionId)
133+
tokenSubscriptions.remove(subscriptionId)
99134
}
100135
}
101136
"getTokens" -> {
102-
if (tokenSubscription != null) {
103-
Mindbox.disposePushTokenSubscription(tokenSubscription!!)
137+
val subscriptionRef = AtomicReference<String?>(null)
138+
val isResultSent = AtomicBoolean(false)
139+
140+
val subscriptionId = Mindbox.subscribePushTokens { token ->
141+
if (isResultSent.compareAndSet(false, true)) {
142+
result.success(token)
143+
144+
val id = subscriptionRef.get()
145+
if (id != null) {
146+
Mindbox.disposePushTokenSubscription(id)
147+
tokenSubscriptions.remove(id)
148+
}
149+
}
104150
}
105-
tokenSubscription = Mindbox.subscribePushTokens { token ->
106-
result.success(token)
151+
152+
subscriptionRef.set(subscriptionId)
153+
tokenSubscriptions.add(subscriptionId)
154+
155+
if (isResultSent.get()) {
156+
Mindbox.disposePushTokenSubscription(subscriptionId)
157+
tokenSubscriptions.remove(subscriptionId)
107158
}
108159
}
109160
"executeAsyncOperation" -> {

mindbox_platform_interface/lib/src/types/mindbox_method_handler.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,13 @@ class MindboxMethodHandler {
8484
final pendingCallbackMethodsCopy =
8585
List<_PendingCallbackMethod>.from(_pendingCallbackMethods);
8686
for (final callbackMethod in pendingCallbackMethodsCopy) {
87-
callbackMethod.callback(
88-
await channel.invokeMethod(callbackMethod.methodName) ?? 'null');
87+
channel
88+
.invokeMethod(callbackMethod.methodName)
89+
.then((result) {
90+
callbackMethod.callback(result ?? 'null');
91+
}).catchError((e) {
92+
_logError('Error processing pending method ${callbackMethod.methodName}: $e');
93+
});
8994
}
9095
final pendingOperationsCopy =
9196
List<_PendingOperations>.from(_pendingOperations);
@@ -99,6 +104,8 @@ class MindboxMethodHandler {
99104
if (operation.errorCallback != null) {
100105
final mindboxError = _convertPlatformExceptionToMindboxError(e);
101106
operation.errorCallback!(mindboxError);
107+
} else {
108+
_logError('Error processing pending operation ${operation.methodName}: $e');
102109
}
103110
});
104111
}

mindbox_platform_interface/test/src/types/mindbox_method_handler_test.dart

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'dart:async';
22

3+
import 'package:flutter/services.dart';
34
import 'package:flutter_test/flutter_test.dart';
45
import 'package:mindbox_platform_interface/mindbox_platform_interface.dart';
56

@@ -405,6 +406,72 @@ void main() {
405406
expect(() => completer.future, throwsA(isA<MindboxInternalError>()));
406407
},
407408
);
409+
410+
test(
411+
'Verify that init completes even if pending getDeviceUUID hangs, allowing retries',
412+
() async {
413+
int getDeviceUUIDCallCount = 0;
414+
415+
// Mock handler that hangs on first getDeviceUUID
416+
Future slowMockMethodCallHandler(MethodCall methodCall) async {
417+
switch (methodCall.method) {
418+
case 'init':
419+
return Future.value(true);
420+
case 'getDeviceUUID':
421+
getDeviceUUIDCallCount++;
422+
if (getDeviceUUIDCallCount == 1) {
423+
// First call hangs (pending one)
424+
return Completer<String>().future;
425+
} else {
426+
// Subsequent calls succeed
427+
return Future.value('retry-uuid');
428+
}
429+
case 'writeNativeLog':
430+
return Future.value(null);
431+
default:
432+
return 'dummy-response';
433+
}
434+
}
435+
436+
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
437+
.setMockMethodCallHandler(channel, slowMockMethodCallHandler);
438+
439+
// 1. Call getDeviceUUID before init. It goes to pending.
440+
bool callback1Called = false;
441+
handler.getDeviceUUID(callback: (uuid) {
442+
callback1Called = true;
443+
});
444+
445+
// 2. Call init.
446+
final validConfig = Configuration(
447+
domain: 'domain',
448+
endpointIos: 'endpointIos',
449+
endpointAndroid: 'endpointAndroid',
450+
subscribeCustomerIfCreated: true,
451+
);
452+
453+
// This should now complete even though the first getDeviceUUID is hanging
454+
// Adding timeout to fail faster if regression occurs (hangs indefinitely)
455+
await handler
456+
.init(configuration: validConfig)
457+
.timeout(const Duration(seconds: 5));
458+
459+
expect(getDeviceUUIDCallCount, equals(1),
460+
reason: 'First call should have been triggered');
461+
expect(callback1Called, isFalse,
462+
reason: 'First callback is still hanging');
463+
464+
// 3. Call getDeviceUUID again (retry).
465+
// This should succeed because init is complete.
466+
final completer = Completer<String>();
467+
handler.getDeviceUUID(callback: (uuid) => completer.complete(uuid));
468+
469+
final result = await completer.future.timeout(const Duration(seconds: 1));
470+
471+
expect(result, equals('retry-uuid'));
472+
expect(getDeviceUUIDCallCount, equals(2));
473+
},
474+
);
408475
}
409476

410477
class StubMindboxMethodHandler {

0 commit comments

Comments
 (0)