diff --git a/CHANGELOG.md b/CHANGELOG.md index d517194..81f68f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +### Version 5.6.0 (31st March 2026) +#### Added +- Added support for direct deep link callbacks. You can now receive direct deep links by assigning the `directDeeplinkCallback` member of your `AdjustConfig` instance. +- Added support for remote trigger callbacks. You can now receive remote trigger updates by assigning the `remoteTriggerCallback` member of your `AdjustConfig` +instance. + +#### Native SDKs +- **iOS:** [v5.6.0](https://github.com/adjust/ios_sdk/tree/v5.6.0) +- **Android:** [v5.6.0](https://github.com/adjust/android_sdk/tree/v5.6.0) + +--- + ### Version 5.5.1 (9th March 2026) #### Fixed - Fixed an Android build failure (`Cannot run Project.afterEvaluate(Action) when the project is already evaluated`) by handling Gradle evaluation order when `:adjust_sdk` is evaluated before the host `:app` project. diff --git a/VERSION b/VERSION index 7acd1cb..1bc788d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.5.1 +5.6.0 diff --git a/android/build.gradle b/android/build.gradle index 21c3f35..c14d72a 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -28,5 +28,5 @@ android { } dependencies { - implementation 'com.adjust.sdk:adjust-android:5.5.1' + implementation 'com.adjust.sdk:adjust-android:5.6.0' } diff --git a/android/src/main/java/com/adjust/sdk/flutter/AdjustSdk.java b/android/src/main/java/com/adjust/sdk/flutter/AdjustSdk.java index f146a44..940c614 100644 --- a/android/src/main/java/com/adjust/sdk/flutter/AdjustSdk.java +++ b/android/src/main/java/com/adjust/sdk/flutter/AdjustSdk.java @@ -9,6 +9,7 @@ package com.adjust.sdk.flutter; import android.content.Context; +import android.content.Intent; import android.net.Uri; import android.util.Log; @@ -23,6 +24,7 @@ import com.adjust.sdk.AdjustSessionSuccess; import com.adjust.sdk.AdjustPlayStoreSubscription; import com.adjust.sdk.AdjustPurchaseVerificationResult; +import com.adjust.sdk.AdjustRemoteTrigger; import com.adjust.sdk.AdjustStoreInfo; import com.adjust.sdk.AdjustThirdPartySharing; import com.adjust.sdk.AdjustTestOptions; @@ -34,6 +36,7 @@ import com.adjust.sdk.OnEventTrackingSucceededListener; import com.adjust.sdk.OnSessionTrackingFailedListener; import com.adjust.sdk.OnSessionTrackingSucceededListener; +import com.adjust.sdk.OnRemoteTriggerListener; import com.adjust.sdk.OnPurchaseVerificationFinishedListener; import com.adjust.sdk.OnLastDeeplinkReadListener; import com.adjust.sdk.OnDeeplinkResolvedListener; @@ -55,16 +58,23 @@ import java.util.Map; import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; 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.MethodCall; +import io.flutter.plugin.common.PluginRegistry.NewIntentListener; -public class AdjustSdk implements FlutterPlugin, MethodCallHandler { +public class AdjustSdk implements FlutterPlugin, MethodCallHandler, ActivityAware, NewIntentListener { private static String TAG = "AdjustBridge"; + private static String DIRECT_DEEPLINK_CALLBACK_NAME = "adj-direct-deeplink"; private static boolean isDeferredDeeplinkOpeningEnabled = true; private MethodChannel channel; private Context applicationContext; + private ActivityPluginBinding activityPluginBinding; + private boolean isSdkInitialized = false; + private final ArrayList> cachedDirectDeeplinks = new ArrayList<>(); // FlutterPlugin @Override @@ -76,6 +86,9 @@ public void onAttachedToEngine(FlutterPluginBinding binding) { @Override public void onDetachedFromEngine(FlutterPluginBinding binding) { + detachFromActivity(); + isSdkInitialized = false; + cachedDirectDeeplinks.clear(); applicationContext = null; if (channel != null) { channel.setMethodCallHandler(null); @@ -83,6 +96,85 @@ public void onDetachedFromEngine(FlutterPluginBinding binding) { channel = null; } + // ActivityAware + @Override + public void onAttachedToActivity(ActivityPluginBinding binding) { + activityPluginBinding = binding; + activityPluginBinding.addOnNewIntentListener(this); + processDeeplinkFromIntent(activityPluginBinding.getActivity().getIntent()); + } + + @Override + public void onDetachedFromActivityForConfigChanges() { + detachFromActivity(); + } + + @Override + public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { + onAttachedToActivity(binding); + } + + @Override + public void onDetachedFromActivity() { + detachFromActivity(); + } + + @Override + public boolean onNewIntent(Intent intent) { + processDeeplinkFromIntent(intent); + return false; + } + + private void detachFromActivity() { + if (activityPluginBinding != null) { + activityPluginBinding.removeOnNewIntentListener(this); + activityPluginBinding = null; + } + } + + private void processDeeplinkFromIntent(final Intent intent) { + if (intent == null) { + return; + } + + Uri deeplinkUri = intent.getData(); + if (deeplinkUri == null) { + return; + } + + dispatchOrCacheDirectDeeplink(deeplinkUri, null); + } + + private void dispatchOrCacheDirectDeeplink(final Uri deeplinkUri, final Uri referrerUri) { + if (deeplinkUri == null) { + return; + } + + HashMap uriParamsMap = new HashMap(); + uriParamsMap.put("deeplink", deeplinkUri.toString()); + if (referrerUri != null) { + uriParamsMap.put("referrer", referrerUri.toString()); + } + + if (!isSdkInitialized || channel == null) { + cachedDirectDeeplinks.add(uriParamsMap); + return; + } + + channel.invokeMethod(DIRECT_DEEPLINK_CALLBACK_NAME, uriParamsMap); + } + + private void flushCachedDirectDeeplinks() { + if (!isSdkInitialized || channel == null || cachedDirectDeeplinks.isEmpty()) { + return; + } + + for (HashMap deeplinkMap : cachedDirectDeeplinks) { + channel.invokeMethod(DIRECT_DEEPLINK_CALLBACK_NAME, deeplinkMap); + } + cachedDirectDeeplinks.clear(); + } + @Override public void onMethodCall(MethodCall call, final Result result) { switch (call.method) { @@ -611,8 +703,25 @@ public boolean launchReceivedDeeplink(Uri deeplink) { } } + // remote trigger callback + if (configMap.containsKey("remoteTriggerCallback")) { + final String dartMethodName = (String) configMap.get("remoteTriggerCallback"); + if (dartMethodName != null) { + adjustConfig.setOnRemoteTriggerListener(new OnRemoteTriggerListener() { + @Override + public void onRemoteTrigger(AdjustRemoteTrigger remoteTrigger) { + if (channel != null) { + channel.invokeMethod(dartMethodName, getRemoteTriggerMap(remoteTrigger)); + } + } + }); + } + } + // initialize SDK Adjust.initSdk(adjustConfig); + isSdkInitialized = true; + flushCachedDirectDeeplinks(); result.success(null); } @@ -1500,4 +1609,69 @@ private void setTestOptions(final MethodCall call, final Result result) { Adjust.setTestOptions(testOptions); } + + private HashMap getRemoteTriggerMap(AdjustRemoteTrigger remoteTrigger) { + HashMap remoteTriggerMap = new HashMap(); + if (remoteTrigger == null) { + remoteTriggerMap.put("label", ""); + remoteTriggerMap.put("payload", new HashMap()); + return remoteTriggerMap; + } + + remoteTriggerMap.put("label", remoteTrigger.getLabel()); + remoteTriggerMap.put("payload", jsonObjectToMap(remoteTrigger.getPayload())); + return remoteTriggerMap; + } + + private HashMap jsonObjectToMap(JSONObject jsonObject) { + HashMap map = new HashMap(); + if (jsonObject == null) { + return map; + } + + JSONArray names = jsonObject.names(); + if (names == null) { + return map; + } + + for (int i = 0; i < names.length(); ++i) { + String key = names.optString(i, null); + if (key == null) { + continue; + } + + map.put(key, jsonValueToObject(jsonObject.opt(key))); + } + + return map; + } + + private ArrayList jsonArrayToList(JSONArray jsonArray) { + ArrayList list = new ArrayList(); + if (jsonArray == null) { + return list; + } + + for (int i = 0; i < jsonArray.length(); ++i) { + list.add(jsonValueToObject(jsonArray.opt(i))); + } + + return list; + } + + private Object jsonValueToObject(Object value) { + if (value == null || value == JSONObject.NULL) { + return null; + } + + if (value instanceof JSONObject) { + return jsonObjectToMap((JSONObject) value); + } + + if (value instanceof JSONArray) { + return jsonArrayToList((JSONArray) value); + } + + return value; + } } diff --git a/example/android/app/build.gradle.kts b/example/android/app/build.gradle.kts index 48706ed..c363d6a 100644 --- a/example/android/app/build.gradle.kts +++ b/example/android/app/build.gradle.kts @@ -42,5 +42,5 @@ flutter { } dependencies { - implementation("com.adjust.sdk:adjust-android-google-lvl:5.5.1") + implementation("com.adjust.sdk:adjust-android-google-lvl:5.6.0") } diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 277d762..ea6c31f 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -22,14 +22,23 @@ /> - + + + + + + + + + + + android:scheme="https" + android:host="chess.go.link"/> - CFBundleURLTypes - - - CFBundleURLName - adjust_deeplink - CFBundleURLSchemes - - adjust - - - diff --git a/example/ios/Runner/Runner.entitlements b/example/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..3a2d227 --- /dev/null +++ b/example/ios/Runner/Runner.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.associated-domains + + applinks:chess.go.link + + + diff --git a/example/lib/main.dart b/example/lib/main.dart index 9c9385e..5f12fdc 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,13 +1,11 @@ import 'package:adjust_sdk/adjust.dart'; import 'package:adjust_sdk/adjust_attribution.dart'; import 'package:adjust_sdk/adjust_config.dart'; -import 'package:adjust_sdk/adjust_deeplink.dart'; import 'package:adjust_sdk/adjust_event.dart'; import 'package:adjust_sdk/adjust_event_failure.dart'; import 'package:adjust_sdk/adjust_event_success.dart'; import 'package:adjust_sdk/adjust_session_failure.dart'; import 'package:adjust_sdk/adjust_session_success.dart'; -import 'package:app_links/app_links.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -44,9 +42,6 @@ class HomePage extends StatefulWidget { } class _HomePageState extends State with WidgetsBindingObserver { - - late final AppLinks _appLinks; - // event tokens for demonstration static const String _eventTokenSimple = 'g3mfiw'; static const String _eventTokenRevenue = 'a4fd35'; @@ -70,12 +65,6 @@ class _HomePageState extends State with WidgetsBindingObserver { void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); - - - _appLinks = AppLinks(); - _listenForAppLinks(); - - _initializeAdjustSdk(); _updateToggleButtonText(); } @@ -86,82 +75,6 @@ class _HomePageState extends State with WidgetsBindingObserver { super.dispose(); } - // listening to app links - Future _listenForAppLinks() async { - try { - // 1) initial link when app is started via deep link - final initialUri = await _appLinks.getInitialLink(); - if (initialUri != null) { - print('[DeepLink]: Initial link: $initialUri'); - await _processAndRouteLink(initialUri.toString()); - } - - // 2) stream for links while app is running - _appLinks.uriLinkStream.listen((Uri uri) { - print('[DeepLink]: Incoming link: $uri'); - _processAndRouteLink(uri.toString()); - }); - } catch (e, s) { - print('[DeepLink]: Error listening for app links: $e'); - print(s); - } - } - - - Future _processAndRouteLink(String url) async { - print('[DeepLink]: Processing URL via Adjust: $url'); - - try { - final deeplink = AdjustDeeplink(url); - - final resolved = await Adjust.processAndResolveDeeplink(deeplink); - - print('[DeepLink]: Resolved deeplink: $resolved'); - - final targetUrl = resolved ?? url; - - if (!mounted) return; - _handleDeepLinkUrl(targetUrl); - } catch (e, s) { - print('[DeepLink]: Error during Adjust deeplink processing: $e'); - print(s); - } - } - - void _handleDeepLinkUrl(String url) { - print('[DeepLink]: Handling final deeplink URL: $url'); - - Uri uri; - try { - uri = Uri.parse(url); - } catch (e) { - print('[DeepLink]: Invalid deeplink URL: $url, error: $e'); - return; - } - - // Example: myapp://product/123 - final segments = uri.pathSegments; - - if (segments.isNotEmpty && segments.first == 'product') { - final productId = segments.length > 1 ? segments[1] : null; - print('[DeepLink]: Navigate to PRODUCT, id: $productId'); - - _showDialog( - 'Deep link', - 'Navigate to PRODUCT screen\n\nURL: $url\nProduct ID: $productId', - ); - return; - } - - // Fallback / unknown deeplink - _showDialog( - 'Deep link', - 'Received deeplink:\n$url', - ); - } - - - /// initialize the Adjust SDK with configuration Future _initializeAdjustSdk() async { setState(() => _isLoading = true); @@ -183,6 +96,7 @@ class _HomePageState extends State with WidgetsBindingObserver { // configure deeplink callback config.deferredDeeplinkCallback = _handleDeferredDeeplink; + config.directDeeplinkCallback = _handleDirectDeeplink; // configure SKAN callback config.skanUpdatedCallback = _handleSkanUpdate; @@ -303,17 +217,37 @@ class _HomePageState extends State with WidgetsBindingObserver { if (jsonResponse != null) print('[AdjustExample]: JSON response: $jsonResponse'); } - /// handle deferred deeplinks from Adjust + /// handle deferred deeplinks Future _handleDeferredDeeplink(String? uri) async { print('[AdjustExample]: Received deferred deeplink: $uri'); - if (uri == null) { + if (!mounted) { return; } - await _processAndRouteLink(uri); + _showDialog( + 'Deferred deeplink', + uri == null + ? 'Deferred deep link:\n\n(null)' + : 'Deferred deep link:\n\n$uri', + ); } + /// handle direct deeplinks + void _handleDirectDeeplink(String? uri) { + print('[AdjustExample]: Received direct deeplink: $uri'); + + if (!mounted) { + return; + } + + _showDialog( + 'Direct deeplink', + uri == null + ? 'Direct deep link:\n\n(null)' + : 'Direct deep link:\n\n$uri', + ); + } /// handle SKAN updates void _handleSkanUpdate(Map skanData) { diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 0b672f5..12d06d7 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -9,7 +9,6 @@ environment: dependencies: flutter: sdk: flutter - app_links: ^6.4.1 adjust_sdk: path: ../ diff --git a/ext/android/sdk b/ext/android/sdk index 82b59b8..5a1fba9 160000 --- a/ext/android/sdk +++ b/ext/android/sdk @@ -1 +1 @@ -Subproject commit 82b59b8c6168329ebb5cb84b4b183f3986f97352 +Subproject commit 5a1fba9fdd20f2919e88410bd72bd8c0c55fc578 diff --git a/ext/ios/sdk b/ext/ios/sdk index 20c877e..4dbba3a 160000 --- a/ext/ios/sdk +++ b/ext/ios/sdk @@ -1 +1 @@ -Subproject commit 20c877e47176535c428e1fc96f1dabc3a2bd1e0d +Subproject commit 4dbba3ac0a247a1072363b11c1f2534e873bc96b diff --git a/ios/Classes/AdjustSdk.h b/ios/Classes/AdjustSdk.h index bf97d01..a31fc73 100644 --- a/ios/Classes/AdjustSdk.h +++ b/ios/Classes/AdjustSdk.h @@ -7,6 +7,14 @@ // #import +#if __has_include() +#import +#define ADJUST_FLUTTER_HAS_SCENE_LIFECYCLE 1 +#endif -@interface AdjustSdk : NSObject +@interface AdjustSdk : NSObject @end diff --git a/ios/Classes/AdjustSdk.m b/ios/Classes/AdjustSdk.m index f51a0b5..1745624 100644 --- a/ios/Classes/AdjustSdk.m +++ b/ios/Classes/AdjustSdk.m @@ -11,10 +11,18 @@ #import static NSString * const CHANNEL_API_NAME = @"com.adjust.sdk/api"; +static NSString * const DIRECT_DEEPLINK_CALLBACK_NAME = @"adj-direct-deeplink"; @interface AdjustSdk () @property (nonatomic, retain) FlutterMethodChannel *channel; +@property (nonatomic, assign) BOOL isSdkInitialized; +@property (nonatomic, strong) NSMutableArray *cachedDirectDeeplinks; + +- (void)processDeeplinkWithUrl:(NSURL *)deeplinkUrl referrer:(NSURL *)referrer; +- (void)processCapturedDeeplinkWithUrl:(NSURL *)deeplinkUrl referrer:(NSURL *)referrer; +- (void)dispatchOrCacheDirectDeeplink:(NSDictionary *)deeplinkMap; +- (void)flushCachedDirectDeeplinks; @end @@ -27,13 +35,123 @@ + (void)registerWithRegistrar:(NSObject *)registrar { binaryMessenger:[registrar messenger]]; AdjustSdk *instance = [[AdjustSdk alloc] init]; instance.channel = channel; + instance.isSdkInitialized = NO; + instance.cachedDirectDeeplinks = [NSMutableArray array]; [registrar addMethodCallDelegate:instance channel:channel]; + [registrar addApplicationDelegate:instance]; +#if ADJUST_FLUTTER_HAS_SCENE_LIFECYCLE + id registrarObject = registrar; + if ([registrarObject respondsToSelector:@selector(addSceneDelegate:)]) { + [registrarObject addSceneDelegate:instance]; + } +#endif } - (void)dealloc { [self.channel setMethodCallHandler:nil]; self.channel = nil; + [self.cachedDirectDeeplinks removeAllObjects]; + self.cachedDirectDeeplinks = nil; +} + +#pragma mark - iOS app lifecycle methods + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + NSURL *launchUrl = launchOptions[UIApplicationLaunchOptionsURLKey]; + [self processCapturedDeeplinkWithUrl:launchUrl referrer:nil]; + + NSDictionary *userActivityDictionary = launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey]; + NSUserActivity *userActivity = nil; + for (id value in [userActivityDictionary allValues]) { + if ([value isKindOfClass:[NSUserActivity class]]) { + userActivity = (NSUserActivity *)value; + break; + } + } + if ([self isFieldValid:userActivity] + && [userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { + [self processCapturedDeeplinkWithUrl:userActivity.webpageURL referrer:userActivity.referrerURL]; + } + + return NO; +} + +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + options:(NSDictionary *)options { + [self processCapturedDeeplinkWithUrl:url referrer:nil]; + return NO; +} + +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication + annotation:(id)annotation { + [self processCapturedDeeplinkWithUrl:url referrer:nil]; + return NO; +} + +- (BOOL)application:(UIApplication *)application +continueUserActivity:(NSUserActivity *)userActivity + restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler { + if ([self isFieldValid:userActivity] + && [userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { + [self processCapturedDeeplinkWithUrl:userActivity.webpageURL referrer:userActivity.referrerURL]; + } + return NO; +} + +#if ADJUST_FLUTTER_HAS_SCENE_LIFECYCLE +- (BOOL)scene:(UIScene *)scene +willConnectToSession:(UISceneSession *)session + options:(UISceneConnectionOptions *)connectionOptions API_AVAILABLE(ios(13.0)) { + if (connectionOptions == nil) { + return NO; + } + + for (NSUserActivity *userActivity in connectionOptions.userActivities) { + if (![self isFieldValid:userActivity]) { + continue; + } + if (![userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { + continue; + } + [self processCapturedDeeplinkWithUrl:userActivity.webpageURL referrer:userActivity.referrerURL]; + } + + for (UIOpenURLContext *urlContext in connectionOptions.URLContexts) { + if (![self isFieldValid:urlContext.URL]) { + continue; + } + [self processCapturedDeeplinkWithUrl:urlContext.URL referrer:nil]; + } + + return NO; +} + +- (BOOL)scene:(UIScene *)scene +openURLContexts:(NSSet *)URLContexts API_AVAILABLE(ios(13.0)) { + for (UIOpenURLContext *urlContext in URLContexts) { + if (![self isFieldValid:urlContext.URL]) { + continue; + } + [self processCapturedDeeplinkWithUrl:urlContext.URL referrer:nil]; + } + return NO; +} + +- (BOOL)scene:(UIScene *)scene +continueUserActivity:(NSUserActivity *)userActivity API_AVAILABLE(ios(13.0)) { + if (![self isFieldValid:userActivity]) { + return NO; + } + if (![userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { + return NO; + } + [self processCapturedDeeplinkWithUrl:userActivity.webpageURL referrer:userActivity.referrerURL]; + return NO; } +#endif - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if ([@"initSdk" isEqualToString:call.method]) { @@ -191,6 +309,7 @@ - (void)initSdk:(FlutterMethodCall *)call withResult:(FlutterResult)result { NSString *dartEventSuccessCallback = call.arguments[@"eventSuccessCallback"]; NSString *dartEventFailureCallback = call.arguments[@"eventFailureCallback"]; NSString *dartDeferredDeeplinkCallback = call.arguments[@"deferredDeeplinkCallback"]; + NSString *dartRemoteTriggerCallback = call.arguments[@"remoteTriggerCallback"]; NSString *dartSkanUpdatedCallback = call.arguments[@"skanUpdatedCallback"]; BOOL allowSuppressLogLevel = NO; BOOL launchDeferredDeeplink = [call.arguments[@"launchDeferredDeeplink"] boolValue]; @@ -347,6 +466,7 @@ - (void)initSdk:(FlutterMethodCall *)call withResult:(FlutterResult)result { || dartEventSuccessCallback != nil || dartEventFailureCallback != nil || dartDeferredDeeplinkCallback != nil + || dartRemoteTriggerCallback != nil || dartSkanUpdatedCallback != nil) { [adjustConfig setDelegate: [AdjustSdkDelegate getInstanceWithSwizzleOfAttributionCallback:dartAttributionCallback @@ -355,6 +475,7 @@ - (void)initSdk:(FlutterMethodCall *)call withResult:(FlutterResult)result { eventSuccessCallback:dartEventSuccessCallback eventFailureCallback:dartEventFailureCallback deferredDeeplinkCallback:dartDeferredDeeplinkCallback + remoteTriggerCallback:dartRemoteTriggerCallback skanUpdatedCallback:dartSkanUpdatedCallback shouldLaunchDeferredDeeplink:launchDeferredDeeplink methodChannel:self.channel]]; @@ -362,6 +483,8 @@ - (void)initSdk:(FlutterMethodCall *)call withResult:(FlutterResult)result { // start SDK [Adjust initSdk:adjustConfig]; + self.isSdkInitialized = YES; + [self flushCachedDirectDeeplinks]; result(nil); } @@ -555,12 +678,11 @@ - (void)processDeeplink:(FlutterMethodCall *)call withResult:(FlutterResult)resu if ([self isFieldValid:deeplink]) { NSURL *urlDeeplink = [NSURL URLWithString:deeplink]; - ADJDeeplink *deeplink = [[ADJDeeplink alloc] initWithDeeplink:urlDeeplink]; + NSURL *urlReferrer = nil; if ([self isFieldValid:referrer]) { - NSURL *urlReferrer = [NSURL URLWithString:referrer]; - [deeplink setReferrer:urlReferrer]; + urlReferrer = [NSURL URLWithString:referrer]; } - [Adjust processDeeplink:deeplink]; + [self processDeeplinkWithUrl:urlDeeplink referrer:urlReferrer]; } } @@ -1072,6 +1194,55 @@ - (BOOL)isFieldValid:(NSObject *)field { return YES; } +- (void)processDeeplinkWithUrl:(NSURL *)deeplinkUrl referrer:(NSURL *)referrer { + if (![self isFieldValid:deeplinkUrl]) { + return; + } + + ADJDeeplink *deeplink = [[ADJDeeplink alloc] initWithDeeplink:deeplinkUrl]; + if ([self isFieldValid:referrer]) { + [deeplink setReferrer:referrer]; + } + [Adjust processDeeplink:deeplink]; +} + +- (void)processCapturedDeeplinkWithUrl:(NSURL *)deeplinkUrl referrer:(NSURL *)referrer { + if (![self isFieldValid:deeplinkUrl]) { + return; + } + + NSMutableDictionary *deeplinkMap = [NSMutableDictionary dictionary]; + [deeplinkMap setObject:[deeplinkUrl absoluteString] forKey:@"deeplink"]; + if ([self isFieldValid:referrer]) { + [deeplinkMap setObject:[referrer absoluteString] forKey:@"referrer"]; + } + [self dispatchOrCacheDirectDeeplink:deeplinkMap]; +} + +- (void)dispatchOrCacheDirectDeeplink:(NSDictionary *)deeplinkMap { + if (![self isFieldValid:deeplinkMap]) { + return; + } + + if (!self.isSdkInitialized || self.channel == nil) { + [self.cachedDirectDeeplinks addObject:deeplinkMap]; + return; + } + + [self.channel invokeMethod:DIRECT_DEEPLINK_CALLBACK_NAME arguments:deeplinkMap]; +} + +- (void)flushCachedDirectDeeplinks { + if (!self.isSdkInitialized || self.channel == nil || [self.cachedDirectDeeplinks count] == 0) { + return; + } + + for (NSDictionary *deeplinkMap in self.cachedDirectDeeplinks) { + [self.channel invokeMethod:DIRECT_DEEPLINK_CALLBACK_NAME arguments:deeplinkMap]; + } + [self.cachedDirectDeeplinks removeAllObjects]; +} + - (void)addValueOrEmpty:(NSObject *)value withKey:(NSString *)key toDictionary:(NSMutableDictionary *)dictionary { diff --git a/ios/Classes/AdjustSdkDelegate.h b/ios/Classes/AdjustSdkDelegate.h index 77d79c4..a121cf8 100644 --- a/ios/Classes/AdjustSdkDelegate.h +++ b/ios/Classes/AdjustSdkDelegate.h @@ -20,6 +20,7 @@ eventSuccessCallback:(NSString *)swizzleEventSuccessCallback eventFailureCallback:(NSString *)swizzleEventFailureCallback deferredDeeplinkCallback:(NSString *)swizzleDeferredDeeplinkCallback + remoteTriggerCallback:(NSString *)swizzleRemoteTriggerCallback skanUpdatedCallback:(NSString *)swizzleSkanUpdatedCallback shouldLaunchDeferredDeeplink:(BOOL)shouldLaunchDeferredDeeplink methodChannel:(FlutterMethodChannel *)channel; diff --git a/ios/Classes/AdjustSdkDelegate.m b/ios/Classes/AdjustSdkDelegate.m index f78aaa9..925372d 100644 --- a/ios/Classes/AdjustSdkDelegate.m +++ b/ios/Classes/AdjustSdkDelegate.m @@ -17,6 +17,7 @@ static NSString *dartEventSuccessCallback = nil; static NSString *dartEventFailureCallback = nil; static NSString *dartDeferredDeeplinkCallback = nil; +static NSString *dartRemoteTriggerCallback = nil; static NSString *dartSkanUpdatedCallback = nil; @implementation AdjustSdkDelegate @@ -39,6 +40,7 @@ + (id)getInstanceWithSwizzleOfAttributionCallback:(NSString *)swizzleAttribution eventSuccessCallback:(NSString *)swizzleEventSuccessCallback eventFailureCallback:(NSString *)swizzleEventFailureCallback deferredDeeplinkCallback:(NSString *)swizzleDeferredDeeplinkCallback + remoteTriggerCallback:(NSString *)swizzleRemoteTriggerCallback skanUpdatedCallback:(NSString *)swizzleSkanUpdatedCallback shouldLaunchDeferredDeeplink:(BOOL)shouldLaunchDeferredDeeplink methodChannel:(FlutterMethodChannel *)channel { @@ -76,6 +78,11 @@ + (id)getInstanceWithSwizzleOfAttributionCallback:(NSString *)swizzleAttribution swizzledSelector:@selector(adjustDeferredDeeplinkReceivedWannabe:)]; dartDeferredDeeplinkCallback = swizzleDeferredDeeplinkCallback; } + if (swizzleRemoteTriggerCallback != nil) { + [defaultInstance swizzleCallbackMethod:@selector(adjustRemoteTriggerReceived:) + swizzledSelector:@selector(adjustRemoteTriggerReceivedWannabe:)]; + dartRemoteTriggerCallback = swizzleRemoteTriggerCallback; + } if (swizzleSkanUpdatedCallback != nil) { [defaultInstance swizzleCallbackMethod:@selector(adjustSkanUpdatedWithConversionData:) swizzledSelector:@selector(adjustSkanUpdatedWithConversionDataWannabe:)]; @@ -98,6 +105,7 @@ + (void)teardown { dartEventSuccessCallback = nil; dartEventFailureCallback = nil; dartDeferredDeeplinkCallback = nil; + dartRemoteTriggerCallback = nil; dartSkanUpdatedCallback = nil; } @@ -286,6 +294,26 @@ - (void)adjustSkanUpdatedWithConversionDataWannabe:(NSDictionary.from(call.arguments)); + } + break; + default: + throw new UnsupportedError( + '[AdjustFlutter]: Received unknown native method: ${call.method}'); + } + } catch (e) { + print(e.toString()); + } + }); + + _isMethodCallHandlerInitialized = true; + } + + static void _onDirectDeeplinkReceived(String? deeplink) { + if (_directDeeplinkCallback != null) { + _directDeeplinkCallback!(deeplink); + } + } + static void trackEvent(AdjustEvent event) { _channel.invokeMethod('trackEvent', event.toMap); } diff --git a/lib/adjust_config.dart b/lib/adjust_config.dart index 3417a5f..640b234 100644 --- a/lib/adjust_config.dart +++ b/lib/adjust_config.dart @@ -11,10 +11,10 @@ import 'dart:convert'; import 'package:adjust_sdk/adjust_attribution.dart'; import 'package:adjust_sdk/adjust_event_failure.dart'; import 'package:adjust_sdk/adjust_event_success.dart'; +import 'package:adjust_sdk/adjust_remote_trigger.dart'; import 'package:adjust_sdk/adjust_session_failure.dart'; import 'package:adjust_sdk/adjust_session_success.dart'; import 'package:adjust_sdk/adjust_store_info.dart'; -import 'package:flutter/services.dart'; enum AdjustLogLevel { verbose, debug, info, warn, error, suppress } @@ -26,17 +26,18 @@ typedef void SessionFailureCallback(AdjustSessionFailure failureData); typedef void EventSuccessCallback(AdjustEventSuccess successData); typedef void EventFailureCallback(AdjustEventFailure failureData); typedef void DeferredDeeplinkCallback(String? deeplink); +typedef void DirectDeeplinkCallback(String? deeplink); +typedef void RemoteTriggerCallback(AdjustRemoteTrigger remoteTrigger); typedef void SkanUpdatedCallback(Map skanUpdateData); class AdjustConfig { - static const MethodChannel _channel = - const MethodChannel('com.adjust.sdk/api'); static const String _attributionCallbackName = 'adj-attribution-changed'; static const String _sessionSuccessCallbackName = 'adj-session-success'; static const String _sessionFailureCallbackName = 'adj-session-failure'; static const String _eventSuccessCallbackName = 'adj-event-success'; static const String _eventFailureCallbackName = 'adj-event-failure'; static const String _deferredDeeplinkCallbackName = 'adj-deferred-deeplink'; + static const String _remoteTriggerCallbackName = 'adj-remote-trigger'; static const String _skanUpdatedCallbackName = 'adj-skan-updated'; final String _appToken; @@ -80,11 +81,11 @@ class AdjustConfig { EventSuccessCallback? eventSuccessCallback; EventFailureCallback? eventFailureCallback; DeferredDeeplinkCallback? deferredDeeplinkCallback; + DirectDeeplinkCallback? directDeeplinkCallback; + RemoteTriggerCallback? remoteTriggerCallback; SkanUpdatedCallback? skanUpdatedCallback; - AdjustConfig(this._appToken, this._environment) { - _initCallbackHandlers(); - } + AdjustConfig(this._appToken, this._environment); void setUrlStrategy(List urlStrategyDomains, bool useSubdomains, bool isDataResidency) { _urlStrategyDomains.addAll(urlStrategyDomains); @@ -92,63 +93,6 @@ class AdjustConfig { _isDataResidency = isDataResidency; } - void _initCallbackHandlers() { - _channel.setMethodCallHandler((MethodCall call) async { - try { - switch (call.method) { - case _attributionCallbackName: - if (attributionCallback != null) { - AdjustAttribution attribution = AdjustAttribution.fromMap(call.arguments); - attributionCallback!(attribution); - } - break; - case _sessionSuccessCallbackName: - if (sessionSuccessCallback != null) { - AdjustSessionSuccess sessionSuccess = AdjustSessionSuccess.fromMap(call.arguments); - sessionSuccessCallback!(sessionSuccess); - } - break; - case _sessionFailureCallbackName: - if (sessionFailureCallback != null) { - AdjustSessionFailure sessionFailure = AdjustSessionFailure.fromMap(call.arguments); - sessionFailureCallback!(sessionFailure); - } - break; - case _eventSuccessCallbackName: - if (eventSuccessCallback != null) { - AdjustEventSuccess eventSuccess = AdjustEventSuccess.fromMap(call.arguments); - eventSuccessCallback!(eventSuccess); - } - break; - case _eventFailureCallbackName: - if (eventFailureCallback != null) { - AdjustEventFailure eventFailure = AdjustEventFailure.fromMap(call.arguments); - eventFailureCallback!(eventFailure); - } - break; - case _deferredDeeplinkCallbackName: - if (deferredDeeplinkCallback != null) { - String? deeplink = call.arguments['deeplink']; - if (deferredDeeplinkCallback != null) { - deferredDeeplinkCallback!(deeplink); - } - } - break; - case _skanUpdatedCallbackName: - if (skanUpdatedCallback != null) { - skanUpdatedCallback!(Map.from(call.arguments)); - } - break; - default: - throw new UnsupportedError( - '[AdjustFlutter]: Received unknown native method: ${call.method}'); - } - } catch (e) { - print(e.toString()); - } - }); - } - Map get toMap { Map configMap = { 'sdkPrefix': sdkPrefix, @@ -261,6 +205,9 @@ class AdjustConfig { if (deferredDeeplinkCallback != null) { configMap['deferredDeeplinkCallback'] = _deferredDeeplinkCallbackName; } + if (remoteTriggerCallback != null) { + configMap['remoteTriggerCallback'] = _remoteTriggerCallbackName; + } if (skanUpdatedCallback != null) { configMap['skanUpdatedCallback'] = _skanUpdatedCallbackName; } diff --git a/lib/adjust_remote_trigger.dart b/lib/adjust_remote_trigger.dart new file mode 100644 index 0000000..3b97b32 --- /dev/null +++ b/lib/adjust_remote_trigger.dart @@ -0,0 +1,66 @@ +// +// adjust_remote_trigger.dart +// Adjust SDK +// +// Created by Ugljesa Erceg (@uerceg) on 30th March 2026. +// Copyright (c) 2026-Present Adjust GmbH. All rights reserved. +// + +class AdjustRemoteTrigger { + final String label; + final Map payload; + + AdjustRemoteTrigger({ + required this.label, + required this.payload, + }); + + factory AdjustRemoteTrigger.fromMap(dynamic map) { + try { + if (map == null || map is! Map) { + throw Exception('Input map is null or has unexpected type.'); + } + + final String? label = map['label']; + final dynamic payload = map['payload']; + if (label == null) { + throw Exception('Missing required remote trigger label.'); + } + + return AdjustRemoteTrigger( + label: label, + payload: _castPayload(payload), + ); + } catch (e) { + throw Exception( + '[AdjustFlutter]: Failed to create AdjustRemoteTrigger object from given map object. Details: ' + + e.toString()); + } + } + + static Map _castPayload(dynamic payload) { + if (payload == null) { + return {}; + } + + if (payload is! Map) { + throw Exception('Remote trigger payload has unexpected type.'); + } + + return payload.map((dynamic key, dynamic value) { + return MapEntry(key.toString(), _castValue(value)); + }); + } + + static dynamic _castValue(dynamic value) { + if (value is Map) { + return _castPayload(value); + } + + if (value is List) { + return value.map(_castValue).toList(); + } + + return value; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index d632c12..33c0cfe 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: adjust_sdk description: This is the Flutter SDK of Adjust™. You can read more about Adjust™ at adjust.com. homepage: https://github.com/adjust/flutter_sdk -version: 5.5.1 +version: 5.6.0 environment: sdk: ">=2.12.0 <4.0.0" diff --git a/test/android/libs/adjust-test-library.jar b/test/android/libs/adjust-test-library.jar index cb217eb..9aee823 100644 Binary files a/test/android/libs/adjust-test-library.jar and b/test/android/libs/adjust-test-library.jar differ diff --git a/test/android/src/main/java/com/adjust/test/lib/TestLibPlugin.java b/test/android/src/main/java/com/adjust/test/lib/TestLibPlugin.java index da32cfb..44b53ed 100644 --- a/test/android/src/main/java/com/adjust/test/lib/TestLibPlugin.java +++ b/test/android/src/main/java/com/adjust/test/lib/TestLibPlugin.java @@ -64,6 +64,9 @@ public void onMethodCall(MethodCall call, Result result) { case "addInfoToSend": addInfoToSend(call, result); break; + case "sendInfoMapToServer": + sendInfoMapToServer(call, result); + break; case "sendInfoToServer": sendInfoToServer(call, result); break; @@ -160,6 +163,31 @@ private void sendInfoToServer(final MethodCall call, final Result result) { result.success(null); } + private void sendInfoMapToServer(final MethodCall call, final Result result) { + if (testLibrary == null) { + result.error("0", "Test Library not initialized. Call 'init' method first.", null); + return; + } + + Map paramsMap = (Map) call.arguments; + String basePath = (String) paramsMap.get("basePath"); + Map info = (Map) paramsMap.get("info"); + HashMap infoMap = new HashMap(); + if (info != null) { + for (Map.Entry entry : info.entrySet()) { + Object key = entry.getKey(); + Object value = entry.getValue(); + if (key instanceof String && value instanceof String) { + infoMap.put((String) key, (String) value); + } + } + } + + testLibrary.setInfoToSend(infoMap); + testLibrary.sendInfoToServer(basePath); + result.success(null); + } + private void addTest(final MethodCall call, final Result result) { if (testLibrary == null) { result.error("0", "Test Library not initialized. Call 'init' method first.", null); diff --git a/test/app/lib/command_executor.dart b/test/app/lib/command_executor.dart index 9f8dbf3..5ca5f7f 100644 --- a/test/app/lib/command_executor.dart +++ b/test/app/lib/command_executor.dart @@ -25,6 +25,7 @@ import 'package:adjust_sdk/adjust_session_success.dart'; import 'package:adjust_sdk/adjust_third_party_sharing.dart'; import 'package:adjust_sdk/adjust_purchase_verification_result.dart'; import 'package:adjust_sdk/adjust_deeplink.dart'; +import 'package:adjust_sdk/adjust_remote_trigger.dart'; import 'package:adjust_sdk/adjust_store_info.dart'; import 'package:test_app/command.dart'; import 'package:test_lib/test_lib.dart'; @@ -452,6 +453,7 @@ class CommandExecutor { adjustConfig.eventSuccessCallback = null; adjustConfig.eventFailureCallback = null; adjustConfig.deferredDeeplinkCallback = null; + adjustConfig.remoteTriggerCallback = null; adjustConfig.skanUpdatedCallback = null; // TODO: Deeplinking in Flutter example. @@ -602,6 +604,19 @@ class CommandExecutor { TestLib.sendInfoToServer(localBasePath); }; } + + if (_command.containsParameter('remoteTriggerCallback')) { + String? localBasePath = _extraPath; + adjustConfig.remoteTriggerCallback = + (AdjustRemoteTrigger remoteTrigger) { + print( + '[CommandExecutor]: Remote Trigger Callback: ${remoteTrigger.label}, payload: ${remoteTrigger.payload}'); + TestLib.sendInfoMapToServer(localBasePath, { + 'label': remoteTrigger.label, + 'payload': jsonEncode(remoteTrigger.payload), + }); + }; + } } void _initSdk() { @@ -984,10 +999,11 @@ class CommandExecutor { Adjust.verifyAppStorePurchase(purchase).then((result) { String? localBasePath = _basePath; - TestLib.addInfoToSend('verification_status', result?.verificationStatus); - TestLib.addInfoToSend('code', result?.code.toString()); - TestLib.addInfoToSend('message', result?.message); - TestLib.sendInfoToServer(localBasePath); + TestLib.sendInfoMapToServer(localBasePath, { + 'verification_status': result?.verificationStatus, + 'code': result?.code.toString(), + 'message': result?.message, + }); }); } else if (Platform.isAndroid) { String productId = _command.getFirstParameterValue('productId')!; @@ -998,10 +1014,11 @@ class CommandExecutor { Adjust.verifyPlayStorePurchase(purchase).then((result) { String? localBasePath = _basePath; - TestLib.addInfoToSend('verification_status', result?.verificationStatus); - TestLib.addInfoToSend('code', result?.code.toString()); - TestLib.addInfoToSend('message', result?.message); - TestLib.sendInfoToServer(localBasePath); + TestLib.sendInfoMapToServer(localBasePath, { + 'verification_status': result?.verificationStatus, + 'code': result?.code.toString(), + 'message': result?.message, + }); }); } } @@ -1019,18 +1036,20 @@ class CommandExecutor { if (Platform.isIOS) { Adjust.verifyAndTrackAppStorePurchase(adjustEvent).then((result) { String? localBasePath = _basePath; - TestLib.addInfoToSend('verification_status', result?.verificationStatus); - TestLib.addInfoToSend('code', result?.code.toString()); - TestLib.addInfoToSend('message', result?.message); - TestLib.sendInfoToServer(localBasePath); + TestLib.sendInfoMapToServer(localBasePath, { + 'verification_status': result?.verificationStatus, + 'code': result?.code.toString(), + 'message': result?.message, + }); }); } else if (Platform.isAndroid) { Adjust.verifyAndTrackPlayStorePurchase(adjustEvent).then((result) { String? localBasePath = _basePath; - TestLib.addInfoToSend('verification_status', result?.verificationStatus); - TestLib.addInfoToSend('code', result?.code.toString()); - TestLib.addInfoToSend('message', result?.message); - TestLib.sendInfoToServer(localBasePath); + TestLib.sendInfoMapToServer(localBasePath, { + 'verification_status': result?.verificationStatus, + 'code': result?.code.toString(), + 'message': result?.message, + }); }); } diff --git a/test/ios/AdjustTestLibrary.framework/AdjustTestLibrary b/test/ios/AdjustTestLibrary.framework/AdjustTestLibrary index 8e30bd0..8ecb638 100644 Binary files a/test/ios/AdjustTestLibrary.framework/AdjustTestLibrary and b/test/ios/AdjustTestLibrary.framework/AdjustTestLibrary differ diff --git a/test/ios/AdjustTestLibrary.framework/Headers/ATLTestLibrary.h b/test/ios/AdjustTestLibrary.framework/Headers/ATLTestLibrary.h index 14f763b..6d01663 100644 --- a/test/ios/AdjustTestLibrary.framework/Headers/ATLTestLibrary.h +++ b/test/ios/AdjustTestLibrary.framework/Headers/ATLTestLibrary.h @@ -42,6 +42,8 @@ - (void)addInfoToSend:(NSString *)key value:(NSString *)value; +- (void)setInfoToSend:(NSDictionary *)info; + - (void)sendInfoToServer:(NSString *)basePath; - (void)signalEndWaitWithReason:(NSString *)reason; diff --git a/test/ios/AdjustTestLibrary.framework/Versions/A/AdjustTestLibrary b/test/ios/AdjustTestLibrary.framework/Versions/A/AdjustTestLibrary index 8e30bd0..8ecb638 100644 Binary files a/test/ios/AdjustTestLibrary.framework/Versions/A/AdjustTestLibrary and b/test/ios/AdjustTestLibrary.framework/Versions/A/AdjustTestLibrary differ diff --git a/test/ios/AdjustTestLibrary.framework/Versions/A/Headers/ATLTestLibrary.h b/test/ios/AdjustTestLibrary.framework/Versions/A/Headers/ATLTestLibrary.h index 14f763b..6d01663 100644 --- a/test/ios/AdjustTestLibrary.framework/Versions/A/Headers/ATLTestLibrary.h +++ b/test/ios/AdjustTestLibrary.framework/Versions/A/Headers/ATLTestLibrary.h @@ -42,6 +42,8 @@ - (void)addInfoToSend:(NSString *)key value:(NSString *)value; +- (void)setInfoToSend:(NSDictionary *)info; + - (void)sendInfoToServer:(NSString *)basePath; - (void)signalEndWaitWithReason:(NSString *)reason; diff --git a/test/ios/AdjustTestLibrary.framework/Versions/Current/AdjustTestLibrary b/test/ios/AdjustTestLibrary.framework/Versions/Current/AdjustTestLibrary index 8e30bd0..8ecb638 100644 Binary files a/test/ios/AdjustTestLibrary.framework/Versions/Current/AdjustTestLibrary and b/test/ios/AdjustTestLibrary.framework/Versions/Current/AdjustTestLibrary differ diff --git a/test/ios/AdjustTestLibrary.framework/Versions/Current/Headers/ATLTestLibrary.h b/test/ios/AdjustTestLibrary.framework/Versions/Current/Headers/ATLTestLibrary.h index 14f763b..6d01663 100644 --- a/test/ios/AdjustTestLibrary.framework/Versions/Current/Headers/ATLTestLibrary.h +++ b/test/ios/AdjustTestLibrary.framework/Versions/Current/Headers/ATLTestLibrary.h @@ -42,6 +42,8 @@ - (void)addInfoToSend:(NSString *)key value:(NSString *)value; +- (void)setInfoToSend:(NSDictionary *)info; + - (void)sendInfoToServer:(NSString *)basePath; - (void)signalEndWaitWithReason:(NSString *)reason; diff --git a/test/ios/Classes/TestLibPlugin.m b/test/ios/Classes/TestLibPlugin.m index 1027a3e..adeb466 100644 --- a/test/ios/Classes/TestLibPlugin.m +++ b/test/ios/Classes/TestLibPlugin.m @@ -40,6 +40,8 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result [self startTestSession:call withResult:result]; } else if ([@"addInfoToSend" isEqualToString:call.method]) { [self addInfoToSend:call withResult:result]; + } else if ([@"sendInfoMapToServer" isEqualToString:call.method]) { + [self sendInfoMapToServer:call withResult:result]; } else if ([@"sendInfoToServer" isEqualToString:call.method]) { [self sendInfoToServer:call withResult:result]; } else if ([@"addTest" isEqualToString:call.method]) { @@ -106,6 +108,18 @@ - (void)sendInfoToServer:(FlutterMethodCall *)call withResult:(FlutterResult)res [self.testLibrary sendInfoToServer:basePath]; } +- (void)sendInfoMapToServer:(FlutterMethodCall *)call withResult:(FlutterResult)result { + if ([self testLibOk:result] == NO) { + return; + } + NSString *basePath = call.arguments[@"basePath"]; + NSDictionary *info = call.arguments[@"info"]; + if ([info isKindOfClass:[NSDictionary class]]) { + [self.testLibrary setInfoToSend:info]; + } + [self.testLibrary sendInfoToServer:basePath]; +} + - (void)addTest:(FlutterMethodCall *)call withResult:(FlutterResult)result { if ([self testLibOk:result] == NO) { return; diff --git a/test/ios/test_lib.podspec b/test/ios/test_lib.podspec index 606776c..c84d055 100644 --- a/test/ios/test_lib.podspec +++ b/test/ios/test_lib.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'test_lib' - s.version = '5.5.1' + s.version = '5.6.0' s.summary = 'Adjust test library for iOS platform' s.description = <<-DESC Adjust test library for iOS platform. diff --git a/test/lib/test_lib.dart b/test/lib/test_lib.dart index beca10e..ee7244f 100644 --- a/test/lib/test_lib.dart +++ b/test/lib/test_lib.dart @@ -62,6 +62,31 @@ class TestLib { _channel.invokeMethod('sendInfoToServer', {'basePath': basePath}); } + static void sendInfoMapToServer( + String? basePath, Map info) { + if (basePath == null) { + print( + '[TestLibrary]: Skip sending info to server with base path set to null.'); + return; + } + + final Map filteredInfo = {}; + info.forEach((String key, String? value) { + if (value == null) { + print( + '[TestLibrary]: Skip adding info to server for key [$key]. Value is null.'); + return; + } + + filteredInfo[key] = value; + }); + + _channel.invokeMethod('sendInfoMapToServer', { + 'basePath': basePath, + 'info': filteredInfo, + }); + } + static void addTest(String? testName) { if (testName == null) { print('[TestLibrary]: Skip adding test with null value for the name.');