From d74054e69220c898241ba3fd329419ce8d8ebf85 Mon Sep 17 00:00:00 2001 From: Dani-Koza-AF Date: Sun, 1 Sep 2024 16:43:24 +0300 Subject: [PATCH 1/6] flutter side impl - New Enum introduced. - New API logAdRevenue. - New AdRevenueData class. - Upgraded Dart SDK versions limits a bit to start from 2.17.0 . --- lib/appsflyer_sdk.dart | 1 + lib/src/appsflyer_ad_revenue_data.dart | 27 +++++++++++++ lib/src/appsflyer_constants.dart | 55 ++++++++++++++++++++++++-- lib/src/appsflyer_sdk.dart | 6 +++ pubspec.yaml | 4 +- 5 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 lib/src/appsflyer_ad_revenue_data.dart diff --git a/lib/appsflyer_sdk.dart b/lib/appsflyer_sdk.dart index 8086305..1d8ab74 100644 --- a/lib/appsflyer_sdk.dart +++ b/lib/appsflyer_sdk.dart @@ -18,3 +18,4 @@ part 'src/udl/deep_link_result.dart'; part 'src/udl/deeplink.dart'; part 'src/appsflyer_consent.dart'; part 'src/appsflyer_request_listener.dart'; +part 'src/appsflyer_ad_revenue_data.dart'; diff --git a/lib/src/appsflyer_ad_revenue_data.dart b/lib/src/appsflyer_ad_revenue_data.dart new file mode 100644 index 0000000..cac981a --- /dev/null +++ b/lib/src/appsflyer_ad_revenue_data.dart @@ -0,0 +1,27 @@ +part of appsflyer_sdk; + +class AdRevenueData { + final String monetizationNetwork; + final MediationNetwork mediationNetwork; + final String currencyIso4217Code; + final double revenue; + final Map? additionalParameters; + + AdRevenueData({ + required this.monetizationNetwork, + required this.mediationNetwork, + required this.currencyIso4217Code, + required this.revenue, + this.additionalParameters + }); + + Map toMap() { + return { + 'monetizationNetwork': monetizationNetwork, + 'mediationNetwork': mediationNetwork, + 'currencyIso4217Code': currencyIso4217Code, + 'revenue': revenue, + 'additionalParameters': additionalParameters + }; + } +} diff --git a/lib/src/appsflyer_constants.dart b/lib/src/appsflyer_constants.dart index 0ee155e..c79ed30 100644 --- a/lib/src/appsflyer_constants.dart +++ b/lib/src/appsflyer_constants.dart @@ -1,9 +1,6 @@ part of appsflyer_sdk; -enum EmailCryptType { - EmailCryptTypeNone, - EmailCryptTypeSHA256 -} +enum EmailCryptType { EmailCryptTypeNone, EmailCryptTypeSHA256 } class AppsflyerConstants { static const String AF_DEV_KEY = "afDevKey"; @@ -30,3 +27,53 @@ class AppsflyerConstants { static const String DISABLE_ADVERTISING_IDENTIFIER = "disableAdvertisingIdentifier"; } + +enum MediationNetwork { + ironSource, + applovinMax, + googleAdMob, + fyber, + appodeal, + admost, + topon, + tradplus, + yandex, + chartboost, + unity, + toponPte, + customMediation, + directMonetizationNetwork; + + String get value { + switch (this) { + case MediationNetwork.ironSource: + return "ironsource"; + case MediationNetwork.applovinMax: + return "applovinmax"; + case MediationNetwork.googleAdMob: + return "googleadmob"; + case MediationNetwork.fyber: + return "fyber"; + case MediationNetwork.appodeal: + return "appodeal"; + case MediationNetwork.admost: + return "Admost"; + case MediationNetwork.topon: + return "Topon"; + case MediationNetwork.tradplus: + return "Tradplus"; + case MediationNetwork.yandex: + return "Yandex"; + case MediationNetwork.chartboost: + return "chartboost"; + case MediationNetwork.unity: + return "Unity"; + case MediationNetwork.toponPte: + return "toponpte"; + case MediationNetwork.customMediation: + return "customMediation"; + case MediationNetwork.directMonetizationNetwork: + return "directMonetizationNetwork"; + } + } +} diff --git a/lib/src/appsflyer_sdk.dart b/lib/src/appsflyer_sdk.dart index 6952286..c4cde1b 100644 --- a/lib/src/appsflyer_sdk.dart +++ b/lib/src/appsflyer_sdk.dart @@ -237,6 +237,12 @@ class AppsflyerSdk { "logEvent", {'eventName': eventName, 'eventValues': eventValues}); } + + /// Log ad revenue API. + void logAdRevenue(AdRevenueData adRevenueData) { + _methodChannel.invokeMethod("logAdRevenue", adRevenueData.toMap()); + } + /// Sets the host name and the host prefix. /// This is only relevant if you need to switch between HTTPS environments. void setHost(String hostPrefix, String hostName) { diff --git a/pubspec.yaml b/pubspec.yaml index fb17829..90a5a68 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,11 +1,11 @@ name: appsflyer_sdk description: A Flutter plugin for AppsFlyer SDK. Supports iOS and Android. -version: 6.14.3 +version: 6.15.1 homepage: https://github.com/AppsFlyerSDK/flutter_appsflyer_sdk environment: - sdk: '>=2.12.0 <4.0.0' + sdk: '>=2.17.0 <4.0.0' flutter: ">=1.10.0" dependencies: From 4a3a0d641c5fb5500d320e0d59f5520f926af89d Mon Sep 17 00:00:00 2001 From: Dani-Koza-AF Date: Mon, 2 Sep 2024 14:48:30 +0300 Subject: [PATCH 2/6] Android side impl - Flutter didn't like the fact that we pass enums, had to change mediation network to String, handled later on native side. - Added an helper method to ensure null safety, hopefully will be embraced by other method in the future. --- android/build.gradle | 2 +- .../appsflyersdk/AppsFlyerConstants.java | 2 +- .../appsflyersdk/AppsflyerSdkPlugin.java | 73 +++++++++++++++++++ lib/src/appsflyer_ad_revenue_data.dart | 2 +- lib/src/appsflyer_constants.dart | 30 ++++---- 5 files changed, 91 insertions(+), 18 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index dfae0b9..9669568 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -31,6 +31,6 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.0.0' - implementation 'com.appsflyer:af-android-sdk:6.14.0' + implementation 'com.appsflyer:af-android-sdk:6.15.1' implementation 'com.android.installreferrer:installreferrer:2.1' } \ No newline at end of file diff --git a/android/src/main/java/com/appsflyer/appsflyersdk/AppsFlyerConstants.java b/android/src/main/java/com/appsflyer/appsflyersdk/AppsFlyerConstants.java index 36a8c25..b6f419c 100644 --- a/android/src/main/java/com/appsflyer/appsflyersdk/AppsFlyerConstants.java +++ b/android/src/main/java/com/appsflyer/appsflyersdk/AppsFlyerConstants.java @@ -1,7 +1,7 @@ package com.appsflyer.appsflyersdk; public class AppsFlyerConstants { - final static String PLUGIN_VERSION = "6.14.3"; + final static String PLUGIN_VERSION = "6.15.1"; final static String AF_APP_INVITE_ONE_LINK = "appInviteOneLink"; final static String AF_HOST_PREFIX = "hostPrefix"; final static String AF_HOST_NAME = "hostName"; diff --git a/android/src/main/java/com/appsflyer/appsflyersdk/AppsflyerSdkPlugin.java b/android/src/main/java/com/appsflyer/appsflyersdk/AppsflyerSdkPlugin.java index 4c45025..9bca7ac 100644 --- a/android/src/main/java/com/appsflyer/appsflyersdk/AppsflyerSdkPlugin.java +++ b/android/src/main/java/com/appsflyer/appsflyersdk/AppsflyerSdkPlugin.java @@ -9,12 +9,14 @@ import android.os.Looper; import android.util.Log; +import com.appsflyer.AFAdRevenueData; import com.appsflyer.AFLogger; import com.appsflyer.AppsFlyerConsent; import com.appsflyer.AppsFlyerConversionListener; import com.appsflyer.AppsFlyerInAppPurchaseValidatorListener; import com.appsflyer.AppsFlyerLib; import com.appsflyer.AppsFlyerProperties; +import com.appsflyer.MediationNetwork; import com.appsflyer.deeplink.DeepLinkListener; import com.appsflyer.deeplink.DeepLinkResult; import com.appsflyer.share.CrossPromotionHelper; @@ -344,6 +346,9 @@ public void onMethodCall(MethodCall call, Result result) { case "addPushNotificationDeepLinkPath": addPushNotificationDeepLinkPath(call, result); break; + case "logAdRevenue": + logAdRevenue(call, result); + break; default: result.notImplemented(); break; @@ -954,6 +959,74 @@ private void logEvent(MethodCall call, MethodChannel.Result result) { result.success(true); } + private void logAdRevenue(MethodCall call, Result result) { + try { + String monetizationNetwork = requireNonNullArgument(call, result, "monetizationNetwork", "NULL_MONETIZATION_NETWORK"); + if (monetizationNetwork == null) return; + + String currencyIso4217Code = requireNonNullArgument(call, result, "currencyIso4217Code", "NULL_CURRENCY_CODE"); + if (currencyIso4217Code == null) return; + + Double revenueValue = requireNonNullArgument(call, result, "revenue", "NULL_REVENUE"); + if (revenueValue == null) return; + double revenue = revenueValue; // Auto-unboxing to primitive double type + + String mediationNetworkString = requireNonNullArgument(call, result, "mediationNetwork", "NULL_MEDIATION_NETWORK"); + if (mediationNetworkString == null) return; + MediationNetwork mediationNetwork = null; + for (MediationNetwork network : MediationNetwork.values()) { + if (network.getValue().equalsIgnoreCase(mediationNetworkString)) { + mediationNetwork = network; + break; + } + } + if (mediationNetwork == null) { + result.error("INVALID_MEDIATION_NETWORK", "Invalid mediation network: " + mediationNetworkString + ". Please use the mediation networks provided by AFMediationNetwork.", null); + Log.e("AppsFlyer", "Invalid mediation network: " + mediationNetworkString); + return; + } + + // No null check for additionalParameters since it's acceptable for it to be null (optional data) + Map additionalParameters = call.argument("additionalParameters"); + + AFAdRevenueData adRevenueData = new AFAdRevenueData( + monetizationNetwork, + mediationNetwork, + currencyIso4217Code, + revenue + ); + + AppsFlyerLib.getInstance().logAdRevenue(adRevenueData, additionalParameters); + + result.success(true); + + } catch (Exception e) { + result.error("UNEXPECTED_ERROR", "[logAdRevenue]: An error occurred retrieving method arguments: " + e.getMessage(), null); + Log.e("AppsFlyer", "Exception occurred: [logAdRevenue]:", e); + } + } + + /** + * Utility method to ensure that an argument with the specified name is not null. + * If the argument is null, this method will log an error, send an error response to the Flutter side, + * and return null. The calling method can then terminate immediately without further processing. + * + * @param call The MethodCall from Flutter, containing all the arguments. + * @param result The Result to send responses back to the calling Dart code in Flutter. + * @param argumentName The name of the argument expected in the MethodCall. + * @param errorCode The error code to use when reporting a null argument error. + * @param The type of the argument being checked for nullity. + * @return The argument value if it is not null; null otherwise. + */ + private T requireNonNullArgument(MethodCall call, Result result, String argumentName, String errorCode) { + T value = call.argument(argumentName); + if (value == null) { + result.error(errorCode, argumentName + " must not be null", null); + Log.e("AppsFlyer", "Exception occurred when trying to: " + call.method + "->" + argumentName + " must not be null"); + } + return value; + } + //RD-65582 private void sendCachedCallbacksToDart() { if (cachedDeepLinkResult != null) { diff --git a/lib/src/appsflyer_ad_revenue_data.dart b/lib/src/appsflyer_ad_revenue_data.dart index cac981a..7482cf7 100644 --- a/lib/src/appsflyer_ad_revenue_data.dart +++ b/lib/src/appsflyer_ad_revenue_data.dart @@ -2,7 +2,7 @@ part of appsflyer_sdk; class AdRevenueData { final String monetizationNetwork; - final MediationNetwork mediationNetwork; + final String mediationNetwork; final String currencyIso4217Code; final double revenue; final Map? additionalParameters; diff --git a/lib/src/appsflyer_constants.dart b/lib/src/appsflyer_constants.dart index c79ed30..6f86f56 100644 --- a/lib/src/appsflyer_constants.dart +++ b/lib/src/appsflyer_constants.dart @@ -28,7 +28,7 @@ class AppsflyerConstants { "disableAdvertisingIdentifier"; } -enum MediationNetwork { +enum AFMediationNetwork { ironSource, applovinMax, googleAdMob, @@ -46,33 +46,33 @@ enum MediationNetwork { String get value { switch (this) { - case MediationNetwork.ironSource: + case AFMediationNetwork.ironSource: return "ironsource"; - case MediationNetwork.applovinMax: + case AFMediationNetwork.applovinMax: return "applovinmax"; - case MediationNetwork.googleAdMob: + case AFMediationNetwork.googleAdMob: return "googleadmob"; - case MediationNetwork.fyber: + case AFMediationNetwork.fyber: return "fyber"; - case MediationNetwork.appodeal: + case AFMediationNetwork.appodeal: return "appodeal"; - case MediationNetwork.admost: + case AFMediationNetwork.admost: return "Admost"; - case MediationNetwork.topon: + case AFMediationNetwork.topon: return "Topon"; - case MediationNetwork.tradplus: + case AFMediationNetwork.tradplus: return "Tradplus"; - case MediationNetwork.yandex: + case AFMediationNetwork.yandex: return "Yandex"; - case MediationNetwork.chartboost: + case AFMediationNetwork.chartboost: return "chartboost"; - case MediationNetwork.unity: + case AFMediationNetwork.unity: return "Unity"; - case MediationNetwork.toponPte: + case AFMediationNetwork.toponPte: return "toponpte"; - case MediationNetwork.customMediation: + case AFMediationNetwork.customMediation: return "customMediation"; - case MediationNetwork.directMonetizationNetwork: + case AFMediationNetwork.directMonetizationNetwork: return "directMonetizationNetwork"; } } From 079ccadb4a616740ced92fd2c20090838d638ce0 Mon Sep 17 00:00:00 2001 From: Dani-Koza-AF Date: Tue, 3 Sep 2024 17:22:48 +0300 Subject: [PATCH 3/6] iOS side impl - Helper func to get the correct enum properly. - requireNonNullArgumentWithCall to make sure we actually get the arguments. - Lots of null safety checks due to testing failures encountered. --- ios/Classes/AppsflyerSdkPlugin.h | 2 +- ios/Classes/AppsflyerSdkPlugin.m | 105 ++++++++++++++++++++++++++++--- ios/appsflyer_sdk.podspec | 4 +- 3 files changed, 98 insertions(+), 13 deletions(-) diff --git a/ios/Classes/AppsflyerSdkPlugin.h b/ios/Classes/AppsflyerSdkPlugin.h index a6055a9..0ae703a 100644 --- a/ios/Classes/AppsflyerSdkPlugin.h +++ b/ios/Classes/AppsflyerSdkPlugin.h @@ -18,7 +18,7 @@ @end // Appsflyer JS objects -#define kAppsFlyerPluginVersion @"6.14.3" +#define kAppsFlyerPluginVersion @"6.15.1" #define afDevKey @"afDevKey" #define afAppId @"afAppId" #define afIsDebug @"isDebug" diff --git a/ios/Classes/AppsflyerSdkPlugin.m b/ios/Classes/AppsflyerSdkPlugin.m index 9d0fec4..4295044 100644 --- a/ios/Classes/AppsflyerSdkPlugin.m +++ b/ios/Classes/AppsflyerSdkPlugin.m @@ -94,7 +94,7 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { }else if([@"setIsUpdate" isEqualToString:call.method]){ // }else if([@"setCustomerUserId" isEqualToString:call.method]){ - [self setCustomerUserId:call result:result]; + [self setCustomerUserId:call result:result]; }else if([@"setCustomerIdAndLogSession" isEqualToString:call.method]){ [self setCustomerUserId:call result:result]; }else if([@"setCurrencyCode" isEqualToString:call.method ]){ @@ -130,7 +130,7 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { }else if([@"setOneLinkCustomDomain" isEqualToString:call.method]){ [self setOneLinkCustomDomain:call result:result]; }else if([@"setPushNotification" isEqualToString:call.method]){ - [self setPushNotification:call result:result]; + [self setPushNotification:call result:result]; }else if([@"sendPushNotificationData" isEqualToString:call.method]){ [self sendPushNotificationData:call result:result]; }else if([@"useReceiptValidationSandbox" isEqualToString:call.method]){ @@ -157,6 +157,8 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { [self enableTCFDataCollection:call result:result]; }else if([@"setConsentData" isEqualToString:call.method]){ [self setConsentData:call result:result]; + }else if([@"logAdRevenue" isEqualToString:call.method]){ + [self logAdRevenue:call result:result]; } else{ result(FlutterMethodNotImplemented); @@ -189,23 +191,106 @@ - (void)startSDK:(FlutterMethodCall*)call result:(FlutterResult)result { - (void)setConsentData:(FlutterMethodCall*)call result:(FlutterResult)result { NSDictionary* consentDict = call.arguments[@"consentData"]; - + BOOL isUserSubjectToGDPR = [consentDict[@"isUserSubjectToGDPR"] boolValue]; BOOL hasConsentForDataUsage = [consentDict[@"hasConsentForDataUsage"] boolValue]; BOOL hasConsentForAdsPersonalization = [consentDict[@"hasConsentForAdsPersonalization"] boolValue]; - + AppsFlyerConsent *consentData; if(isUserSubjectToGDPR){ consentData = [[AppsFlyerConsent alloc] initForGDPRUserWithHasConsentForDataUsage:hasConsentForDataUsage - hasConsentForAdsPersonalization:hasConsentForAdsPersonalization]; + hasConsentForAdsPersonalization:hasConsentForAdsPersonalization]; }else{ consentData = [[AppsFlyerConsent alloc] initNonGDPRUser]; } - + [[AppsFlyerLib shared] setConsentData:consentData]; result(nil); } +- (void)logAdRevenue:(FlutterMethodCall*)call result:(FlutterResult)result { + @try { + NSString *monetizationNetwork = [self requireNonNullArgumentWithCall:call result:result argumentName:@"monetizationNetwork" errorCode:@"NULL_MONETIZATION_NETWORK"]; + if (monetizationNetwork == nil) return; + + NSString *currencyIso4217Code = [self requireNonNullArgumentWithCall:call result:result argumentName:@"currencyIso4217Code" errorCode:@"NULL_CURRENCY_CODE"]; + if (currencyIso4217Code == nil) return; + + NSNumber *revenueValue = [self requireNonNullArgumentWithCall:call result:result argumentName:@"revenue" errorCode:@"NULL_REVENUE"]; + if (revenueValue == nil) return; + + NSString *mediationNetworkString = [self requireNonNullArgumentWithCall:call result:result argumentName:@"mediationNetwork" errorCode:@"NULL_MEDIATION_NETWORK"]; + if (mediationNetworkString == nil) return; + + // Fetching the actual mediationNetwork Enum + AppsFlyerAdRevenueMediationNetworkType mediationNetwork = [self getEnumValueFromString:mediationNetworkString]; + if (mediationNetwork == -1) { //mediation network not found. + result([FlutterError errorWithCode:@"INVALID_MEDIATION_NETWORK" + message:@"The provided mediation network is not supported." + details:nil]); + return; + } + + NSDictionary *additionalParameters = call.arguments[@"additionalParameters"]; + if ([additionalParameters isEqual:[NSNull null]]) { + additionalParameters = nil; // Set to nil to avoid sending NSNull to the SDK which cannot be proseesed. + } + + AFAdRevenueData *adRevenueData = [[AFAdRevenueData alloc] + initWithMonetizationNetwork:monetizationNetwork + mediationNetwork:mediationNetwork + currencyIso4217Code:currencyIso4217Code + eventRevenue:revenueValue]; + + [[AppsFlyerLib shared] logAdRevenue:adRevenueData additionalParameters:additionalParameters]; + + } @catch (NSException *exception) { + result([FlutterError errorWithCode:@"UNEXPECTED_ERROR" + message:[NSString stringWithFormat:@"[logAdRevenue]: An error occurred retrieving method arguments: %@", exception.reason] + details:nil]); + NSLog(@"AppsFlyer, Exception occurred in [logAdRevenue]: %@", exception.reason); + } + +} + +- (AppsFlyerAdRevenueMediationNetworkType)getEnumValueFromString:(NSString *)mediationNetworkString { + NSDictionary *stringToEnumMap = @{ + @"googleadmob": @(AppsFlyerAdRevenueMediationNetworkTypeGoogleAdMob), + @"ironsource": @(AppsFlyerAdRevenueMediationNetworkTypeIronSource), + @"applovinmax": @(AppsFlyerAdRevenueMediationNetworkTypeApplovinMax), + @"fyber": @(AppsFlyerAdRevenueMediationNetworkTypeFyber), + @"appodeal": @(AppsFlyerAdRevenueMediationNetworkTypeAppodeal), + @"Admost": @(AppsFlyerAdRevenueMediationNetworkTypeAdmost), + @"Topon": @(AppsFlyerAdRevenueMediationNetworkTypeTopon), + @"Tradplus": @(AppsFlyerAdRevenueMediationNetworkTypeTradplus), + @"Yandex": @(AppsFlyerAdRevenueMediationNetworkTypeYandex), + @"chartboost": @(AppsFlyerAdRevenueMediationNetworkTypeChartBoost), + @"Unity": @(AppsFlyerAdRevenueMediationNetworkTypeUnity), + @"toponpte": @(AppsFlyerAdRevenueMediationNetworkTypeToponPte), + @"customMediation": @(AppsFlyerAdRevenueMediationNetworkTypeCustom), + @"directMonetizationNetwork": @(AppsFlyerAdRevenueMediationNetworkTypeDirectMonetization) + }; + + NSNumber *enumValueNumber = stringToEnumMap[mediationNetworkString]; + if (enumValueNumber) { + return (AppsFlyerAdRevenueMediationNetworkType)[enumValueNumber integerValue]; + } else { + return -1; + } +} + +- (id)requireNonNullArgumentWithCall:(FlutterMethodCall*)call result:(FlutterResult)result argumentName:(NSString *)argumentName errorCode:(NSString *)errorCode { + id value = call.arguments[argumentName]; + if (value == nil) { + result([FlutterError + errorWithCode:errorCode + message:[NSString stringWithFormat:@"%@ must not be null", argumentName] + details:nil]); + NSLog(@"AppsFlyer, %@ must not be null", argumentName); + } + return value; +} + - (void)enableTCFDataCollection:(FlutterMethodCall*)call result:(FlutterResult)result { BOOL shouldCollect = [call.arguments[@"shouldCollect"] boolValue]; [[AppsFlyerLib shared] enableTCFDataCollection:shouldCollect]; @@ -658,7 +743,7 @@ - (void)initSdkWithCall:(FlutterMethodCall*)call result:(FlutterResult)result{ } [[AppsFlyerLib shared] setPluginInfoWith:AFSDKPluginFlutter pluginVersion:kAppsFlyerPluginVersion additionalParams:nil]; - + [AppsFlyerLib shared].appleAppID = appId; [AppsFlyerLib shared].appsFlyerDevKey = devKey; [AppsFlyerLib shared].isDebug = isDebug; @@ -674,12 +759,12 @@ - (void)initSdkWithCall:(FlutterMethodCall*)call result:(FlutterResult)result{ if (timeToWaitForATTUserAuthorization != 0) { [[AppsFlyerLib shared] waitForATTUserAuthorizationWithTimeoutInterval:timeToWaitForATTUserAuthorization]; } - + if (manualStart == NO){ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil]; [[AppsFlyerLib shared] start]; } - + //post notification for the deep link object that the bridge is set and he can handle deep link [AppsFlyerAttribution shared].isBridgeReady = YES; [[NSNotificationCenter defaultCenter] postNotificationName:AF_BRIDGE_SET object:self]; @@ -691,7 +776,7 @@ - (void)initSdkWithCall:(FlutterMethodCall*)call result:(FlutterResult)result{ -(void)logEventWithCall:(FlutterMethodCall*)call result:(FlutterResult)result{ NSString *eventName = call.arguments[afEventName]; NSDictionary *eventValues = call.arguments[afEventValues]; - + // Explicitily setting the values to be nil if call.arguments[afEventValues] returns . if (eventValues == [NSNull null]) { eventValues = nil; diff --git a/ios/appsflyer_sdk.podspec b/ios/appsflyer_sdk.podspec index 2c063dd..56145b3 100644 --- a/ios/appsflyer_sdk.podspec +++ b/ios/appsflyer_sdk.podspec @@ -3,7 +3,7 @@ # Pod::Spec.new do |s| s.name = 'appsflyer_sdk' - s.version = '6.14.3' + s.version = '6.15.1' s.summary = 'AppsFlyer Integration for Flutter' s.description = <<-DESC AppsFlyer is the market leader in mobile advertising attribution & analytics, helping marketers to pinpoint their targeting, optimize their ad spend and boost their ROI. @@ -21,5 +21,5 @@ AppsFlyer is the market leader in mobile advertising attribution & analytics, he s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' - s.ios.dependency 'AppsFlyerFramework','6.14.3' + s.ios.dependency 'AppsFlyerFramework','6.15.1' end From 9f90c8e3cb036218a091d47a56acce0aeff2ba9c Mon Sep 17 00:00:00 2001 From: Dani-Koza-AF Date: Tue, 3 Sep 2024 17:48:33 +0300 Subject: [PATCH 4/6] Improvement of Android side impl --- .../appsflyersdk/AppsflyerSdkPlugin.java | 59 +++++++------------ 1 file changed, 21 insertions(+), 38 deletions(-) diff --git a/android/src/main/java/com/appsflyer/appsflyersdk/AppsflyerSdkPlugin.java b/android/src/main/java/com/appsflyer/appsflyersdk/AppsflyerSdkPlugin.java index 9bca7ac..bc66b4a 100644 --- a/android/src/main/java/com/appsflyer/appsflyersdk/AppsflyerSdkPlugin.java +++ b/android/src/main/java/com/appsflyer/appsflyersdk/AppsflyerSdkPlugin.java @@ -961,30 +961,12 @@ private void logEvent(MethodCall call, MethodChannel.Result result) { private void logAdRevenue(MethodCall call, Result result) { try { - String monetizationNetwork = requireNonNullArgument(call, result, "monetizationNetwork", "NULL_MONETIZATION_NETWORK"); - if (monetizationNetwork == null) return; - - String currencyIso4217Code = requireNonNullArgument(call, result, "currencyIso4217Code", "NULL_CURRENCY_CODE"); - if (currencyIso4217Code == null) return; - - Double revenueValue = requireNonNullArgument(call, result, "revenue", "NULL_REVENUE"); - if (revenueValue == null) return; - double revenue = revenueValue; // Auto-unboxing to primitive double type - - String mediationNetworkString = requireNonNullArgument(call, result, "mediationNetwork", "NULL_MEDIATION_NETWORK"); - if (mediationNetworkString == null) return; - MediationNetwork mediationNetwork = null; - for (MediationNetwork network : MediationNetwork.values()) { - if (network.getValue().equalsIgnoreCase(mediationNetworkString)) { - mediationNetwork = network; - break; - } - } - if (mediationNetwork == null) { - result.error("INVALID_MEDIATION_NETWORK", "Invalid mediation network: " + mediationNetworkString + ". Please use the mediation networks provided by AFMediationNetwork.", null); - Log.e("AppsFlyer", "Invalid mediation network: " + mediationNetworkString); - return; - } + String monetizationNetwork = requireNonNullArgument(call, "monetizationNetwork"); + String currencyIso4217Code = requireNonNullArgument(call, "currencyIso4217Code"); + double revenue = requireNonNullArgument(call,"revenue"); + String mediationNetworkString = requireNonNullArgument(call,"mediationNetwork"); + + MediationNetwork mediationNetwork = MediationNetwork.valueOf(mediationNetworkString.toUpperCase()); // No null check for additionalParameters since it's acceptable for it to be null (optional data) Map additionalParameters = call.argument("additionalParameters"); @@ -997,34 +979,35 @@ private void logAdRevenue(MethodCall call, Result result) { ); AppsFlyerLib.getInstance().logAdRevenue(adRevenueData, additionalParameters); - result.success(true); - } catch (Exception e) { - result.error("UNEXPECTED_ERROR", "[logAdRevenue]: An error occurred retrieving method arguments: " + e.getMessage(), null); - Log.e("AppsFlyer", "Exception occurred: [logAdRevenue]:", e); + } catch (IllegalArgumentException e) { + // The IllegalArgumentException could come from either requireNonNullArgument or valueOf methods. + result.error("INVALID_ARGUMENT_PROVIDED", e.getMessage(), null); + } + catch (Throwable t) { + result.error("UNEXPECTED_ERROR", "[logAdRevenue]: An unexpected error occurred: " + t.getMessage(), null); + Log.e("AppsFlyer", "Unexpected exception occurred: [logAdRevenue]", t); } } /** * Utility method to ensure that an argument with the specified name is not null. - * If the argument is null, this method will log an error, send an error response to the Flutter side, - * and return null. The calling method can then terminate immediately without further processing. + * If the argument is null, this method will throw an IllegalArgumentException. + * The calling method can then terminate immediately without further processing. * * @param call The MethodCall from Flutter, containing all the arguments. - * @param result The Result to send responses back to the calling Dart code in Flutter. * @param argumentName The name of the argument expected in the MethodCall. - * @param errorCode The error code to use when reporting a null argument error. * @param The type of the argument being checked for nullity. - * @return The argument value if it is not null; null otherwise. + * @return The argument value if it is not null; throw IllegalArgumentException otherwise. */ - private T requireNonNullArgument(MethodCall call, Result result, String argumentName, String errorCode) { - T value = call.argument(argumentName); - if (value == null) { - result.error(errorCode, argumentName + " must not be null", null); + private T requireNonNullArgument(MethodCall call, String argumentName) throws IllegalArgumentException { + T argument = call.argument(argumentName); + if (argument == null) { Log.e("AppsFlyer", "Exception occurred when trying to: " + call.method + "->" + argumentName + " must not be null"); + throw new IllegalArgumentException("[" + call.method + "]: " + argumentName + " must not be null"); } - return value; + return argument; } //RD-65582 From e7d4dc6b0143cb89ab00d712422f03c47d0593ac Mon Sep 17 00:00:00 2001 From: Dani-Koza-AF Date: Wed, 4 Sep 2024 14:17:28 +0300 Subject: [PATCH 5/6] Added documentation --- doc/API.md | 101 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 95 insertions(+), 6 deletions(-) diff --git a/doc/API.md b/doc/API.md index 390ae9e..94cc932 100644 --- a/doc/API.md +++ b/doc/API.md @@ -4,6 +4,8 @@ ## Types - [AppsFlyerOptions](#appsflyer-options) +- [AdRevenueData](#AdRevenueData) +- [AFMediationNetwork](#AFMediationNetwork) ## Methods - [initSdk](#initSdk) @@ -52,11 +54,12 @@ - [getOutOfStore](#getOutOfStore) - [setDisableNetworkData](#setDisableNetworkData) - [performOnDeepLinking](#performondeeplinking) +- [logAdRevenue](#logAdRevenue) - Since 6.15.1 --- -##### **`AppsflyerSdk(Map options)`** +##### **`AppsflyerSdk(Map options)`** | parameter | type | description | | --------- | ----- | ----------------- | @@ -116,6 +119,42 @@ Once `AppsflyerSdk` object is created, you can call `initSdk` method. --- +##### **`AdRevenueData`** + +| parameter | type | description | +| --------- | ------------------ | ----------------- | +| `monetizationNetwork` | `String` | | +| `mediationNetwork` | `String` | value must be taken from `AFMediationNetwork` | +| `currencyIso4217Code` | `String` | | +| `revenue` | `double` | | +| `additionalParameters` | `Map?` | | + +--- + +##### **`AFMediationNetwork`** +an enumeration that includes the supported mediation networks by AppsFlyer. + + +| networks | +| -------- | +| ironSource +applovinMax +googleAdMob +fyber +appodeal +admost +topon +tradplus +yandex +chartboost +unity +toponPte +customMediation +directMonetizationNetwork | + +--- + + ##### **`initSdk({bool registerConversionDataCallback, bool registerOnAppOpenAttributionCallback}) async` (Changed in 1.2.2)** initialize the SDK, using the options initialized from the constructor| @@ -561,7 +600,7 @@ This call matches the following payload structure: 1. First define the Onelink ID (find it in the AppsFlyer dashboard in the onelink section: - **`Future setAppInviteOneLinkID(String oneLinkID, Function callback)`** +**`Future setAppInviteOneLinkID(String oneLinkID, Function callback)`** 2. Set the AppsFlyerInviteLinkParams class to set the query params in the user invite link: @@ -579,7 +618,7 @@ class AppsFlyerInviteLinkParams { 3. Call the generateInviteLink API to generate the user invite link. Use the success and error callbacks for handling. - **`void generateInviteLink(AppsFlyerInviteLinkParams parameters, Function success, Function error)`** +**`void generateInviteLink(AppsFlyerInviteLinkParams parameters, Function success, Function error)`** _Example:_ @@ -653,7 +692,7 @@ appsFlyerSdk.setCurrentDeviceLanguage("en"); --- ** `void setSharingFilterForPartners(List partners)`** -`setSharingFilter` & `setSharingFilterForAllPartners` APIs were deprecated! +`setSharingFilter` & `setSharingFilterForAllPartners` APIs were deprecated! Use `setSharingFilterForPartners` instead. @@ -672,9 +711,9 @@ appsFlyerSdk.setSharingFilterForPartners(['googleadwords_int', 'all']); --- ** `void setOneLinkCustomDomain(List brandDomains)`** -Use this API in order to set branded domains. +Use this API in order to set branded domains. -Find more information in the [following article on branded domains](https://support.appsflyer.com/hc/en-us/articles/360002329137-Implementing-Branded-Links). +Find more information in the [following article on branded domains](https://support.appsflyer.com/hc/en-us/articles/360002329137-Implementing-Branded-Links). _Example:_ ```dart @@ -823,3 +862,53 @@ Note:
This API will trigger the `appsflyerSdk.onDeepLink` callback. In the fo _appsflyerSdk.startSDK(); } ``` + +--- + +### **
`void logAdRevenue(AdRevenueData adRevenueData)`** + +The logAdRevenue API is designed to simplify the process of logging ad revenue events to AppsFlyer from your Flutter application. This API tracks revenue generated from advertisements, enriching your monetization analytics. Below you will find instructions on how to use this API correctly, along with detailed descriptions and examples for various input scenarios. + +### **Usage:** +To use the logAdRevenue method, you must: + +1. Prepare an instance of `AdRevenueData` with the required information about the ad revenue event. +1. Call `logAdRevenue` with the `AdRevenueData` instance. + +**AdRevenueData Class** +[AdRevenueData](#AdRevenueData) is a data class representing all the relevant information about an ad revenue event: + +* `monetizationNetwork`: The source network from which the revenue was generated (e.g., AdMob, Unity Ads). +* `mediationNetwork`: The mediation platform managing the ad (use AFMediationNetwork enum for supported networks). +* `currencyIso4217Code`: The ISO 4217 currency code representing the currency of the revenue amount (e.g., "USD", "EUR"). +* `revenue`: The amount of revenue generated from the ad. +* `additionalParameters`: Additional parameters related to the ad revenue event (optional). + + +**AFMediationNetwork Enum** +[AFMediationNetwork](#AFMediationNetwork) is an enumeration that includes the supported mediation networks by AppsFlyer. It's important to use this enum to ensure you provide a valid network identifier to the logAdRevenue API. + +### Example: +```dart +// Instantiate AdRevenueData with the ad revenue details. +AdRevenueData adRevenueData = AdRevenueData( + monetizationNetwork: "GoogleAdMob", // Replace with your actual monetization network. + mediationNetwork: AFMediationNetwork.applovinMax.value, // Use the value from the enum. + currencyIso4217Code: "USD", + revenue: 1.23, + additionalParameters: { + // Optional additional parameters can be added here. This is an example, can be discard if not needed. + 'adUnitId': 'ca-app-pub-XXXX/YYYY', + 'ad_network_click_id': '12345' + } +); + +// Log the ad revenue event. +logAdRevenue(adRevenueData); +``` + +**Additional Points** +* Mediation network input must be from the provided [AFMediationNetwork](#AFMediationNetwork) + enum to ensure proper processing by AppsFlyer. For instance, use `AFMediationNetwork.googleAdMob.value` to denote Google AdMob as the Mediation Network. +* The `additionalParameters` map is optional. Use it to pass any extra information you have regarding the ad revenue event; this information could be useful for more refined analytics. +* Make sure the `currencyIso4217Code` adheres to the appropriate standard. Misconfigured currency code may result in incorrect revenue tracking. \ No newline at end of file From 6b76d63952dc79e649eb35338b1bbc54d2e59611 Mon Sep 17 00:00:00 2001 From: Dani-Koza-AF Date: Wed, 4 Sep 2024 15:13:04 +0300 Subject: [PATCH 6/6] Added missing info in docs --- CHANGELOG.md | 5 +++++ README.md | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67e2559..cb93dfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ # Versions +## 6.15.1 +- Implementation of the new logAdRevenue API for iOS and Android +- Documentation update for the new logAdRevenue API +- Update iOS version to 6.15.1 +- Update Android version to 6.15.1 ## 6.14.3 - Fixed mapOptions issue with manualStart - Inherit Privacy Manifest from the native iOS SDK via Cocoapods diff --git a/README.md b/README.md index 130c50f..5a68ad6 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ ### This plugin is built for -- Android AppsFlyer SDK **v6.14.0** -- iOS AppsFlyer SDK **v6.14.3** +- Android AppsFlyer SDK **v6.15.1** +- iOS AppsFlyer SDK **v6.15.1** ## ❗❗ Breaking changes when updating to v6.x.x❗❗