├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md ├── bot_config.yaml └── workflows │ └── responseToSupportIssue.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── appsflyer │ └── appsflyersdk │ ├── AppsFlyerConstants.java │ ├── AppsflyerSdkPlugin.java │ └── LogMessages.java ├── assets └── demo_example.png ├── covBadgeGen.js ├── coverage_badge.svg ├── doc ├── API.md ├── AdvancedAPI.md ├── BasicIntegration.md ├── DMA.md ├── DeepLink.md ├── Guides.md ├── InAppEvents.md ├── Installation.md └── Testing.md ├── example ├── .env ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ ├── appsflyer │ │ │ │ │ └── example │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── assets │ └── demo_example.png ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ ├── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ │ └── LaunchImage.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ └── README.md │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── Info.plist │ │ ├── Runner-Bridging-Header.h │ │ └── Runner.entitlements │ └── RunnerTests │ │ └── RunnerTests.swift ├── lib │ ├── app_constants.dart │ ├── home_container.dart │ ├── home_container_streams.dart │ ├── main.dart │ ├── main_page.dart │ ├── text_border.dart │ └── utils.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── AppsFlyerAttribution.h │ ├── AppsFlyerAttribution.m │ ├── AppsFlyerStreamHandler.h │ ├── AppsFlyerStreamHandler.m │ ├── AppsflyerSdkPlugin.h │ ├── AppsflyerSdkPlugin.m │ └── FlutterAppDelegate+AppsFlyerStreamHandler.h └── appsflyer_sdk.podspec ├── lib ├── appsflyer_sdk.dart └── src │ ├── appsflyer_ad_revenue_data.dart │ ├── appsflyer_consent.dart │ ├── appsflyer_constants.dart │ ├── appsflyer_invite_link_params.dart │ ├── appsflyer_options.dart │ ├── appsflyer_request_listener.dart │ ├── appsflyer_sdk.dart │ ├── callbacks.dart │ └── udl │ ├── deep_link_result.dart │ └── deeplink.dart ├── local.properties ├── package.json ├── pubspec.yaml └── test └── appsflyer_sdk_test.dart /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/bot_config.yaml: -------------------------------------------------------------------------------- 1 | # Configuration for AppsFlyerBot 2 | # aging issues: 3 | enableClosingAgingIssue: true 4 | enableLabelingAgingIssue: true 5 | 6 | #After how long the bot should consider an issue as obsolete (in days) 7 | agingTime: 180 8 | -------------------------------------------------------------------------------- /.github/workflows/responseToSupportIssue.yml: -------------------------------------------------------------------------------- 1 | 2 | # This workflow creates new comment from template to an issue 3 | # if it was labled as 'support' 4 | 5 | name: Add comment 6 | on: 7 | issues: 8 | types: 9 | - labeled 10 | jobs: 11 | add-comment: 12 | if: github.event.label.name == 'support' 13 | runs-on: ubuntu-latest 14 | permissions: 15 | issues: write 16 | steps: 17 | - name: Add comment 18 | uses: peter-evans/create-or-update-comment@a35cf36e5301d70b76f316e867e7788a55a31dae 19 | with: 20 | issue-number: ${{ github.event.issue.number }} 21 | body: | 22 | 👋 Hi @${{ github.event.issue.user.login }} and Thank you for reaching out to us. 23 | In order for us to provide optimal support, please submit a ticket to our support team at support@appsflyer.com. 24 | When submitting the ticket, please specify: 25 | - ✅ your AppsFlyer sign-up (account) email 26 | - ✅ app ID 27 | - ✅ production steps 28 | - ✅ logs 29 | - ✅ code snippets 30 | - ✅ and any additional relevant information. 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .vscode/ 21 | 22 | # Flutter repo-specific 23 | /bin/cache/ 24 | /bin/mingit/ 25 | /dev/benchmarks/mega_gallery/ 26 | /dev/bots/.recipe_deps 27 | /dev/bots/android_tools/ 28 | /dev/docs/doc/ 29 | /dev/docs/flutter.docs.zip 30 | /dev/docs/lib/ 31 | /dev/docs/pubspec.yaml 32 | /packages/flutter/coverage/ 33 | version 34 | 35 | # Flutter/Dart/Pub related 36 | **/doc/api/ 37 | .dart_tool/ 38 | .flutter-plugins 39 | .packages 40 | .pub-cache/ 41 | .pub/ 42 | build/ 43 | flutter_*.png 44 | linked_*.ds 45 | unlinked.ds 46 | unlinked_spec.ds 47 | 48 | # Android related 49 | **/android/.gradle 50 | **/android/captures/ 51 | **/android/gradlew.bat 52 | **/android/local.properties 53 | **/android/**/GeneratedPluginRegistrant.java 54 | 55 | # iOS/XCode related 56 | **/ios/**/*.mode1v3 57 | **/ios/**/*.mode2v3 58 | **/ios/**/*.moved-aside 59 | **/ios/**/*.pbxuser 60 | **/ios/**/*.perspectivev3 61 | **/ios/**/*sync/ 62 | **/ios/**/.sconsign.dblite 63 | **/ios/**/.tags* 64 | **/ios/**/.vagrant/ 65 | **/ios/**/DerivedData/ 66 | **/ios/**/Icon? 67 | **/ios/**/Pods/ 68 | **/ios/**/.symlinks/ 69 | **/ios/**/profile 70 | **/ios/**/xcuserdata 71 | **/ios/.generated/ 72 | **/ios/Flutter/App.framework 73 | **/ios/Flutter/Flutter.framework 74 | **/ios/Flutter/Generated.xcconfig 75 | **/ios/Flutter/app.flx 76 | **/ios/Flutter/app.zip 77 | **/ios/Flutter/flutter_assets/ 78 | **/ios/ServiceDefinitions.json 79 | **/ios/Runner/GeneratedPluginRegistrant.* 80 | 81 | # Exceptions to above rules. 82 | !**/ios/**/default.mode1v3 83 | !**/ios/**/default.mode2v3 84 | !**/ios/**/default.pbxuser 85 | !**/ios/**/default.perspectivev3 86 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 87 | 88 | android/\.settings/ 89 | 90 | android/\.project 91 | 92 | android/\.classpath 93 | 94 | example/ios/Flutter/flutter_export_environment.sh 95 | 96 | example/ios/Runner.xcodeproj/project.pbxproj 97 | 98 | example/.flutter-plugins-dependencies 99 | 100 | example/macos/* 101 | 102 | example/linux/* 103 | 104 | example/windows/* 105 | 106 | example/web/* 107 | 108 | example/android/app/.cxx 109 | 110 | example/\.metadata 111 | example/analysis_options.yaml 112 | 113 | 114 | node_modules/ 115 | 116 | covBadgeGen.js 117 | coverage/ 118 | .env 119 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | language: minimal 4 | env: 5 | global: 6 | - FLUTTER_HOME=$HOME/flutter 7 | 8 | before_install: 9 | - export PATH="$PATH:$FLUTTER_HOME/bin" 10 | - git clone https://github.com/flutter/flutter.git -b stable --depth=1 $FLUTTER_HOME 11 | - flutter config --no-analytics 12 | - flutter doctor -v 13 | install: 14 | - flutter pub get 15 | script: 16 | - flutter test test 17 | cache: 18 | directories: 19 | - "$HOME/.pub-cache" -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Versions 2 | ## 6.16.2 3 | - setConsentData is now deprecated! 4 | - setConsentDataV2 is the new and recommended way to set manual user consent. 5 | - Added getVersionNumber, returns the plugin's version. 6 | - Fixed typos within the code. 7 | - Fixed and updated tests and their frameworks. 8 | - Push notification measurment API's documentation has been updated. 9 | - Closed a few potential memory leaks. 10 | - Update iOS version to 6.16.2 11 | - Update Android version to 6.16.2 12 | ## 6.15.2 13 | - Fixed NullPointerException issue on Android that some clients had. 14 | - Fixed Android MediationNetwork enum issue. 15 | - Update iOS version to 6.15.3 16 | - Update Android version to 6.15.2 17 | ## 6.15.1 18 | - Implementation of the new logAdRevenue API for iOS and Android 19 | - Documentation update for the new logAdRevenue API 20 | - Update iOS version to 6.15.1 21 | - Update Android version to 6.15.1 22 | ## 6.14.3 23 | - Fixed mapOptions issue with manualStart 24 | - Inherit Privacy Manifest from the native iOS SDK via Cocoapods 25 | - Bump iOS version to 6.14.3 26 | ## 6.14.2 27 | - Bump version to iOS v6.14.2 and Android v6.14.0 28 | - Added Privacy Manifest to support Apple latest changes: https://developer.apple.com/documentation/bundleresources/privacy_manifest_files 29 | ## 6.13.2+1 30 | - Hotfix for manualStart on iOS 31 | ## 6.13.2 32 | - Added new APIs such as `anonymizeUser` , `performOnDeepLinking` 33 | - Added to the `startSDK` API, `onSuccess` and `onError` callbacks 34 | - Update to iOS SDK to v6.13.2 35 | ## 6.13.0+2 36 | - Update to iOS SDK to v6.13.1 37 | ## 6.13.0+1 38 | - Added enableTCFDataCollection , setConsentData with AppsFlyerConsent class 39 | - Added new boolean option to AppsFlyerOption class , manualStart 40 | - Added startSDK API 41 | - Updated readme and elaborated on the new APIs 42 | ## 6.12.2 43 | - Update to Android SDK to v6.12.2 & iOS SDK to v6.12.2 44 | - Deprecated CreateOneLinkHttpTask updated to LinkGenerator 45 | - Fixed Gradle 8.0 issue 46 | - Documented API and removed unused imports 47 | ## 6.11.3 48 | - null pointer exception fix for android, push notification bug fix & ios sdk 6.11.2 49 | ## 6.11.2 50 | - update to Android SDK to v6.11.2 51 | ## 6.11.1 52 | - update to Android SDK to v6.11.1 53 | ## 6.10.1 54 | - update to Android SDK to v6.10.3 & iOS SDK to v6.10.1 55 | ## 6.9.3 56 | - update to Android SDK to v6.9.3 & iOS SDK to v6.9.1 57 | - Added `addPushNotificationDeepLinkPath` API 58 | - Added `setCustomerIdAndLogSession` API for android 59 | ## 6.8.2 60 | - update to android v6.8.2 61 | ## 6.8.0 62 | - The API `enableLocationCollection` has been removed. 63 | - The API `setDisableNetworkData` has been added. 64 | - The AD_ID permission has been added to the plugin. 65 | - Updated AppsFlyer Android SDK to v6.8.0 66 | - Updated AppsFlyer iOS SDK to v6.8.0 67 | ## 6.5.2+2 68 | ## 6.5.2+1 69 | - New APIs: getOutOfStore, setOutOfStore, setResolveDeepLinkURLs, setPartnerData 70 | ## 6.5.2 71 | - Updated AppsFlyer Android SDK to v6.5.2 72 | - Updated AppsFlyer iOS SDK to v6.5.2 73 | ## 6.4.4+2 74 | ## 6.4.0+2 75 | ## 6.4.0+1 76 | - Added nullable in deeplink object 77 | - Remove of local stream import 78 | ## 6.4.0 79 | - Updated to 6.4.0 in iOS & Android SDK 80 | - Dedicated class for UDL for handling deeplink 81 | - New API `setSharingFilterForPartners`.`setSharingFilter` & `setSharingFilterForAllPartners` APIs were deprecated. 82 | - setIntent is not required anymore in MainActivity (Android) 83 | - application(_:open:sourceApplication:annotation:) is not required anymore in AppDelegate (iOS) 84 | - application(_:open:options:) is not required anymore in AppDelegate (iOS) 85 | - application(_:continue:restorationHandler:) is not required anymore in AppDelegate (iOS) 86 | 87 | ## 6.3.5+3 88 | rollback to previous version 89 | ## 6.3.5+2 90 | Removed streams from the plugin 91 | ## 6.3.5+1 92 | Added setCurrentDeviceLanguage API 93 | ## 6.3.5 94 | - Updated AppsFlyer iOS SDK to v6.3.5 95 | 96 | ## 6.3.3+1 97 | - fix JNI issue 98 | 99 | ## 6.3.3-nullsafety.0 100 | - change to local broadcast 101 | 102 | ## 6.3.2-nullsafety.0 103 | - Update to SDK v6.3.2 and added support for disabling advertiser ID on Android 104 | 105 | ## 6.3.0-nullsafety.1 106 | - Added effective dart package for linter rules 107 | 108 | ## 6.3.0-nullsafety.0 109 | - Update iOS & Android to SDK v6.3.0 110 | 111 | ## 6.2.6-nullsafety.1 112 | - Fix for deeplinking in iOS 113 | 114 | ## 6.2.6-nullsafety.0 115 | - Update for iOS SDK V6.2.6 116 | - Refactoring for SKAD network feature 117 | 118 | ## 6.2.4-nullsafety.5 119 | - Added support for strict mode (kids app) 120 | - Added support for wait for att status API 121 | 122 | ## 6.2.4+4-nullsafety 123 | - Fix small bug with validateAndLogInAppIosPurchase API 124 | 125 | ## 6.2.4+3-nullsafety 126 | - Small fix for enableFacebookDeferredApplinks, useReceiptValidationSandbox, disableSKAdNetwork, setPushNotification APIs in iOS 127 | 128 | ## 6.2.4+2-nullsafety 129 | - Added disable SKAD API 130 | 131 | ## 6.2.4+1-nullsafety 132 | - Fix for SKAD 133 | 134 | ## 6.2.4 135 | - Update to iOS SDK v6.2.4 136 | 137 | ## 6.2.3+2 138 | - Flutter 2.0 update including null safety support 139 | 140 | ## 6.2.3+2-beta 141 | - Flutter 2.0 update including null safety support 142 | 143 | ## 6.2.3+1 144 | - Added enableFacebookDeferredApplinks API 145 | 146 | ## 6.2.3 147 | - Update to iOS SDK V6.2.3 148 | 149 | ## 6.2.1+7 150 | - Refactor for user invite feature 151 | 152 | ## 6.2.1+6 153 | - Added callbacks support for purchase validation API 154 | 155 | ## 6.2.1+5 156 | - Added support for useReceiptValidationSandbox API 157 | 158 | ## 6.2.1+4 159 | - Seperated purchase validation API to iOS/Android 160 | 161 | ## 6.2.1+3 162 | - Fixed Unified deeplink crush on first launch 163 | 164 | ## 6.2.1+2 165 | - Hot Fix 166 | 167 | ## 6.2.1+1 168 | - Added support for push notification API 169 | 170 | ## 6.2.1 171 | - Update iOS to v6.2.1 172 | - Added support for Unified Deeplink 173 | - Fixed deeplinks issues both for Android & iOS 174 | 175 | ## 6.2.0+2 176 | - Revert back to version 6.2.0 177 | 178 | ## 6.2.0+1 179 | - Added Unified Deeplinking for Android 180 | 181 | ## 6.2.0 182 | - Update both iOS & Android to v6.2.0 183 | 184 | ## 6.0.5+3 185 | - Fixed `FormatException` caused by iOS side 186 | 187 | ## 6.0.5+2 188 | - Switch to callbacks for `onAppOpenAttribution` and `onConversionData` 189 | 190 | ## 6.0.5+1 191 | - Fixed `updateServerUninstallToken` on iOS 192 | 193 | ## 6.0.5 194 | - Update SDK version to: 195 | - Android: 5.4.5 196 | - iOS: 6.0.5 197 | - Update Google install referrer to 2.1 198 | - Added support for: https://support.appsflyer.com/hc/en-us/articles/207032066#additional-apis-kids-apps 199 | - Fixed typo in `validateAndLogInAppPurchase` 200 | 201 | ## 6.0.3+5 202 | - Add null check for context in Android 203 | 204 | ## 6.0.3+4 205 | - Fixed bug with sending arguments with methodChannel 206 | 207 | ## 6.0.3+3 208 | - Added the functions: 209 | `logCrossPromotionAndOpenStore` 210 | `logCrossPromotionImpression` 211 | `setAppInviteOneLinkID` 212 | `generateInviteLink` 213 | 214 | ## 6.0.3+2 215 | - Removed AppTrackingTransparency framework 216 | 217 | ## 6.0.3+1 218 | - Updated AppsFlyer iOS SDK to v6.0.3 219 | 220 | ## 6.0.2+1 221 | - Fixed the issue in the example app on Android platform 222 | - Updated AppsFlyer SDK to v5.4.3 223 | 224 | ## 6.0.2 225 | - iOS sdk version is now 6.0.2 and support AppTrackingTransparency framework 226 | - Android sdk version is 5.4.1 227 | 228 | ## 5.4.1+1 229 | - Added documentation 230 | - Added secured links to README 231 | 232 | ## 5.4.1 233 | 234 | - Updated AppsFlyer SDK to v5.4.1 235 | - Added `sharedFilter` support 236 | 237 | ## 5.2.0+3 238 | 239 | - Add support for opt-in/ opt-out scenarios 240 | - Fix typo in constant AF_VALIDATE_PURCHASE 241 | 242 | ## 5.2.0+2 243 | 244 | - added default values to `initSdk` params 245 | 246 | ## 5.2.0+1 247 | 248 | - Removed the use of RxDart 249 | - Checked that the streams are not closed before sending events 250 | 251 | ## 5.2.0 252 | 253 | - AppsFlyer sdk version is updated to v5.2.0 254 | - Switched `StreamController` to `BehaviourSubject` to fix bad state related to unclosed streams 255 | 256 | ## 1.2.5 257 | 258 | - `initSdk` now uses Future.delayed 259 | - Fixed iOS error in `initSdk` returned String instead of Map 260 | 261 | ## 1.2.3 262 | 263 | - Updated the README 264 | - `initSdk` function now uses named parameters 265 | 266 | ## 1.2.2 267 | 268 | - Updated AppsFlyer SDK version: 269 | - Android: v5.1.1 270 | - iOS: v5.1.0 271 | - Added `getSdkVersion` to the api 272 | - Changed `initSdk` to return a dynamic map 273 | 274 | ## 1.1.3 275 | 276 | - Added getAppsFlyerUID function to get a device unique user id 277 | 278 | ## 1.1.2 279 | 280 | - Updated appsflyer framework to 4.9.0 281 | 282 | ## 1.1.0 283 | 284 | - Added the following functions: 285 | - `Stream validateAndTrackInAppPurchase( String publicKey, String signature, String purchaseData, String price, String currency, Map additionalParameters)` 286 | - `void updateServerUninstallToken(String token)` 287 | - `Future getHostPrefix()` 288 | - `Future getHostName()` 289 | - `void setHost(String hostPrefix, String hostName)` 290 | - `void setCollectIMEI(bool isCollect)` 291 | - `void setCollectAndroidId(bool isCollect)` 292 | - `void setAdditionalData(Map addionalData)` 293 | - `void waitForCustomerUserId(bool wait)` 294 | - `void setCustomerUserId(String userId)` 295 | - `void enableLocationCollection(bool flag)` 296 | - `void setAndroidIdData(String androidIdData)` 297 | - `void setImeiData(String imei)` 298 | - `void enableUninstallTracking(String senderId)` 299 | - `void setIsUpdate(bool isUpdate)` 300 | - `void setCurrencyCode(String currencyCode)` 301 | - `void stopTracking(bool isTrackingStopped)` 302 | - `void setMinTimeBetweenSessions(int seconds)` 303 | - `void setUserEmails(List emails, [EmailCryptType cryptType]` 304 | 305 | - Fixed `onAppOpenAttribution` not being called bug 306 | 307 | ## 1.0.8 308 | 309 | - Added `AppsFlyerOptions` to support easier options setup 310 | - Changed plugin lib structure 311 | 312 | ## 1.0.6 313 | 314 | - Fixed iOS app id crash 315 | 316 | ## 1.0.4 317 | 318 | - Added dartdoc documentation. 319 | - Changed static methods to class instance methods. 320 | 321 | ## 1.0.0 322 | 323 | First stable version 324 | 325 | ## 0.0.5 326 | 327 | - Changed access modifiers from public to private to class variables 328 | 329 | ## 0.0.3 330 | 331 | Supported sdk functions: 332 | 333 | - initSdk 334 | - trackEvent 335 | - registerConversionDataCallback 336 | - registerOnAppOpenAttributionCallback 337 | 338 | ## 0.0.1 339 | 340 | Initial release. 341 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Appsflyer Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # appsflyer-flutter-plugin 4 | 5 | [![pub package](https://img.shields.io/pub/v/appsflyer_sdk.svg)](https://pub.dartlang.org/packages/appsflyer_sdk) 6 | ![Coverage](https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/master/coverage_badge.svg) 7 | 8 | 🛠 In order for us to provide optimal support, we would kindly ask you to submit any issues to support@appsflyer.com 9 | 10 | > *When submitting an issue please specify your AppsFlyer sign-up (account) email , your app ID , production steps, logs, code snippets and any additional relevant information.* 11 | 12 | 13 | ### This plugin is built for 14 | 15 | - Android AppsFlyer SDK **v6.16.2** 16 | - iOS AppsFlyer SDK **v6.16.2** 17 | 18 | ## ❗❗ Breaking changes when updating to v6.x.x❗❗ 19 | 20 | If you have used one of the removed/changed APIs, please check the integration guide for the updated instructions. 21 | 22 | - From version `6.11.2`, the `setPushNotification` will not work in iOS. [Please use our new API `sendPushNotificationData` when receiving a notification on flutter side](/doc/API.md#sendPushNotificationData). 23 | 24 | - From version `6.8.0`, the `enableLocationCollection` has been removed from the plugin. 25 | 26 | - From version `6.4.0`, UDL (Unified deep link) now as a dedicated class with getters for handling the deeplink result. 27 | [Check the full UDL guide](https://github.com/AppsFlyerSDK/appsflyer-flutter-plugin/blob/master/doc/Guides.md#-3-unified-deep-linking). 28 | `setSharingFilter` & `setSharingFilterForAllPartners` APIs are deprecated. 29 | Instead use the [new API `setSharingFilterForPartners`](https://github.com/AppsFlyerSDK/appsflyer-flutter-plugin/blob/RD-69098/update6.4.0%26more/doc/API.md#setSharingFilterForPartners). 30 | 31 | - From version `6.3.5+2`, Remove stream from the plugin (no change is needed if you use callbacks for handling deeplink). 32 | 33 | - From version `6.2.3+2`, Flutter 2 is supported, including null safety. 34 | `6.2.4-flutterv1` will use iOS SDK 6.2.4 with Flutter V1. 35 | 36 | - From version `6.0.0`, we have renamed the following APIs: 37 | 38 | |Before v6 | v6 | 39 | |-------------------------------|-----------------------------| 40 | | trackEvent | logEvent | 41 | | stopTracking | stop | 42 | | validateAndTrackInAppPurchase | validateAndLogInAppPurchase | 43 | 44 | - From version `6.1.2+4`, we have renamed the following APIs: 45 | 46 | |Before v6.1.2+4 | v6.1.2+4 | 47 | |-------------------------------|-----------------------------| 48 | | validateAndLogInAppPurchase | validateAndLogInAppIosPurchase/validateAndLogInAppAndroidPurchase | 49 | 50 | ### Important notice 51 | - Switch `ConversionData` and `OnAppOpenAttribution` to be based on callbacks instead of streams from plugin version `6.0.5+2`. 52 | 53 | ## AD_ID permission for Android 54 | In v6.8.0 of the AppsFlyer SDK, we added the normal permission `com.google.android.gms.permission.AD_ID` to the SDK's AndroidManifest, 55 | to allow the SDK to collect the Android Advertising ID on apps targeting API 33. 56 | If your app is targeting children, you need to revoke this permission to comply with Google's Data policy. 57 | You can read more about it [here](https://dev.appsflyer.com/hc/docs/install-android-sdk#the-ad_id-permission). 58 | 59 | ## 📖 Guides 60 | - [Adding the SDK to your project](/doc/Installation.md) 61 | - [Initializing the SDK](/doc/BasicIntegration.md) 62 | - [In-app Events](/doc/InAppEvents.md) 63 | - [Deep Linking](/doc/DeepLink.md) 64 | - [Advanced APIs](/doc/AdvancedAPI.md) 65 | - [Testing the integration](/doc/Testing.md) 66 | - [APIs](/doc/API.md) 67 | - [Sample App](/example) 68 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:effective_dart/analysis_options.yaml 2 | linter: 3 | rules: 4 | public_member_api_docs: false 5 | constant_identifier_names: false 6 | lines_longer_than_80_chars: false 7 | omit_local_variable_types: false 8 | avoid_positional_boolean_parameters: false -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.appsflyer.appsflyersdk' 2 | version '1.0-SNAPSHOT' 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | google() 7 | } 8 | } 9 | 10 | rootProject.allprojects { 11 | repositories { 12 | mavenCentral() 13 | google() 14 | } 15 | } 16 | apply plugin: 'com.android.library' 17 | apply plugin: 'org.jetbrains.kotlin.android' 18 | 19 | android { 20 | defaultConfig { 21 | minSdkVersion 19 22 | compileSdk 35 23 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 24 | 25 | multiDexEnabled true 26 | } 27 | lintOptions { 28 | disable 'InvalidPackage' 29 | } 30 | namespace 'com.appsflyer.appsflyersdk' 31 | 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_17 34 | targetCompatibility JavaVersion.VERSION_17 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = '17' 39 | } 40 | 41 | } 42 | 43 | dependencies { 44 | implementation fileTree(dir: 'libs', include: ['*.jar']) 45 | implementation 'androidx.appcompat:appcompat:1.0.0' 46 | implementation 'com.appsflyer:af-android-sdk:6.16.2' 47 | implementation 'com.android.installreferrer:installreferrer:2.1' 48 | } -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'appsflyer_sdk' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/src/main/java/com/appsflyer/appsflyersdk/AppsFlyerConstants.java: -------------------------------------------------------------------------------- 1 | package com.appsflyer.appsflyersdk; 2 | 3 | public final class AppsFlyerConstants { 4 | final static String PLUGIN_VERSION = "6.16.2"; 5 | final static String AF_APP_INVITE_ONE_LINK = "appInviteOneLink"; 6 | final static String AF_HOST_PREFIX = "hostPrefix"; 7 | final static String AF_HOST_NAME = "hostName"; 8 | final static String AF_IS_DEBUG = "isDebug"; 9 | final static String AF_MANUAL_START = "manualStart"; 10 | final static String AF_DEV_KEY = "afDevKey"; 11 | final static String AF_EVENT_NAME = "eventName"; 12 | final static String AF_EVENT_VALUES = "eventValues"; 13 | final static String AF_ON_INSTALL_CONVERSION_DATA_LOADED = "onInstallConversionDataLoaded"; 14 | final static String AF_ON_APP_OPEN_ATTRIBUTION = "onAppOpenAttribution"; 15 | final static String AF_SUCCESS = "success"; 16 | final static String AF_FAILURE = "failure"; 17 | final static String AF_GCD = "GCD"; 18 | final static String AF_UDL = "UDL"; 19 | final static String AF_VALIDATE_PURCHASE = "validatePurchase"; 20 | final static String AF_GCD_CALLBACK = "onInstallConversionData"; 21 | final static String AF_OAOA_CALLBACK = "onAppOpenAttribution"; 22 | final static String AF_UDL_CALLBACK = "onDeepLinking"; 23 | final static String DISABLE_ADVERTISING_IDENTIFIER = "disableAdvertisingIdentifier"; 24 | 25 | final static String AF_EVENTS_CHANNEL = "af-events"; 26 | final static String AF_METHOD_CHANNEL = "af-api"; 27 | final static String AF_CALLBACK_CHANNEL = "callbacks"; 28 | 29 | final static String AF_BROADCAST_ACTION_NAME = "com.appsflyer.appsflyersdk"; 30 | 31 | final static String AF_PLUGIN_TAG = "AppsFlyer_FlutterPlugin"; 32 | } 33 | -------------------------------------------------------------------------------- /android/src/main/java/com/appsflyer/appsflyersdk/LogMessages.java: -------------------------------------------------------------------------------- 1 | package com.appsflyer.appsflyersdk; 2 | 3 | public final class LogMessages { 4 | 5 | // Prevent the instantiation of this utilities class. 6 | private LogMessages() { 7 | throw new IllegalStateException("LogMessages class should not be instantiated"); 8 | } 9 | 10 | public static final String METHOD_CHANNEL_IS_NULL = "mMethodChannel is null, cannot invoke the callback"; 11 | public static final String ACTIVITY_NOT_ATTACHED_TO_ENGINE = "Activity isn't attached to the flutter engine"; 12 | public static final String ERROR_WHILE_SETTING_CONSENT = "Error while setting consent data: "; 13 | } 14 | -------------------------------------------------------------------------------- /assets/demo_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/assets/demo_example.png -------------------------------------------------------------------------------- /covBadgeGen.js: -------------------------------------------------------------------------------- 1 | const lcov2badge = require("lcov2badge"); 2 | const fs = require("fs"); 3 | 4 | lcov2badge.badge("./coverage/lcov.info", function (err, svgBadge) { 5 | if (err) throw err; 6 | 7 | try { 8 | if (fs.existsSync("./coverage_badge.svg")) { 9 | fs.unlinkSync("./coverage_badge.svg"); 10 | console.log("[INFO] remove old file"); 11 | } 12 | } catch (err) { 13 | console.error(err); 14 | } 15 | 16 | console.log("[INFO] generate coverage image"); 17 | fs.writeFile("./coverage_badge.svg", svgBadge, (_) => 18 | console.log("[INFO] complete") 19 | ); 20 | }); -------------------------------------------------------------------------------- /coverage_badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | coverage 14 | coverage 15 | 39% 16 | 39% 17 | 18 | 19 | -------------------------------------------------------------------------------- /doc/AdvancedAPI.md: -------------------------------------------------------------------------------- 1 | # 📑 Advanced APIs 2 | 3 | - [Measure App Uninstalls](#uninstall) 4 | - [User invite](#user-invite) 5 | - [In-app purchase validation](#iae) 6 | - [Android Out of Store](#out-of-store) 7 | - [Set plugin for IOS 14](#ios14) 8 | 9 | --- 10 | 11 | ## Measure App Uninstalls 12 | 13 | ### iOS 14 | 15 | You may update the uninstall token from the native side and from the plugin side, as shown in the methods below, you do not have to implement both of the methods, but only one. 16 | You can read more about iOS Uninstall Measurement in our [knowledge base](https://support.appsflyer.com/hc/en-us/articles/4408933557137) and you can follow our guide for Uninstall measurement on our [DevHub](https://dev.appsflyer.com/hc/docs/uninstall-measurement-ios). 17 | 18 | #### First method 19 | 20 | You can register the uninstall token with AppsFlyer by modifying your `AppDelegate.m` file, add the following function call with your uninstall token inside [didRegisterForRemoteNotificationsWithDeviceToken](https://developer.apple.com/reference/uikit/uiapplicationdelegate). 21 | 22 | **Example:** 23 | ```objective-c 24 | @import AppsFlyerLib; 25 | 26 | ... 27 | 28 | - (void)application:(UIApplication ​*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *​)deviceToken { 29 | // notify AppsFlyerLib 30 | [[AppsFlyerLib shared] registerUninstall:deviceToken]; 31 | } 32 | ``` 33 | 34 | #### Second method 35 | 36 | You can register the uninstall token with AppsFlyer by calling the following API with your uninstall token: 37 | ```dart 38 | appsFlyerSdk.updateServerUninstallToken("token"); 39 | ``` 40 | 41 | ### Android 42 | 43 | It is possible to utilize the [Firebase Messaging Plugin for Flutter](https://pub.dev/packages/firebase_messaging) for everything related to the uninstall token. 44 | You can read more about Android Uninstall Measurement in our [knowledge base](https://support.appsflyer.com/hc/en-us/articles/4408933557137) and you can follow our guide for Uninstall measurement using FCM on our [DevHub](https://dev.appsflyer.com/hc/docs/uninstall-measurement-android). 45 | 46 | On the flutter side, you can register the uninstall token with AppsFlyer by calling the following API with your uninstall token: 47 | ```dart 48 | appsFlyerSdk.updateServerUninstallToken("token"); 49 | ``` 50 | 51 | --- 52 | 53 | ## User invite 54 | 55 | A complete list of supported parameters is available [here](https://support.appsflyer.com/hc/en-us/articles/115004480866-User-Invite-Tracking), you can also make use of the `customParams` field to include custom parameters of your choice. 56 | 57 | 1. First define the Onelink ID either in the AppsFlyerOptions, or in the setAppInviteOneLinkID API (find it in the AppsFlyer dashboard in the onelink section): 58 | 59 | **`Future setAppInviteOneLinkID(String oneLinkID, Function callback)`** 60 | 61 | 2. Utilize the AppsFlyerInviteLinkParams class to set the query params in the user invite link: 62 | ```dart 63 | class AppsFlyerInviteLinkParams { 64 | final String channel; 65 | final String campaign; 66 | final String referrerName; 67 | final String referrerImageUrl; 68 | final String customerID; 69 | final String baseDeepLink; 70 | final String brandDomain; 71 | final Map? customParams; 72 | } 73 | ``` 74 | 75 | 3. Call the generateInviteLink API to generate the user invite link. Use the success and error callbacks for handling. 76 | 77 | **Full example:** 78 | ```dart 79 | // Setting the OneLinkID 80 | appsFlyerSdk.setAppInviteOneLinkID('OnelinkID', 81 | (res){ 82 | print("setAppInviteOneLinkID callback: $res"); 83 | }); 84 | 85 | // Creating the required parameters of the OneLink 86 | AppsFlyerInviteLinkParams inviteLinkParams = new AppsFlyerInviteLinkParams( 87 | channel: "", 88 | referrerName: "", 89 | baseDeepLink: "", 90 | brandDomain: "", 91 | customerID: "", 92 | referrerImageUrl: "", 93 | campaign: "", 94 | customParams: {"key":"value"} 95 | ); 96 | 97 | // Generating the OneLink 98 | appsFlyerSdk.generateInviteLink(inviteLinkParams, 99 | (result){ 100 | print(result); 101 | }, 102 | (error){ 103 | print(error); 104 | } 105 | ); 106 | ``` 107 | 108 | --- 109 | 110 | ### In-app purchase validation 111 | Receipt validation is a secure mechanism whereby the payment platform (e.g. Apple or Google) validates that an in-app purchase indeed occurred as reported.
112 | Learn more - https://support.appsflyer.com/hc/en-us/articles/207032106-Receipt-validation-for-in-app-purchases
113 | 114 | There are two different functions, one for iOS and one for Android: 115 | 116 | **Android:** 117 | ```dart 118 | Future validateAndLogInAppAndroidPurchase( 119 | String publicKey, 120 | String signature, 121 | String purchaseData, 122 | String price, 123 | String currency, 124 | Map? additionalParameters) 125 | ``` 126 | Example: 127 | ```dart 128 | appsFlyerSdk.validateAndLogInAppAndroidPurchase( 129 | "publicKey", 130 | "signature", 131 | "purchaseData", 132 | "price", 133 | "currency", 134 | {"fs": "fs"}); 135 | ``` 136 | 137 | **iOS:** 138 | 139 | ❗Important❗ for iOS - set SandBox to ```true```
140 | ```appsFlyer.useReceiptValidationSandbox(true);``` 141 | 142 | ```dart 143 | Future validateAndLogInAppIosPurchase( 144 | String productIdentifier, 145 | String price, 146 | String currency, 147 | String transactionId, 148 | Map additionalParameters) 149 | ``` 150 | 151 | Example: 152 | ```dart 153 | appsFlyerSdk.validateAndLogInAppIosPurchase( 154 | "productIdentifier", 155 | "price", 156 | "currency", 157 | "transactionId", 158 | {"fs": "fs"}); 159 | ``` 160 | 161 | **Purchase validation callback:** 162 | 163 | `void onPurchaseValidation(Function callback)` 164 | 165 | Example: 166 | ```dart 167 | appsflyerSdk.onPurchaseValidation((res){ 168 | print("res: " + res.toString()); 169 | }); 170 | ``` 171 | 172 | --- 173 | 174 | ##
Android Out of Store 175 | Please make sure to go over [this guide](https://support.appsflyer.com/hc/en-us/articles/207447023-Attributing-out-of-store-Android-markets-guide) to get a general understanding of how out of store attribution is set up in AppsFlyer, and how to implement it. 176 | 177 | --- 178 | 179 | ## Set plugin for IOS 14 180 | 181 | 1. Adding the conset dialog: 182 | 183 | There are 2 ways to add it to your app: 184 | 185 | a. Utilize the following Library: https://pub.dev/packages/app_tracking_transparency 186 | 187 | Or 188 | 189 | b. Add native implementation: 190 | 191 | 192 | - Add `#import ` in your `AppDelegate.m` 193 | 194 | - Add the ATT pop-up for IDFA collection so your `AppDelegate.m` will look like this: 195 | 196 | ``` 197 | - (void)applicationDidBecomeActive:(nonnull UIApplication *)application { 198 | if (@available(iOS 14, *)) { 199 | [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) { 200 | // native code here 201 | }]; 202 | } 203 | } 204 | ``` 205 | 206 | 2. Add Privacy - Tracking Usage Description inside your `.plist` file in Xcode. 207 | 208 | ``` 209 | NSUserTrackingUsageDescription 210 | This identifier will be used to deliver personalized ads to you. 211 | ``` 212 | 213 | 3. Optional: Set the `timeToWaitForATTUserAuthorization` property in the `AppsFlyerOptions` to delay the sdk initazliation for a number of `x seconds` until the user accept the consent dialog: 214 | 215 | ```dart 216 | AppsFlyerOptions options = AppsFlyerOptions( 217 | afDevKey: DotEnv().env["DEV_KEY"], 218 | appId: DotEnv().env["APP_ID"], 219 | showDebug: true, 220 | timeToWaitForATTUserAuthorization: 30 221 | ); 222 | ``` 223 | 224 | For more info visit our [Full Support guide for iOS 14](https://support.appsflyer.com/hc/en-us/articles/207032066#integration-33-configuring-app-tracking-transparency-att-support). -------------------------------------------------------------------------------- /doc/BasicIntegration.md: -------------------------------------------------------------------------------- 1 | # 🚀 Basic integration of the SDK 2 | 3 | Initialize the SDK to enable AppsFlyer to detect installations, sessions (app opens) and updates. 4 | `AppsflyerSdk` receives either a Map with the defined parameters or an `AppsFlyerOptions` object. 5 | 6 | ```dart 7 | import 'package:appsflyer_sdk/appsflyer_sdk.dart'; 8 | 9 | AppsFlyerOptions appsFlyerOptions = AppsFlyerOptions( 10 | afDevKey: afDevKey, 11 | appId: appId, 12 | showDebug: true, 13 | timeToWaitForATTUserAuthorization: 50, // for iOS 14.5 14 | appInviteOneLink: oneLinkID, // Optional field 15 | disableAdvertisingIdentifier: false, // Optional field 16 | disableCollectASA: false, //Optional field 17 | manualStart: true, ); // Optional field 18 | 19 | AppsflyerSdk appsflyerSdk = AppsflyerSdk(appsFlyerOptions); 20 | ``` 21 | 22 | | Setting | Type | Description | 23 | |-----------------------------------| -------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 24 | | devKey | String | Your application's [devKey](https://support.appsflyer.com/hc/en-us/articles/207032066-Basic-SDK-integration-guide#retrieving-the-dev-key) provided by AppsFlyer (required) | 25 | | appId | String | Your application's [App ID](https://support.appsflyer.com/hc/en-us/articles/207377436-Adding-a-new-app#available-in-the-app-store-google-play-store-windows-phone-store) (required for iOS only) that you configured in your AppsFlyer dashboard should be without the 'id' prefix | 26 | | showDebug | bool | Debug mode - set to `true` for testing only, do not release to production with this parameter set to `true`! | 27 | | timeToWaitForATTUserAuthorization | double | Delays the SDK start for x seconds until the user either accepts the consent dialog, declines it, or the timer runs out. | 28 | | appInviteOneLink | String | The [OneLink template ID](https://support.appsflyer.com/hc/en-us/articles/115004480866-User-invite-attribution#parameters) that is used to generate a User Invite, this is not a required field in the `AppsFlyerOptions`, you may choose to set it later via the appropriate API. | 29 | | disableAdvertisingIdentifier | bool | Opt-out of the collection of Advertising Identifiers, which include OAID, AAID, GAID and IDFA. | 30 | | disableCollectASA | bool | Opt-out of the Apple Search Ads attributions. | 31 | | manualStart | bool | Prevents from the SDK from sending the launch request after using appsFlyer.initSdk(...). When using this property, the apps needs to manually trigger the appsFlyer.startSdk() API to report the app launch. | 32 | 33 | The next step is to call `initSdk` which have the optional boolean parameters `registerConversionDataCallback` and the deeplink callbacks: `registerOnAppOpenAttributionCallback` 34 | `registerOnDeepLinkingCallback`. 35 | > These are **all set to false by default**, meaning listeners will only be registered if you explicitly pass true. 36 | 37 | > Please keep in mind that registering the `registerOnDeepLinkingCallback` will override the `registerOnAppOpenAttributionCallback`, as the latter is a Legacy callback used for direct deep-linking, please read more about this in our DeepLinking guide. 38 | 39 | After we call `initSdk` we can use all of AppsFlyer SDK features. 40 | Here’s an example of how to register all three: 41 | ```dart 42 | await appsflyerSdk.initSdk( 43 | registerConversionDataCallback: true, 44 | registerOnAppOpenAttributionCallback: true, 45 | registerOnDeepLinkingCallback: true 46 | ); 47 | ``` 48 | 49 | | Setting | Description | 50 | | -------- | ------------- | 51 | | registerConversionDataCallback | Set a listener for the [GCD](https://dev.appsflyer.com/hc/docs/conversion-data) response, it is also the callback used for the [Legacy deferred deeplinking](https://dev.appsflyer.com/hc/docs/android-legacy-apis#deferred-deep-linking) | 52 | | registerOnAppOpenAttributionCallback | Set a listener for the [Legacy direct deeplinking](https://dev.appsflyer.com/hc/docs/android-legacy-apis) response | 53 | | registerOnDeepLinkingCallback | Set a listener for the [UDL](https://dev.appsflyer.com/hc/docs/unified-deep-linking-udl) response | 54 | 55 | ### startSdk 56 | `startSDK({RequestSuccessListener? onSuccess, RequestErrorListener? onError})` 57 | Version 6.13.0+ of the AppsFlyer Flutter plugin introduces the option to manually start the SDK.
58 | To utilise this feature, set the property `manualStart: true` within the initialization configuration.
59 | Once the `manualStart` option is activated, you can call `appsFlyer.startSdk()` at your discretion. If the `manualStart` property is omitted or set to false, the SDK will start immediately after calling `appsFlyer.initSdk(...)`. 60 | 61 | `onSuccess`: An optional callback that is triggered after a successful initialization of the SDK. 62 | `onError`: An optional callback that is fired in case of an error during SDK initialization, providing an error code and an error message. 63 | 64 | ```dart 65 | // SDK Options 66 | final AppsFlyerOptions options = AppsFlyerOptions( 67 | afDevKey: "", 68 | appId: "", 69 | showDebug: true, 70 | timeToWaitForATTUserAuthorization: 15, 71 | manualStart: true); 72 | _appsflyerSdk = AppsflyerSdk(options); 73 | 74 | // Initialization of the AppsFlyer SDK 75 | _appsflyerSdk.initSdk( 76 | registerConversionDataCallback: true, 77 | registerOnAppOpenAttributionCallback: true, 78 | registerOnDeepLinkingCallback: true); 79 | 80 | // Starting the SDK with optional success and error callbacks 81 | _appsflyerSdk.startSDK( 82 | onSuccess: () { 83 | showMessage("AppsFlyer SDK initialized successfully."); 84 | }, 85 | onError: (int errorCode, String errorMessage) { 86 | showMessage("Error initializing AppsFlyer SDK: Code $errorCode - $errorMessage"); 87 | }, 88 | ); 89 | ``` 90 | 91 | Use the `onSuccess` callback to perform actions after successful SDK initialization, and the `onError` callback to handle initialization errors.
92 | Here's an example from the demo app. 93 | 94 | ```dart 95 | _appsflyerSdk.startSDK( 96 | onSuccess: () { 97 | showMessage("AppsFlyer SDK initialized successfully."); 98 | }, 99 | onError: (int errorCode, String errorMessage) { 100 | showMessage("Error initializing AppsFlyer SDK: Code $errorCode - $errorMessage"); 101 | }, 102 | ); 103 | ``` -------------------------------------------------------------------------------- /doc/DMA.md: -------------------------------------------------------------------------------- 1 | # Set Consent For DMA Compliance 2 | 3 | Following the DMA regulations that were set by the European Commission, Google (and potentially other SRNs in the future) require to send them the user's consent data in order to interact with them during the attribution process. In our latest plugin update (6.16.2), we've introduced two new public APIs, enhancing our support for user consent and data collection preferences in line with evolving digital market regulations. 4 | There are two alternative ways for gathering consent data: 5 | 6 | - Through a Consent Management Platform (CMP): If the app uses a CMP that complies with the Transparency and Consent Framework (TCF) v2.2 protocol, the SDK can automatically retrieve the consent details. 7 | ### OR 8 | - Through a dedicated SDK API: Developers can pass Google's required consent data directly to the SDK using a specific API designed for this purpose. 9 | 10 | ## Use CMP to collect consent data 11 | A CMP compatible with TCF v2.2 collects DMA consent data and stores it in NSUserDefaults (iOS) and SharedPreferences (Android). To enable the SDK to access this data and include it with every event, follow these steps: 12 | 1. Call `appsflyerSdk.enableTCFDataCollection(true)` 13 | 2. Initialize the SDK in manual start mode by setting `manualStart: true` in the `AppsFlyerOptions` when creating the AppsflyerSdk instance. 14 | 3. Use the CMP to decide if you need the consent dialog in the current session to acquire the consent data. If you need the consent dialog move to step 4, otherwise move to step 5. 15 | 4. Get confirmation from the CMP that the user has made their consent decision and the data is available in NSUserDefaults/SharedPreferences. 16 | 5. Call `appsflyerSdk.startSDK()` 17 | 18 | ```dart 19 | // Initialize AppsFlyerOptions with manualStart: true 20 | final AppsFlyerOptions options = AppsFlyerOptions( 21 | afDevKey: 'your_dev_key', 22 | appId: '1234567890', // Required for iOS only 23 | showDebug: true, 24 | manualStart: true // <--- Manual Start 25 | ); 26 | 27 | // Create the AppsflyerSdk instance 28 | AppsflyerSdk appsflyerSdk = AppsflyerSdk(options); 29 | 30 | // Initialize the SDK 31 | appsflyerSdk.initSdk( 32 | registerConversionDataCallback: true, 33 | registerOnAppOpenAttributionCallback: true, 34 | registerOnDeepLinkingCallback: true 35 | ); 36 | 37 | // CMP pseudocode procedure 38 | if (cmpManager.hasConsent()) { 39 | appsflyerSdk.startSDK(); 40 | } else { 41 | cmpManager.presentConsentDialogToUser() 42 | .then((_) => appsflyerSdk.startSDK()); 43 | } 44 | ``` 45 | 46 | ## Manually collect consent data 47 | ### setConsentData is now **deprecated**. use
setConsentDataV2 48 | If your app does not use a CMP compatible with TCF v2.2, use the SDK API detailed below to provide the consent data directly to the SDK, distinguishing between cases when GDPR applies or not. 49 | 50 | ### When GDPR applies to the user 51 | If GDPR applies to the user, perform the following: 52 | 53 | 1. Given that GDPR is applicable to the user, determine whether the consent data is already stored for this session. 54 | 1. If there is no consent data stored, show the consent dialog to capture the user consent decision. 55 | 2. If there is consent data stored continue to the next step. 56 | 2. To transfer the consent data to the SDK create an AppsFlyerConsent object using `forGDPRUser` method that accepts the following parameters:
57 | `hasConsentForDataUsage: boolean` - Indicates whether the user has consented to use their data for advertising purposes.
58 | `hasConsentForAdsPersonalization: boolean` - Indicates whether the user has consented to use their data for personalized advertising. 59 | 3. Call `appsflyerSdk.setConsentData(consentData)` with the AppsFlyerConsent object. 60 | 4. Initialize the SDK using `appsflyerSdk.initSdk()`. 61 | 62 | ```dart 63 | // If the user is subject to GDPR - collect the consent data 64 | // or retrieve it from the storage 65 | // ... 66 | 67 | // Set the consent data to the SDK: 68 | var gdprConsent = AppsFlyerConsent.forGDPRUser( 69 | hasConsentForDataUsage: true, 70 | hasConsentForAdsPersonalization: false 71 | ); 72 | 73 | appsflyerSdk.setConsentData(gdprConsent); 74 | 75 | // Initialize AppsFlyerOptions 76 | final AppsFlyerOptions options = AppsFlyerOptions( 77 | afDevKey: 'your_dev_key', 78 | appId: '1234567890', // Required for iOS only 79 | showDebug: true 80 | ); 81 | 82 | // Create the AppsflyerSdk instance 83 | AppsflyerSdk appsflyerSdk = AppsflyerSdk(options); 84 | 85 | // Initialize the SDK 86 | appsflyerSdk.initSdk( 87 | registerConversionDataCallback: true, 88 | registerOnAppOpenAttributionCallback: true, 89 | registerOnDeepLinkingCallback: true 90 | ); 91 | ``` 92 | 93 | ### When GDPR does not apply to the user 94 | 95 | If GDPR doesn't apply to the user perform the following: 96 | 1. Create an AppsFlyerConsent object using `nonGDPRUser` method that doesn't accept any parameters. 97 | 2. Call `appsflyerSdk.setConsentData(consentData)` with the AppsFlyerConsent object. 98 | 3. Initialize the SDK using `appsflyerSdk.initSdk()`. 99 | 100 | ```dart 101 | // If the user is not subject to GDPR: 102 | var nonGdprUserConsentData = AppsFlyerConsent.nonGDPRUser(); 103 | 104 | appsflyerSdk.setConsentData(nonGdprUserConsentData); 105 | 106 | // Initialize AppsFlyerOptions 107 | final AppsFlyerOptions options = AppsFlyerOptions( 108 | afDevKey: 'your_dev_key', 109 | appId: '1234567890', // Required for iOS only 110 | showDebug: true 111 | ); 112 | 113 | // Create the AppsflyerSdk instance 114 | AppsflyerSdk appsflyerSdk = AppsflyerSdk(options); 115 | 116 | // Initialize the SDK 117 | appsflyerSdk.initSdk( 118 | registerConversionDataCallback: true, 119 | registerOnAppOpenAttributionCallback: true, 120 | registerOnDeepLinkingCallback: true 121 | ); 122 | ``` 123 | 124 | ## setConsentDataV2 (Recommended API for Manual Consent Collection) - since 6.16.2 125 | 🚀 **Why Use setConsentDataV2?**
126 | The setConsentDataV2 API is the new and improved way to manually provide user consent data to the AppsFlyer SDK. 127 | 128 | It replaces the now deprecated setConsentData method, offering several improvements:
129 | ✅ **Simpler and More Intuitive:** Accepts named parameters, making it easier to manage.
130 | ✅ **Includes an Additional Consent Parameter:** Now supports hasConsentForAdStorage to give users more granular control over their data.
131 | ✅ **Enhanced Clarity**: Allows nullable boolean values, indicating when users have not provided consent instead of forcing defaults.
132 | ✅ **Future-Proof:** Designed to be aligned with evolving privacy regulations and best practices.
133 | 134 | If your app previously used setConsentData, it is highly recommended to migrate to setConsentDataV2 for a more flexible and robust solution. 135 | 136 | 📌 **API Reference** 137 | ```dart 138 | void setConsentDataV2({ 139 | bool? isUserSubjectToGDPR, 140 | bool? consentForDataUsage, 141 | bool? consentForAdsPersonalization, 142 | bool? hasConsentForAdStorage 143 | }) 144 | ``` 145 | 146 | **Parameters** 147 | | Parameter | Type | Description | 148 | | -------- | -------- | -------- | 149 | | isUserSubjectToGDPR | bool? | Indicates if the user is subject to GDPR regulations. | 150 | | consentForDataUsage | bool? | Determines if the user consents to data usage. | 151 | | consentForAdsPersonalization | bool? | Determines if the user consents to personalized ads. | 152 | | hasConsentForAdStorage | bool? | **(New!)** Determines if the user consents to storing ad-related data.| 153 | 154 | - If a parameter is `null`, it means the user has **not explicitly provided consent** for that option. 155 | - These values should be collected from the user via an appropriate **UI or consent prompt** before calling this method. 156 | 157 | 📌 **Example Usage** 158 | ```dart 159 | // Initialize AppsFlyerOptions with manualStart: true 160 | final AppsFlyerOptions options = AppsFlyerOptions( 161 | afDevKey: 'your_dev_key', 162 | appId: '1234567890', // Required for iOS only 163 | showDebug: true, 164 | manualStart: true 165 | ); 166 | 167 | // Create the AppsflyerSdk instance 168 | AppsflyerSdk appsflyerSdk = AppsflyerSdk(options); 169 | 170 | // Set consent data BEFORE initializing the SDK 171 | appsflyerSdk.setConsentDataV2( 172 | isUserSubjectToGDPR: true, 173 | consentForDataUsage: true, 174 | consentForAdsPersonalization: false, 175 | hasConsentForAdStorage: null // User has not explicitly provided consent 176 | ); 177 | 178 | // Initialize the SDK 179 | appsflyerSdk.initSdk( 180 | registerConversionDataCallback: true, 181 | registerOnAppOpenAttributionCallback: true, 182 | registerOnDeepLinkingCallback: true 183 | ); 184 | 185 | // Start the SDK 186 | appsflyerSdk.startSDK(); 187 | ``` 188 | 📌 **Notes**
189 | • You should call this method **before initializing the AppsFlyer SDK** if possible, or at least before `startSDK()` when using manual initialization.
190 | • Ensure you collect consent **legally and transparently** from the user before passing these values. 191 | -------------------------------------------------------------------------------- /doc/DeepLink.md: -------------------------------------------------------------------------------- 1 | # Deep linking 2 | 3 | Deep Linking vs Deferred Deep Linking: 4 | 5 | A deep link is a special URL that routes to a specific spot, whether that’s on a website or in an app. A “mobile deep link” then, is a link that contains all the information needed to take a user directly into an app or a particular location within an app instead of just launching the app’s home page. 6 | 7 | If the app is installed on the user's device - the deep link routes them to the correct location in the app. But what if the app isn't installed? This is where Deferred Deep Linking is used. When the app isn't installed, clicking on the link routes the user to the store to download the app. Deferred Deep linking defer or delay the deep linking process until after the app has been downloaded, and ensures that after they install, the user gets to the right location in the app. 8 | 9 | [Android and iOS set-up](#setup) 10 | 11 | ![alt text](https://massets.appsflyer.com/wp-content/uploads/2018/03/21101417/app-installed-Recovered.png "") 12 | 13 | 14 | #### The 3 Deep Linking Types: 15 | Since users may or may not have the mobile app installed, there are 2 types of deep linking (Deferred + Direct DeepLinking Legacy APIs or Unified Deep Linking): 16 | 17 | 1. Deferred Deep Linking - Legacy API, serving personalized content to new or former users, directly after the installation. 18 | 2. Direct Deep Linking - Legacy API, directly serving personalized content to existing users, which already have the mobile app installed. 19 | 3. Unified Deep Linking - Starting from v6.1.3, the new Unified Deep Linking API is available to handle deeplinking logic. 20 | 21 | In general, you should utilize either **both** of the legacy methods for deep linking, or only the Unified Deep Linking. 22 | 23 | For more info please check out the [OneLink™ Deep Linking Guide](https://support.appsflyer.com/hc/en-us/articles/208874366-OneLink-Deep-Linking-Guide#Intro). 24 | 25 | --- 26 | 27 | ### 1. Deferred Deep Linking (Get Conversion Data) 28 | 29 | Check out the deferred deeplinking guide from the AppFlyer knowledge base [here](https://support.appsflyer.com/hc/en-us/articles/207032096-Accessing-AppsFlyer-Attribution-Conversion-Data-from-the-SDK-Deferred-Deeplinking-#Introduction). 30 | 31 | Code sample to handle the `onInstallConversionData`: 32 | 33 | ```dart 34 | appsflyerSdk.onInstallConversionData((res){ 35 | print("res: " + res.toString()); 36 | }); 37 | ``` 38 | 39 | **Note:** The code implementation for `onInstallConversionData` must be made **prior to the initialization** code of the SDK. 40 | 41 | --- 42 | 43 | ### 2. Direct Deeplinking 44 | 45 | When a deeplink is clicked on the device the AppsFlyer SDK will return the resolved link in the [onAppOpenAttribution](https://support.appsflyer.com/hc/en-us/articles/208874366-OneLink-Deep-Linking-Guide#deep-linking-data-the-onappopenattribution-method-) method. 46 | 47 | Code sample to handle `OnAppOpenAttribution`: 48 | 49 | ```dart 50 | appsflyerSdk.onAppOpenAttribution((res){ 51 | print("res: " + res.toString()); 52 | }); 53 | ``` 54 | 55 | **Note:** The code implementation for `onAppOpenAttribution` must be made **prior to the initialization** code of the SDK. 56 | 57 | --- 58 | 59 | ### 3. Unified deep linking 60 | 61 | > 📘 **UDL privacy protection** 62 | > 63 | > For new users, the UDL method only returns parameters relevant to deferred deep linking: `deep_link_value` and `deep_link_sub1` to `deep_link_sub10`. If you try to get any other parameters (`media_source`, `campaign`, `af_sub1-5`, etc.), they return `null`. 64 | 65 | The flow works as follows: 66 | 67 | 1. User clicks the OneLink short URL. 68 | 2. The iOS Universal Links/ Android App Links (for deep linking) or the deferred deep link, triggers the SDK. 69 | 3. The SDK triggers the didResolveDeepLink method, and passes the deep link result object to the user. 70 | 4. The onDeepLinking method uses the deep link result object that includes the deep_link_value and other parameters to create the personalized experience for the users, which is the main goal of OneLink. 71 | 72 | > Check out the Unified Deep Linking docs for [Android](https://dev.appsflyer.com/docs/android-unified-deep-linking) and [iOS](https://dev.appsflyer.com/docs/ios-unified-deep-linking). 73 | 74 | Considerations: 75 | 76 | * Requires AppsFlyer Android SDK V6.1.3 or later. 77 | * Does not support SRN campaigns. 78 | * Does not provide af_dp in the API response. 79 | * `onAppOpenAttribution` will not be called. All code should migrate to `onDeepLinking`. 80 | 81 | **Note:** The code implementation for `onDeepLinking` must be made **prior to the initialization** code of the SDK. 82 | 83 | Code sample to handle `onDeepLinking`: 84 | 85 | ```dart 86 | appsflyerSdk.onDeepLinking((DeepLinkResult dp) { 87 | switch (dp.status) { 88 | case Status.FOUND: 89 | print(dp.deepLink?.toString()); 90 | print("deep link value: ${dp.deepLink?.deepLinkValue}"); 91 | break; 92 | case Status.NOT_FOUND: 93 | print("deep link not found"); 94 | break; 95 | case Status.ERROR: 96 | print("deep link error: ${dp.error}"); 97 | break; 98 | case Status.PARSE_ERROR: 99 | print("deep link status parsing error"); 100 | break; 101 | } 102 | } 103 | ``` 104 | 105 | From version v6.4.0 a Unified deeplinking class was addded. You may use the following class to handle the deeplink: 106 | 107 | ```dart 108 | class DeepLink { 109 | 110 | DeepLink(this._clickEvent); 111 | final Map _clickEvent; 112 | Map get clickEvent => _clickEvent; 113 | String? get deepLinkValue => _clickEvent["deep_link_value"] as String; 114 | String? get matchType => _clickEvent["match_type"] as String; 115 | String? get clickHttpReferrer => _clickEvent["click_http_referrer"] as String; 116 | String? get mediaSource => _clickEvent["media_source"] as String; 117 | String? get campaign => _clickEvent["campaign"] as String; 118 | String? get campaignId => _clickEvent["campaign_id"] as String; 119 | String? get afSub1 => _clickEvent["af_sub1"] as String; 120 | String? get afSub2 => _clickEvent["af_sub2"] as String; 121 | String? get afSub3 => _clickEvent["af_sub3"] as String; 122 | String? get afSub4 => _clickEvent["af_sub4"] as String; 123 | String? get afSub5 => _clickEvent["af_sub5"] as String; 124 | bool get isDeferred => _clickEvent["is_deferred"] as bool; 125 | 126 | @override 127 | String toString() { 128 | return 'DeepLink: ${jsonEncode(_clickEvent)}'; 129 | } 130 | String? getStringValue(String key) { 131 | return _clickEvent[key] as String; 132 | } 133 | } 134 | ``` 135 | 136 | --- 137 | 138 | # Set-up 139 | 140 | ### Android Deeplink Setup 141 | 142 | #### URI Scheme 143 | In your app’s manifest add the following intent-filter to your relevant activity: 144 | ```xml 145 | 146 | 147 | 148 | 149 | 150 | 151 | ``` 152 | 153 | --- 154 | 155 | #### App Links 156 | For more on App Links check out the guide [here](https://support.appsflyer.com/hc/en-us/articles/115005314223-Deep-Linking-Users-with-Android-App-Links#what-are-android-app-links). 157 | 158 | In your app's manifest add the following intent-filter to your relevant activity: 159 | ```xml 160 | 161 | 162 | 163 | 164 | 165 | 168 | 169 | ``` 170 | 171 | --- 172 | 173 | #### onNewIntent 174 | 175 | **❗Setting the intent this way is not required from v6.4.0 and above❗** 176 | **❗If you are using a plugin version higher or equal to v6.4.0, ignore this section❗** 177 | 178 | **NOTE:** On Android, AppsFlyer SDK inspects the activity intent object during onResume(). Because of that, for each activity that may be configured or launched with any [non-standard launch mode](https://developer.android.com/guide/topics/manifest/activity-element#lmode) please make sure to add the following code to `MainActivity.java` in `android/app/src/main/java/com...` 179 | 180 | Java example: 181 | ```java 182 | @Override 183 | public void onNewIntent(Intent intent) { 184 | super.onNewIntent(intent); 185 | setIntent(intent); 186 | } 187 | ``` 188 | 189 | Kotlin example: 190 | ``` 191 | override fun onNewIntent(intent : Intent){ 192 | super.onNewIntent(intent) 193 | setIntent(intent) 194 | } 195 | ``` 196 | 197 | ✏️✏️ 198 | 199 | ### iOS Deeplink Setup 200 | 201 | For more on Universal Links check out the guide [here](https://support.appsflyer.com/hc/en-us/articles/208874366-OneLink-Deep-Linking-Guide#setups-universal-links). 202 | 203 | Essentially, the Universal Links method links between an iOS mobile app and an associate website/domain, such as AppsFlyer’s OneLink domain (xxx.onelink.me). To do so, it is required to: 204 | 205 | 1. Configure OneLink sub-domain and link to the mobile app (by hosting the ‘apple-app-site-association’ file - AppsFlyer takes care of this part in the onelink setup on your dashboard) 206 | 2. Configure the mobile app to register approved domains: 207 | 208 | ```xml 209 | 210 | 211 | 212 | 213 | com.apple.developer.associated-domains 214 | 215 | applinks:test.onelink.me 216 | 217 | 218 | 219 | ``` 220 | 221 | #### URI Scheme 222 | 223 | Add your URI Scheme in the project's settings under "General" -> "URL Types" -> Add a new "URI Scheme". 224 | 225 | **❗Adding the following URI Scheme code is not required from v6.4.0 and above❗** 226 | **❗If you are using a plugin version higher or equal to v6.4.0, ignore the rest of this section❗** 227 | 228 | Add the following to your `AppDelegate`: 229 | 230 | Objective-C example: 231 | 232 | ``` 233 | - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation { 234 | // Only for AppsFlyer SDK version 6.2.0 and above 235 | [[AppsFlyerAttribution shared] handleOpenUrl:url sourceApplication:sourceApplication annotation:annotation]; 236 | 237 | // Only for AppsFlyer SDK version 6.1.0 and below 238 | [[AppsFlyerLib shared] handleOpenURL:url sourceApplication:sourceApplication withAnnotation:annotation]; 239 | return YES; 240 | } 241 | 242 | // Reports app open from deep link for iOS 10 243 | - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *) options { 244 | 245 | // Only for AppsFlyer SDK version 6.2.0 and above 246 | [[AppsFlyerAttribution shared] handleOpenUrl:url options:options]; 247 | 248 | // Only for AppsFlyer SDK version 6.1.0 and below 249 | [[AppsFlyerLib shared] handleOpenUrl:url options:options]; 250 | return YES; 251 | } 252 | ``` 253 | 254 | Swift example: 255 | 256 | ```swift 257 | override func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool { 258 | AppsFlyerAttribution.shared()!.handleOpenUrl(url, sourceApplication: sourceApplication, annotation: annotation); 259 | return true 260 | } 261 | 262 | override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { 263 | AppsFlyerAttribution.shared()!.handleOpenUrl(url, options: options) 264 | return true 265 | } 266 | ``` 267 | 268 | For more information on URI-Schemes check out the guide [here](https://support.appsflyer.com/hc/en-us/articles/208874366-OneLink-deep-linking-guide#setups-uri-scheme-for-ios-8-and-below). 269 | 270 | --- 271 | 272 | #### Universal Links 273 | 274 | **❗Adding the following Universal Links code is not required from v6.4.0 and above❗** 275 | **❗If you are using a plugin version higher or equal to v6.4.0, ignore the rest of this section❗** 276 | 277 | Objective-C example: 278 | 279 | ``` 280 | // Reports app open from a Universal Link for iOS 9 or above 281 | - (BOOL) application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray> *restorableObjects))restorationHandler { 282 | // AppsFlyer SDK version 6.2.0 and above 283 | [[AppsFlyerAttribution shared] continueUserActivity:userActivity restorationHandler:restorationHandler]; 284 | 285 | // AppsFlyer SDK version 6.1.0 and below 286 | [[AppsFlyerLib shared] continueUserActivity:userActivity restorationHandler:restorationHandler]; 287 | return YES; 288 | } 289 | ``` 290 | 291 | Swift example: 292 | 293 | ```swift 294 | private func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool { 295 | AppsFlyerAttribution.shared()!.continueUserActivity(userActivity, restorationHandler: nil) 296 | return true 297 | } 298 | 299 | override func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { 300 | AppsFlyerAttribution.shared()!.continueUserActivity(userActivity, restorationHandler: nil) 301 | return true 302 | } 303 | ``` -------------------------------------------------------------------------------- /doc/Guides.md: -------------------------------------------------------------------------------- 1 | # Flutter AppsFlyer Plugin Guides 2 | 3 | 4 | 5 | ## Table of content 6 | 7 | - [Init SDK](#init-sdk) 8 | - [Android out of store](#out-of-store) 9 | - [Deep Linking](#deeplinking) 10 | - [Deferred Deep Linking (Get Conversion Data)](#deferred-deep-linking) 11 | - [Direct Deep Linking](#direct-deep-linking) 12 | - [Unified deep linking](#Unified-deep-linking) 13 | - [Android Deeplink Setup](#android-deeplinks) 14 | - [iOS Deeplink Setup](#iosdeeplinks) 15 | - [Example in swift](#Example-swift) 16 | - [Set plugin for IOS 14](#ios14) 17 | - [Setting strict mode (app for kids)](#strictMode) 18 | - [Uninstall feature](#uninstall) 19 | 20 | --- 21 | 22 | ## Init SDK 23 | 24 | To start using AppsFlyer you first need to create an instance of `AppsflyerSdk` before using any other of our sdk functionalities. 25 | 26 | `AppsflyerSdk` receives a map or `AppsFlyerOptions` object. This is how you can configure our `AppsflyerSdk` instance and connect it to your AppsFlyer account. 27 | 28 | *Example (using map):* 29 | ```dart 30 | import 'package:appsflyer_sdk/appsflyer_sdk.dart'; 31 | //.. 32 | 33 | AppsFlyerOptions appsFlyerOptions = { "afDevKey": afDevKey, 34 | "afAppId": appId, 35 | "isDebug": true}; 36 | 37 | AppsflyerSdk appsflyerSdk = AppsflyerSdk(appsFlyerOptions); 38 | ``` 39 | 40 | The next step is to call `initSdk` which have the optional boolean parameters `registerConversionDataCallback` and the deeplink callbacks: `registerOnAppOpenAttributionCallback` 41 | `registerOnDeepLinkingCallback` 42 | All callbacks are set to false as default. 43 | 44 | After we call `initSdk` we can use all of AppsFlyer SDK features. 45 | 46 | ```dart 47 | appsflyerSdk.initSdk( 48 | registerConversionDataCallback: true, 49 | registerOnAppOpenAttributionCallback: true, 50 | registerOnDeepLinkingCallback: true 51 | ); 52 | ``` 53 | 54 | --- 55 | 56 | ## Android Out of store 57 | Please make sure to go over [this guide](https://support.appsflyer.com/hc/en-us/articles/207447023-Attributing-out-of-store-Android-markets-guide) to get general understanding of how out of store attribution is set up in AppsFlyer. If the store you distribute the app through supports install referrer matching or requires the referrer in the postback, make sure to add the following to the AndroidManifest.xml: 58 | ```xml 59 | 60 | ... 61 | 62 | 63 | 64 | 65 | 66 | 67 | ``` 68 | 69 | --- 70 | 71 | ## Deep Linking 72 | 73 | 74 | 75 | #### The 3 Deep Linking Types: 76 | Since users may or may not have the mobile app installed, there are 2 types of deep linking: 77 | 78 | 1. Deferred Deep Linking - Serving personalized content to new or former users, directly after the installation. 79 | 2. Direct Deep Linking - Directly serving personalized content to existing users, which already have the mobile app installed. 80 | 3. Unified deep linking - Unified deep linking sends new and existing users to a specific in-app activity as soon as the app is opened. 81 | 82 | For more info please check out the [OneLink™ Deep Linking Guide](https://support.appsflyer.com/hc/en-us/articles/208874366-OneLink-Deep-Linking-Guide#Intro). 83 | 84 | ### 1. Deferred Deep Linking (Get Conversion Data) 85 | In order to use the unified deep link you need to send the `registerConversionDataCallback: true` flag inside the object that sent to the sdk. 86 | 87 | ======= 88 | Handle the Deferred deeplink in the following callback: 89 | ```dart 90 | appsflyerSdk.onInstallConversionData((res){ 91 | print("res: " + res.toString()); 92 | }); 93 | ``` 94 | 95 | Check out the deferred deeplinkg guide from the AppFlyer knowledge base [here](https://support.appsflyer.com/hc/en-us/articles/207032096-Accessing-AppsFlyer-Attribution-Conversion-Data-from-the-SDK-Deferred-Deeplinking-#Introduction) 96 | 97 | ### 2. Direct Deeplinking 98 | In order to use the unified deep link you need to send the `registerOnAppOpenAttributionCallback: true` flag inside the object that sent to the sdk. 99 | 100 | Handle the Direct deeplink in the following callback: 101 | 102 | ```dart 103 | appsflyerSdk.onAppOpenAttribution((res){ 104 | print("res: " + res.toString()); 105 | }); 106 | ``` 107 | 108 | When a deeplink is clicked on the device the AppsFlyer SDK will return the link in the [onAppOpenAttribution](https://support.appsflyer.com/hc/en-us/articles/208874366-OneLink-Deep-Linking-Guide#deep-linking-data-the-onappopenattribution-method-) method. 109 | 110 | ### 3. Unified deep linking 111 | In order to use the unified deep link you need to send the `registerOnDeepLinkingCallback: true` flag inside the object that sent to the sdk. 112 | **NOTE:** when sending this flag, the sdk will ignore `onAppOpenAttribution`! 113 | 114 | **Breaking changes!** 115 | 116 | From version v6.4.0 a Unified deeplinking class was addded. You can use the following class to handle the deeplink: 117 | 118 | ```dart 119 | class DeepLink{ 120 | 121 | DeepLink(this._clickEvent); 122 | final Map _clickEvent; 123 | Map get clickEvent => _clickEvent; 124 | String? get deepLinkValue => _clickEvent["deep_link_value"] as String; 125 | String? get matchType => _clickEvent["match_type"] as String; 126 | String? get clickHttpReferrer => _clickEvent["click_http_referrer"] as String; 127 | String? get mediaSource => _clickEvent["media_source"] as String; 128 | String? get campaign => _clickEvent["campaign"] as String; 129 | String? get campaignId => _clickEvent["campaign_id"] as String; 130 | String? get afSub1 => _clickEvent["af_sub1"] as String; 131 | String? get afSub2 => _clickEvent["af_sub2"] as String; 132 | String? get afSub3 => _clickEvent["af_sub3"] as String; 133 | String? get afSub4 => _clickEvent["af_sub4"] as String; 134 | String? get afSub5 => _clickEvent["af_sub5"] as String; 135 | bool get isDeferred => _clickEvent["is_deferred"] as bool; 136 | 137 | @override 138 | String toString() { 139 | return 'DeepLink: ${jsonEncode(_clickEvent)}'; 140 | } 141 | String? getStringValue(String key) { 142 | return _clickEvent[key] as String; 143 | } 144 | } 145 | ``` 146 | 147 | Example of handling both the Direct & the deferred deeplink in the following callback: 148 | 149 | ```dart 150 | _appsflyerSdk?.onDeepLinking((DeepLinkResult dp) { 151 | switch (dp.status) { 152 | case Status.FOUND: 153 | print(dp.deepLink?.toString()); 154 | print("deep link value: ${dp.deepLink?.deepLinkValue}"); 155 | break; 156 | case Status.NOT_FOUND: 157 | print("deep link not found"); 158 | break; 159 | case Status.ERROR: 160 | print("deep link error: ${dp.error}"); 161 | break; 162 | case Status.PARSE_ERROR: 163 | print("deep link status parsing error"); 164 | break; 165 | } 166 | } 167 | ``` 168 | 169 | For more information about this api, please check [OneLink Guide Here](https://dev.appsflyer.com/docs/android-unified-deep-linking) 170 | 171 | ### Android Deeplink Setup 172 | 173 | 174 | 175 | #### URI Scheme 176 | In your app’s manifest add the following intent-filter to your relevant activity: 177 | ```xml 178 | 179 | 180 | 181 | 182 | 183 | 184 | ``` 185 | 186 | **❗Not needed from v6.4.0 and above** 187 | 188 | **NOTE:** On Android, AppsFlyer SDK inspects activity intent object during onResume(). Because of that, for each activity that may be configured or launched with any [non-standard launch mode](https://developer.android.com/guide/topics/manifest/activity-element#lmode) the following code was added to `MainActivity.java` in `android/app/src/main/java/com...` 189 | 190 | Java: 191 | 192 | ```java 193 | @Override 194 | public void onNewIntent(Intent intent) { 195 | super.onNewIntent(intent); 196 | setIntent(intent); 197 | } 198 | ``` 199 | 200 | Kotlin: 201 | 202 | ``` 203 | override fun onNewIntent(intent : Intent){ 204 | super.onNewIntent(intent) 205 | setIntent(intent) 206 | } 207 | ``` 208 | 209 | 210 | #### App Links 211 | 212 | In your app’s manifest add the following intent-filter to your relevant activity: 213 | 214 | ```xml 215 | 216 | 217 | 218 | 219 | 220 | 223 | 224 | ``` 225 | 226 | For more on App Links check out the guide [here](https://support.appsflyer.com/hc/en-us/articles/115005314223-Deep-Linking-Users-with-Android-App-Links#what-are-android-app-links). 227 | 228 | 229 | ### iOS Deeplink Setup 230 | 231 | **❗Not needed from v6.4.0 and above** 232 | 233 | 234 | In order for the callback to be called: 235 | 236 | 1. Import AppsFlyer SDK: 237 | 238 | Objective C: 239 | 240 | a. For AppsFlyer SDK V6.2.0 and above add: 241 | 242 | ```#import "AppsflyerSdkPlugin.h"``` 243 | 244 | b. For AppsFlyer SDK V6.1.0 and below add: 245 | 246 | ```#import ``` 247 | 248 | Swift: 249 | 250 | Add ```import AppsFlyerLib``` in the `AppDelegate.swift` file. 251 | 252 | Add in the `Runner-Bridging-Header.h` one of the following lines: 253 | 254 | a. For AppsFlyer SDK V6.2.0 and above add: 255 | 256 | ```#import `` 257 | 258 | b. For AppsFlyer SDK V6.1.0 and below add: 259 | 260 | ```#import ``` 261 | 262 | 2. Set-up the following AppsFlyer API: 263 | 264 | ### URI Scheme 265 | 266 | 267 | Objective-C: 268 | 269 | ``` 270 | - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation { 271 | // AppsFlyer SDK version 6.2.0 and above 272 | [[AppsFlyerAttribution shared] handleOpenUrl:url sourceApplication:sourceApplication annotation:annotation]; 273 | 274 | // AppsFlyer SDK version 6.1.0 and below 275 | [[AppsFlyerLib shared] handleOpenURL:url sourceApplication:sourceApplication withAnnotation:annotation]; 276 | return YES; 277 | } 278 | 279 | // Reports app open from deep link for iOS 10 280 | - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *) options { 281 | 282 | // AppsFlyer SDK version 6.2.0 and above 283 | [[AppsFlyerAttribution shared] handleOpenUrl:url options:options]; 284 | 285 | // AppsFlyer SDK version 6.1.0 and below 286 | [[AppsFlyerLib shared] handleOpenUrl:url options:options]; 287 | return YES; 288 | } 289 | ``` 290 | 291 | Swift: 292 | 293 | ```swift 294 | override func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool { 295 | AppsFlyerAttribution.shared()!.handleOpenUrl(url, sourceApplication: sourceApplication, annotation: annotation); 296 | return true 297 | } 298 | 299 | override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { 300 | AppsFlyerAttribution.shared()!.handleOpenUrl(url, options: options) 301 | return true 302 | } 303 | ``` 304 | 305 | For more on URI-schemes check out the guide [here](https://support.appsflyer.com/hc/en-us/articles/208874366-OneLink-deep-linking-guide#setups-uri-scheme-for-ios-8-and-below) 306 | 307 | 308 | ### Universal Links 309 | 310 | **❗Not needed from v6.4.0 and above** 311 | 312 | Objective-C: 313 | 314 | ``` 315 | // Reports app open from a Universal Link for iOS 9 or above 316 | - (BOOL) application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray> *restorableObjects))restorationHandler { 317 | // AppsFlyer SDK version 6.2.0 and above 318 | [[AppsFlyerAttribution shared] continueUserActivity:userActivity restorationHandler:restorationHandler]; 319 | 320 | // AppsFlyer SDK version 6.1.0 and below 321 | [[AppsFlyerLib shared] continueUserActivity:userActivity restorationHandler:restorationHandler]; 322 | return YES; 323 | } 324 | ``` 325 | 326 | Swift: 327 | 328 | ```swift 329 | private func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool { 330 | AppsFlyerAttribution.shared()!.continueUserActivity(userActivity, restorationHandler: nil) 331 | return true 332 | } 333 | 334 | override func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { 335 | AppsFlyerAttribution.shared()!.continueUserActivity(userActivity, restorationHandler: nil) 336 | return true 337 | } 338 | ``` 339 | 340 | 341 | ###Example in swift:### 342 | 343 | 344 | 345 | `Runner-Bridging-Header.h` 346 | 347 | ``` 348 | #import "GeneratedPluginRegistrant.h" 349 | #import 350 | ``` 351 | 352 | 353 | `AppDelegate.swift` 354 | 355 | ```swift 356 | import UIKit 357 | import Flutter 358 | import AppsFlyerLib 359 | 360 | @UIApplicationMain 361 | @objc class AppDelegate: FlutterAppDelegate { 362 | override func application( 363 | _ application: UIApplication, 364 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 365 | ) -> Bool { 366 | GeneratedPluginRegistrant.register(with: self) 367 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 368 | } 369 | 370 | // Open URI-scheme for iOS 9 and above 371 | override func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool { 372 | NSLog("AppsFlyer [deep link]: Open URI-scheme for iOS 9 and above") 373 | AppsFlyerAttribution.shared()!.handleOpenUrl(url, sourceApplication: sourceApplication, annotation: annotation); 374 | return true 375 | } 376 | 377 | // Reports app open from deep link for iOS 10 or later 378 | override func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { 379 | NSLog("AppsFlyer [deep link]: continue userActivity") 380 | AppsFlyerAttribution.shared()!.continueUserActivity(userActivity, restorationHandler:nil ) 381 | return true 382 | } 383 | 384 | override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { 385 | NSLog("AppsFlyer [deep link]: Open URI-scheme options") 386 | 387 | AppsFlyerAttribution.shared()!.handleOpenUrl(url, options: options) 388 | return true 389 | } 390 | } 391 | ``` 392 | 393 | 394 | More on Universal Links: 395 | Essentially, the Universal Links method links between an iOS mobile app and an associate website/domain, such as AppsFlyer’s OneLink domain (xxx.onelink.me). To do so, it is required to: 396 | 397 | 1. Get your SHA256 fingerprint: 398 | 399 | a. [Creating A Keystore](https://flutter.dev/docs/deployment/android#create-a-keystore) (you'll eventually need to do this to release on the Play Store) 400 | 401 | b. [Generate Fingerprint](https://developers.google.com/android/guides/client-auth) 402 | 2. Configure OneLink sub-domain and link to mobile app in the AppsFlyer onelink setup on your dashboard, add the fingerprint there (AppsFlyer takes care of hosting the ‘apple-app-site-association’ file) 403 | 3. Configure the mobile app to register approved domains: 404 | 405 | ```xml 406 | 407 | 408 | 409 | 410 | com.apple.developer.associated-domains 411 | 412 | applinks:test.onelink.me 413 | 414 | 415 | 416 | ``` 417 | 418 | For more on Universal Links check out the guide [here](https://support.appsflyer.com/hc/en-us/articles/208874366-OneLink-Deep-Linking-Guide#setups-universal-links). 419 | 420 | 421 | ## Set plugin for IOS 14 422 | 423 | 1. Adding the conset dialog: 424 | 425 | There are 2 ways to add it to your app: 426 | 427 | a. Add the following Library: https://pub.dev/packages/app_tracking_transparency 428 | 429 | Or 430 | 431 | b. Add native implementation: 432 | 433 | 434 | - Add `#import ` in your `AppDelegate.m` 435 | 436 | - Add the ATT pop-up for IDFA collection so your `AppDelegate.m` will look like this: 437 | 438 | ``` 439 | - (void)applicationDidBecomeActive:(nonnull UIApplication *)application { 440 | if (@available(iOS 14, *)) { 441 | [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) { 442 | // native code here 443 | }]; 444 | } 445 | } 446 | ``` 447 | 448 | 449 | 450 | 2. Add Privacy - Tracking Usage Description inside your `.plist` file in Xcode. 451 | 452 | ``` 453 | NSUserTrackingUsageDescription 454 | This identifier will be used to deliver personalized ads to you. 455 | ``` 456 | 457 | 3. Optional: Set the `timeToWaitForATTUserAuthorization` property in the `AppsFlyerOptions` to delay the sdk initazliation for a number of `x seconds` until the user accept the consent dialog: 458 | 459 | ```dart 460 | AppsFlyerOptions options = AppsFlyerOptions( 461 | afDevKey: DotEnv().env["DEV_KEY"], 462 | appId: DotEnv().env["APP_ID"], 463 | showDebug: true, 464 | timeToWaitForATTUserAuthorization: 30 465 | ); 466 | ``` 467 | 468 | For more info visit our Full Support guide for iOS 14: 469 | 470 | https://support.appsflyer.com/hc/en-us/articles/207032066#integration-33-configuring-app-tracking-transparency-att-support 471 | 472 | --- 473 | 474 | ## 👨‍👩‍👧‍👦 Strict mode for App-kids 475 | 476 | Starting from version **6.2.4-nullsafety.5** iOS SDK comes in two variants: **Strict** mode and **Regular** mode. 477 | 478 | Please read more: https://support.appsflyer.com/hc/en-us/articles/207032066#integration-strict-mode-sdk 479 | 480 | ***Change to Strict mode*** 481 | 482 | After you [installed](#installation) the AppsFlyer plugin: 483 | 484 | 1. Go to the `$HOME/.pub-cache/hosted/pub.dartlang.org/appsflyer_sdk-/ios` folder 485 | 2. Open `appsflyer_sdk.podspec`, add `/Strict` to the `s.ios.dependency` as follow: 486 | 487 | `s.ios.dependency 'AppsFlyerFramework', '6.x.x'` To >> `s.ios.dependency 'AppsFlyerFramework/Strict', '6.x.x'` 488 | and save 489 | 490 | 3. Go to `ios` folder of your current project and Run `pod update` 491 | 492 | ***Change to Regular mode*** 493 | 494 | 1. Go to the `$HOME/.pub-cache/hosted/pub.dartlang.org/appsflyer_sdk-/ios` folder: 495 | 2. Open `appsflyer_sdk.podspec` and remove `/strict`: 496 | 497 | `s.ios.dependency 'AppsFlyerFramework/Strict', '6.x.x'` To >> `s.ios.dependency 'AppsFlyerFramework', '6.x.x'` 498 | and save 499 | 500 | 3. Go to `ios` folder of your current project and Run `pod update` 501 | 502 | --- 503 | 504 | ## Uninstall Feature 505 | 506 | Android: 507 | 508 | 1. Add Firebase messaging to your flutter app. You can use the Offical Firebase messagin package by Google: 509 | https://pub.dev/packages/firebase_messaging 510 | 2. Follow the native guide on implementing the Uninstall feature both on the Firebase plaform and the app: 511 | https://support.appsflyer.com/hc/en-us/articles/360017822118-Integrate-Android-uninstall-measurement-into-an-app 512 | 513 | iOS: 514 | 515 | 1. Follow the native iOS guide: 516 | 517 | https://support.appsflyer.com/hc/en-us/articles/360017822178-Integrate-iOS-uninstall-measurement-into-an-app- 518 | 519 | --- 520 | -------------------------------------------------------------------------------- /doc/InAppEvents.md: -------------------------------------------------------------------------------- 1 | # In-App events 2 | 3 | In-App Events provide insight on what is happening in your app. It is recommended to take the time and define the events you want to measure to allow you to measure ROI (Return on Investment) and LTV (Lifetime Value). 4 | 5 | Recording in-app events is performed by calling logEvent with event name and value parameters. See In-App Events documentation for more details. 6 | 7 | **Note:** An In-App Event name must be no longer than 45 characters. Events names with more than 45 characters do not appear in the dashboard, but only in the raw Data, Pull and Push APIs. 8 | Find more info about recording events [here](https://dev.appsflyer.com/hc/docs/in-app-events-sdk). 9 | 10 | --- 11 | 12 | ## logEvent 13 | 14 | ** `logEvent(String eventName, Map? eventValues)`** 15 | 16 | | parameter | type | description | 17 | | ----------- |----------|------------------------------------------ | 18 | | eventName | String | The event name, it is presented in your dashboard. | 19 | | eventValues | Map | The event values that are sent with the event. | 20 | 21 | **Example:** 22 | ```dart 23 | Future logEvent(String eventName, Map? eventValues) async { 24 | bool? result; 25 | try { 26 | result = await appsflyerSdk.logEvent(eventName, eventValues); 27 | } on Exception catch (e) {} 28 | print("Result logEvent: $result"); 29 | } 30 | ``` -------------------------------------------------------------------------------- /doc/Installation.md: -------------------------------------------------------------------------------- 1 | # Adding appsflyer-flutter-plugin to your project 2 | 3 | ## Installation 4 | 5 | Open the terminal of your chosen IDE and run the following: 6 | 7 | ``` 8 | flutter pub add appsflyer_sdk 9 | ``` 10 | 11 | This will download the AppsFlyer flutter plugin to your project, you may observe the changes in your `pubspec.yaml` file. 12 | 13 | --- 14 | ## Huawei Referrer 15 | Huawei Referrer is supported in SDK v6.14.0 and above. 16 | Due to changes in the Huawei AppGallery store, previous versions of the AppsFlyer SDK are not able to fetch the referrer from the store. [Learn more](https://dev.appsflyer.com/hc/docs/install-android-sdk#huawei-install-referrer). 17 | --- 18 | 19 | ## 👨‍👩‍👧‍👦 Strict mode for Kids Apps 20 | 21 | Starting from version **6.2.4-nullsafety.5**, the iOS SDK comes in two variants: **Strict** mode and **Regular** mode. 22 | Please read more: https://support.appsflyer.com/hc/en-us/articles/207032066#integration-strict-mode-sdk 23 | 24 | ***Change to Strict mode*** 25 | 26 | After you installed the AppsFlyer plugin: 27 | 1. Go to the `$HOME/.pub-cache/hosted/pub.dartlang.org/appsflyer_sdk-/ios` folder 28 | 2. Open `appsflyer_sdk.podspec`, add `/Strict` to the `s.ios.dependency` as follow: 29 | `s.ios.dependency 'AppsFlyerFramework', '6.x.x'` to `s.ios.dependency 'AppsFlyerFramework/Strict', '6.x.x'` 30 | and save. 31 | 32 | 3. Go to the `ios` folder of your current project and run `pod update`. 33 | 34 | ***Change to Regular mode*** 35 | 36 | After you installed the AppsFlyer plugin: 37 | 1. Go to the `$HOME/.pub-cache/hosted/pub.dartlang.org/appsflyer_sdk-/ios` folder: 38 | 2. Open `appsflyer_sdk.podspec` and remove `/Strict`: 39 | change `s.ios.dependency 'AppsFlyerFramework/Strict', '6.x.x'` to `s.ios.dependency 'AppsFlyerFramework', '6.x.x'` 40 | and save. 41 | 42 | 3. Go to the `ios` folder of your current project and run `pod update`. -------------------------------------------------------------------------------- /doc/Testing.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | More info about testing the SDK for marketers [here](https://support.appsflyer.com/hc/en-us/articles/360001559405-Test-mobile-SDK-integration-with-the-app#introduction). 4 | 5 | - [Testing for iOS](#iOS) 6 | - [Testing for Android](#Android) 7 | 8 | Before testing the SDK, you need to enable the debug mode so the SDK will produce the full logs. 9 | To enable it, set the appsFlyer options object with `showDebug` as `true`, and then initialize the SDK: 10 | 11 | ```dart 12 | AppsFlyerOptions appsFlyerOptions = AppsFlyerOptions( 13 | afDevKey: afDevKey, 14 | appId: appId, 15 | showDebug: true); 16 | 17 | AppsflyerSdk appsflyerSdk = AppsflyerSdk(appsFlyerOptions); 18 | 19 | appsflyerSdk.initSdk( 20 | registerConversionDataCallback: true, 21 | registerOnAppOpenAttributionCallback: true, 22 | registerOnDeepLinkingCallback: false 23 | ); 24 | ``` 25 | 26 | --- 27 | 28 | ## Testing for iOS 29 | 30 | Open your iOS project with XCode (`appName.xcworkspace`) and run it. In the logs section or in the console app, you will see logs related to AppsFlyer start with `[AppsFlyerSDK]`.
31 | Search for the launch event that looks like this: 32 | 33 | ``` 34 | <~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~~+~> 35 | <~+~ SEND Start: https://launches.appsflyer.com/api/v6.4/iosevent?app_id=7xXxXxX1&buildnumber=6.4.4 36 | <~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~~+~> 37 | { launch event payload } // Just an example of a JSON. you will see the full payload 38 | ``` 39 | and also: 40 | ``` 41 | Result: { 42 | data = {length = 64, bytes = 0x7b226f6c 5f696422 3a224476 5769222c ... 696e6b2e 6d65227d }; 43 | dataStr = "{\"oxXxXxd\":\"DXxXxi\",\"oXxXer\":ss,\"olXxXxain\":\"xXxXxXx\"}"; 44 | retries = 2; 45 | statusCode = 200; // ~~> success! 46 | taskIdentifier = 4; 47 | } 48 | ``` 49 | 50 | For more iOS integration tests, see [Here](https://dev.appsflyer.com/hc/docs/testing-ios) 51 | 52 | --- 53 | 54 | ##
Testing for Android 55 | 56 | Open your Android project with Android Studio (`android` folder) and run it. In the logcat, you will see logs related to AppsFlyer start with `I/AppsFlyer_x.x.x`.
57 | Search for the launch event that looks like this: 58 | 59 | ``` 60 | I/AppsFlyer_6.4.3: url: https://launches.appsflyer.com/api/v6.4/androidevent?app_id=com.aXxXxt.rxXxXxt&buildnumber=6.4.3 61 | I/AppsFlyer_6.4.3: data: { launch event payload } // Just an example of a JSON. you will see the full payload 62 | ``` 63 | and also: 64 | ``` 65 | I/AppsFlyer_6.4.3: response code: 200 // ~~> success! 66 | ``` 67 | 68 | For more Android integration tests, see [Here](https://dev.appsflyer.com/hc/docs/testing-android) -------------------------------------------------------------------------------- /example/.env: -------------------------------------------------------------------------------- 1 | DEV_KEY="7jKdYxdnYcbSQ5iWrGytWc" 2 | APP_ID="112233554" -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .vscode/ 21 | 22 | # Flutter repo-specific 23 | /bin/cache/ 24 | /bin/mingit/ 25 | /dev/benchmarks/mega_gallery/ 26 | /dev/bots/.recipe_deps 27 | /dev/bots/android_tools/ 28 | /dev/docs/doc/ 29 | /dev/docs/flutter.docs.zip 30 | /dev/docs/lib/ 31 | /dev/docs/pubspec.yaml 32 | /packages/flutter/coverage/ 33 | version 34 | 35 | # Flutter/Dart/Pub related 36 | **/doc/api/ 37 | .dart_tool/ 38 | .flutter-plugins 39 | .packages 40 | .pub-cache/ 41 | .pub/ 42 | build/ 43 | flutter_*.png 44 | linked_*.ds 45 | unlinked.ds 46 | unlinked_spec.ds 47 | 48 | # Android related 49 | **/android/**/gradle-wrapper.jar 50 | **/android/.gradle 51 | **/android/captures/ 52 | **/android/gradlew 53 | **/android/gradlew.bat 54 | **/android/local.properties 55 | **/android/**/GeneratedPluginRegistrant.java 56 | 57 | # iOS/XCode related 58 | **/ios/**/*.mode1v3 59 | **/ios/**/*.mode2v3 60 | **/ios/**/*.moved-aside 61 | **/ios/**/*.pbxuser 62 | **/ios/**/*.perspectivev3 63 | **/ios/**/*sync/ 64 | **/ios/**/.sconsign.dblite 65 | **/ios/**/.tags* 66 | **/ios/**/.vagrant/ 67 | **/ios/**/DerivedData/ 68 | **/ios/**/Icon? 69 | **/ios/**/Pods/ 70 | **/ios/**/.symlinks/ 71 | **/ios/**/profile 72 | **/ios/**/xcuserdata 73 | **/ios/.generated/ 74 | **/ios/Flutter/App.framework 75 | **/ios/Flutter/Flutter.framework 76 | **/ios/Flutter/Generated.xcconfig 77 | **/ios/Flutter/app.flx 78 | **/ios/Flutter/app.zip 79 | **/ios/Flutter/flutter_assets/ 80 | **/ios/ServiceDefinitions.json 81 | **/ios/Runner/GeneratedPluginRegistrant.* 82 | 83 | # Exceptions to above rules. 84 | !**/ios/**/default.mode1v3 85 | !**/ios/**/default.mode2v3 86 | !**/ios/**/default.pbxuser 87 | !**/ios/**/default.perspectivev3 88 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 89 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "ef1af02aead6fe2414f3aafa5a61087b610e1332" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 17 | base_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 18 | - platform: android 19 | create_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 20 | base_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 21 | - platform: ios 22 | create_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 23 | base_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 24 | - platform: linux 25 | create_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 26 | base_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 27 | - platform: macos 28 | create_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 29 | base_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 30 | - platform: web 31 | create_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 32 | base_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 33 | - platform: windows 34 | create_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 35 | base_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # appsflyer_sdk_example 2 | 3 | This plugin has a demo project bundled with it. To give it a try , clone this repo and from root a.e. `flutter_appsflyer_sdk` execute the following: 4 | 5 | ```bash 6 | $ flutter packages get 7 | $ cd example/ 8 | $ flutter run 9 | 10 | ``` 11 | 12 | ![demo printscreen](assets/demo_example.png?raw=true) 13 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = '1' 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | flutterVersionName = '1.0' 23 | } 24 | 25 | android { 26 | compileSdkVersion flutter.compileSdkVersion 27 | ndkVersion = "27.0.12077973" 28 | 29 | compileOptions { 30 | sourceCompatibility JavaVersion.VERSION_17 31 | targetCompatibility JavaVersion.VERSION_17 32 | } 33 | 34 | kotlinOptions { 35 | jvmTarget = '17' 36 | } 37 | 38 | sourceSets { 39 | main.java.srcDirs += 'src/main/kotlin' 40 | } 41 | 42 | defaultConfig { 43 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 44 | applicationId "com.appsflyer.appsflyersdkexample" 45 | // You can update the following values to match your application needs. 46 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 47 | minSdkVersion flutter.minSdkVersion 48 | targetSdkVersion flutter.targetSdkVersion 49 | versionCode flutterVersionCode.toInteger() 50 | versionName flutterVersionName 51 | } 52 | 53 | buildTypes { 54 | release { 55 | // TODO: Add your own signing config for the release build. 56 | // Signing with the debug keys for now, so `flutter run --release` works. 57 | signingConfig signingConfigs.debug 58 | } 59 | } 60 | namespace 'com.appsflyer.appsflyersdkexample' 61 | } 62 | 63 | flutter { 64 | source '../..' 65 | } 66 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 16 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | 34 | 37 | 38 | 39 | 41 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/appsflyer/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.appsflyer.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.appsflyer.appsflyersdkexample 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = '../build' 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(':app') 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version '8.8.1' apply false 22 | id "org.jetbrains.kotlin.android" version "2.0.0" apply false 23 | } 24 | 25 | include ":app" -------------------------------------------------------------------------------- /example/assets/demo_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/assets/demo_example.png -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | target 'RunnerTests' do 36 | inherit! :search_paths 37 | end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | target.build_configurations.each do |config| 44 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0' 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 64 | 66 | 72 | 73 | 74 | 75 | 81 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | Example 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | example 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | $(FLUTTER_BUILD_NUMBER) 27 | LSRequiresIPhoneOS 28 | 29 | UIApplicationSupportsIndirectInputEvents 30 | 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | UIMainStoryboardFile 34 | Main 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.associated-domains 6 | 7 | applinks:flutterdp.onelink.me 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /example/lib/app_constants.dart: -------------------------------------------------------------------------------- 1 | class AppConstants { 2 | static const double TOP_PADDING = 12.0; 3 | static const double CONTAINER_PADDING = 8.0; 4 | } 5 | -------------------------------------------------------------------------------- /example/lib/home_container.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | import './app_constants.dart'; 4 | import 'text_border.dart'; 5 | import 'utils.dart'; 6 | 7 | class HomeContainer extends StatefulWidget { 8 | final Map onData; 9 | final Future Function(String, Map) logEvent; 10 | final void Function() logAdRevenueEvent; 11 | Object deepLinkData; 12 | 13 | HomeContainer({ 14 | required this.onData, 15 | required this.deepLinkData, 16 | required this.logEvent, 17 | required this.logAdRevenueEvent, 18 | }); 19 | 20 | @override 21 | _HomeContainerState createState() => _HomeContainerState(); 22 | } 23 | 24 | class _HomeContainerState extends State { 25 | final String eventName = "Purchase Event"; 26 | 27 | final Map eventValues = { 28 | "af_content_id": "id123", 29 | "af_currency": "USD", 30 | "af_revenue": "20" 31 | }; 32 | 33 | String _logEventResponse = "Awaiting event status"; 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | return SingleChildScrollView( 38 | child: Container( 39 | padding: const EdgeInsets.all(AppConstants.CONTAINER_PADDING), 40 | child: Column( 41 | crossAxisAlignment: CrossAxisAlignment.start, 42 | children: [ 43 | Container( 44 | padding: EdgeInsets.all(20.0), 45 | decoration: BoxDecoration( 46 | color: Colors.white, 47 | border: Border.all( 48 | color: Colors.blueGrey, 49 | width: 0.5 50 | ), 51 | borderRadius: BorderRadius.circular(5), 52 | ),child: Column( 53 | children: [ 54 | Text( 55 | "APPSFLYER SDK", 56 | style: TextStyle( 57 | fontSize: 18, 58 | color: Colors.black, 59 | fontWeight: FontWeight.w500, 60 | ), 61 | ), 62 | SizedBox(height: AppConstants.TOP_PADDING), 63 | TextBorder( 64 | controller: TextEditingController( 65 | text: widget.onData.isNotEmpty 66 | ? Utils.formatJson(widget.onData) 67 | : "Waiting for conversion data...", 68 | ), 69 | labelText: "CONVERSION DATA", 70 | ), 71 | SizedBox(height: 12.0), 72 | TextBorder( 73 | controller: TextEditingController( 74 | text: widget.deepLinkData != null 75 | ? Utils.formatJson(widget.deepLinkData) 76 | : "Waiting for attribution data...", 77 | ), 78 | labelText: "ATTRIBUTION DATA", 79 | ), 80 | ], 81 | ), 82 | ), 83 | SizedBox(height: 12.0), 84 | Container( 85 | padding: EdgeInsets.all(20.0), 86 | decoration: BoxDecoration( 87 | color: Colors.white, 88 | border: Border.all( 89 | color: Colors.blueGrey, 90 | width: 0.5 91 | ), 92 | borderRadius: BorderRadius.circular(5), 93 | ), 94 | child: Column( 95 | children: [ 96 | Text( 97 | "EVENT LOGGER", 98 | style: TextStyle( 99 | fontSize: 18, 100 | color: Colors.black, 101 | fontWeight: FontWeight.w500, 102 | ), 103 | ), 104 | SizedBox(height: 12.0), 105 | TextBorder( 106 | controller: TextEditingController( 107 | text: "Event Name: $eventName\nEvent Values: $eventValues" 108 | ), 109 | labelText: "EVENT REQUEST", 110 | ), 111 | SizedBox(height: 12.0), 112 | TextBorder( 113 | labelText: "SERVER RESPONSE", 114 | controller: TextEditingController( 115 | text: _logEventResponse 116 | ), 117 | ), 118 | SizedBox(height: 20.0), 119 | ElevatedButton( 120 | onPressed: () { 121 | widget.logEvent(eventName, eventValues).then((onValue) { 122 | setState(() { 123 | _logEventResponse = "Event Status: " + onValue.toString(); 124 | }); 125 | }).catchError((onError) { 126 | setState(() { 127 | _logEventResponse = "Error: " + onError.toString(); 128 | }); 129 | }); 130 | }, 131 | child: Text("Trigger Purchase Event"), 132 | style: ElevatedButton.styleFrom( 133 | backgroundColor: Colors.white, 134 | padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), 135 | textStyle: TextStyle( 136 | fontWeight: FontWeight.bold, 137 | fontSize: 16, 138 | ), 139 | ), 140 | ), 141 | ElevatedButton( 142 | onPressed: () { 143 | widget.logAdRevenueEvent(); 144 | }, 145 | child: Text("Trigger AdRevenue Event"), 146 | style: ElevatedButton.styleFrom( 147 | backgroundColor: Colors.white, 148 | padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), 149 | textStyle: TextStyle( 150 | fontWeight: FontWeight.bold, 151 | fontSize: 16, 152 | ), 153 | ), 154 | ), 155 | ], 156 | ), 157 | ) 158 | ], 159 | ), 160 | ), 161 | ); 162 | } 163 | } -------------------------------------------------------------------------------- /example/lib/home_container_streams.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | import 'app_constants.dart'; 4 | import 'text_border.dart'; 5 | import 'utils.dart'; 6 | 7 | class HomeContainerStreams extends StatefulWidget { 8 | final Stream onData; 9 | final Stream onAttribution; 10 | final Future Function(String, Map) logEvent; 11 | 12 | HomeContainerStreams({ 13 | required this.onData, 14 | required this.onAttribution, 15 | required this.logEvent 16 | }); 17 | 18 | @override 19 | _HomeContainerStreamsState createState() => _HomeContainerStreamsState(); 20 | } 21 | 22 | class _HomeContainerStreamsState extends State { 23 | final String eventName = "Custom Event"; 24 | 25 | final Map eventValues = { 26 | "af_content_id": "id123", 27 | "af_currency": "USD", 28 | "af_revenue": "2" 29 | }; 30 | 31 | String _logEventResponse = "Event status will be shown here once it's triggered."; 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return SingleChildScrollView( 36 | child: Container( 37 | padding: EdgeInsets.all(AppConstants.CONTAINER_PADDING), 38 | child: Column( 39 | crossAxisAlignment: CrossAxisAlignment.start, 40 | children: [ 41 | Container( 42 | padding: EdgeInsets.all(20.0), 43 | decoration: BoxDecoration( 44 | color: Colors.white, 45 | border: Border.all( 46 | color: Colors.blueGrey, 47 | width: 0.5 48 | ), 49 | borderRadius: BorderRadius.circular(5), 50 | ), 51 | child: Column( 52 | crossAxisAlignment: CrossAxisAlignment.start, 53 | children: [ 54 | Text( 55 | "APPSFLYER SDK", 56 | style: TextStyle( 57 | fontSize: 18, 58 | color: Colors.black, 59 | fontWeight: FontWeight.w500, 60 | ), 61 | ), 62 | SizedBox(height: AppConstants.TOP_PADDING), 63 | StreamBuilder( 64 | stream: widget.onData.asBroadcastStream(), 65 | builder: (BuildContext context, AsyncSnapshot snapshot) { 66 | return TextBorder( 67 | controller: TextEditingController( 68 | text: snapshot.hasData ? Utils.formatJson(snapshot.data) : "Waiting for conversion data..." 69 | ), 70 | labelText: "CONVERSION DATA", 71 | ); 72 | }, 73 | ), 74 | SizedBox(height: 12.0), 75 | StreamBuilder( 76 | stream: widget.onAttribution.asBroadcastStream(), 77 | builder: (BuildContext context, AsyncSnapshot snapshot) { 78 | return TextBorder( 79 | controller: TextEditingController( 80 | text: snapshot.hasData ? Utils.formatJson(snapshot.data) : "Waiting for attribution data..." 81 | ), 82 | labelText: "ATTRIBUTION DATA", 83 | ); 84 | } 85 | ), 86 | ], 87 | ) 88 | ), 89 | SizedBox(height: 12.0), 90 | Container( 91 | padding: EdgeInsets.all(20.0), 92 | decoration: BoxDecoration( 93 | color: Colors.white, 94 | borderRadius: BorderRadius.circular(5), 95 | border: Border.all( 96 | color: Colors.grey, 97 | width: 0.5 98 | ), 99 | ), 100 | child: Column(children: [ 101 | Text( 102 | "EVENT LOGGER", 103 | style: TextStyle( 104 | fontSize: 18, 105 | fontWeight: FontWeight.w500, 106 | ), 107 | ), 108 | SizedBox(height: 12.0), 109 | TextBorder( 110 | controller: TextEditingController( 111 | text: "Event Name: $eventName\nEvent Values: $eventValues" 112 | ), 113 | labelText: "EVENT REQUEST", 114 | ), 115 | SizedBox(height: 12.0), 116 | TextBorder( 117 | labelText: "SERVER RESPONSE", 118 | controller: TextEditingController(text: _logEventResponse), 119 | ), 120 | SizedBox(height: 20), 121 | ElevatedButton( 122 | onPressed: () { 123 | widget.logEvent(eventName, eventValues).then((onValue) { 124 | setState(() { 125 | _logEventResponse = onValue.toString(); 126 | }); 127 | }).catchError((onError) { 128 | setState(() { 129 | _logEventResponse = onError.toString(); 130 | }); 131 | }); 132 | }, 133 | child: Text("Trigger Event"), 134 | style: ElevatedButton.styleFrom( 135 | backgroundColor: Colors.white, 136 | padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), 137 | textStyle: TextStyle( 138 | fontWeight: FontWeight.bold, 139 | fontSize: 16, 140 | ), 141 | ), 142 | ), 143 | ]), 144 | ) 145 | ], 146 | ), 147 | ), 148 | ); 149 | } 150 | } -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_dotenv/flutter_dotenv.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | import './main_page.dart'; 7 | 8 | Future main() async { 9 | await dotenv.load(fileName: '.env'); 10 | runApp(MyApp()); 11 | } 12 | 13 | class MyApp extends StatelessWidget { 14 | @override 15 | Widget build(BuildContext context) { 16 | return new MaterialApp( 17 | home: new MainPage(), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example/lib/main_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:appsflyer_sdk/appsflyer_sdk.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_dotenv/flutter_dotenv.dart'; 6 | 7 | import 'home_container.dart'; 8 | 9 | class MainPage extends StatefulWidget { 10 | @override 11 | State createState() { 12 | return MainPageState(); 13 | } 14 | } 15 | 16 | class MainPageState extends State { 17 | late AppsflyerSdk _appsflyerSdk; 18 | Map _deepLinkData = {}; 19 | Map _gcd = {}; 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | afStart(); 25 | } 26 | 27 | void afStart() async { 28 | // SDK Options 29 | final AppsFlyerOptions options = AppsFlyerOptions( 30 | afDevKey: dotenv.env["DEV_KEY"]!, 31 | appId: dotenv.env["APP_ID"]!, 32 | showDebug: true, 33 | timeToWaitForATTUserAuthorization: 15, 34 | manualStart: true); 35 | /* 36 | final Map? map = { 37 | 'afDevKey': dotenv.env["DEV_KEY"]!, 38 | 'appId': dotenv.env["APP_ID"]!, 39 | 'isDebug': true, 40 | 'timeToWaitForATTUserAuthorization': 15.0//, 41 | //'manualStart': false 42 | }; 43 | _appsflyerSdk = AppsflyerSdk(map); 44 | */ 45 | _appsflyerSdk = AppsflyerSdk(options); 46 | 47 | /* 48 | Setting configuration to the SDK: 49 | _appsflyerSdk.setCurrencyCode("USD"); 50 | _appsflyerSdk.enableTCFDataCollection(true); 51 | var forGdpr = AppsFlyerConsent.forGDPRUser(hasConsentForDataUsage: true, hasConsentForAdsPersonalization: true); 52 | _appsflyerSdk.setConsentData(forGdpr); 53 | var nonGdpr = AppsFlyerConsent.nonGDPRUser(); 54 | _appsflyerSdk.setConsentData(nonGdpr); 55 | */ 56 | 57 | // Init of AppsFlyer SDK 58 | await _appsflyerSdk.initSdk( 59 | registerConversionDataCallback: true, 60 | registerOnAppOpenAttributionCallback: true, 61 | registerOnDeepLinkingCallback: true); 62 | 63 | // Conversion data callback 64 | _appsflyerSdk.onInstallConversionData((res) { 65 | print("onInstallConversionData res: " + res.toString()); 66 | setState(() { 67 | _gcd = res; 68 | }); 69 | }); 70 | 71 | // App open attribution callback 72 | _appsflyerSdk.onAppOpenAttribution((res) { 73 | print("onAppOpenAttribution res: " + res.toString()); 74 | setState(() { 75 | _deepLinkData = res; 76 | }); 77 | }); 78 | 79 | // Deep linking callback 80 | _appsflyerSdk.onDeepLinking((DeepLinkResult dp) { 81 | switch (dp.status) { 82 | case Status.FOUND: 83 | print(dp.deepLink?.toString()); 84 | print("deep link value: ${dp.deepLink?.deepLinkValue}"); 85 | break; 86 | case Status.NOT_FOUND: 87 | print("deep link not found"); 88 | break; 89 | case Status.ERROR: 90 | print("deep link error: ${dp.error}"); 91 | break; 92 | case Status.PARSE_ERROR: 93 | print("deep link status parsing error"); 94 | break; 95 | } 96 | print("onDeepLinking res: " + dp.toString()); 97 | setState(() { 98 | _deepLinkData = dp.toJson(); 99 | }); 100 | }); 101 | 102 | //_appsflyerSdk.anonymizeUser(true); 103 | if (Platform.isAndroid) { 104 | _appsflyerSdk.performOnDeepLinking(); 105 | } 106 | setState(() {}); // Call setState to rebuild the widget 107 | } 108 | 109 | @override 110 | Widget build(BuildContext context) { 111 | return Scaffold( 112 | appBar: AppBar( 113 | title: Text('AppsFlyer SDK example app'), 114 | centerTitle: true, 115 | backgroundColor: Colors.green, 116 | ), 117 | body: Builder( 118 | builder: (context) { 119 | return SafeArea( 120 | child: Column( 121 | children: [ 122 | Expanded( 123 | child: HomeContainer( 124 | onData: _gcd, 125 | deepLinkData: _deepLinkData, 126 | logEvent: logEvent, 127 | logAdRevenueEvent: logAdRevenueEvent, 128 | ), 129 | ), 130 | ElevatedButton( 131 | onPressed: () { 132 | _appsflyerSdk.startSDK( 133 | onSuccess: () { 134 | showMessage("AppsFlyer SDK initialized successfully."); 135 | }, 136 | onError: (int errorCode, String errorMessage) { 137 | showMessage( 138 | "Error initializing AppsFlyer SDK: Code $errorCode - $errorMessage"); 139 | }, 140 | ); 141 | }, 142 | child: Text("START SDK"), 143 | ) 144 | ], 145 | ), 146 | ); 147 | }, 148 | ), 149 | ); 150 | } 151 | 152 | Future logEvent(String eventName, Map eventValues) async { 153 | bool? logResult; 154 | try { 155 | logResult = await _appsflyerSdk.logEvent(eventName, eventValues); 156 | print("Event logged"); 157 | } catch (e) { 158 | print("Failed to log event: $e"); 159 | } 160 | return logResult; 161 | } 162 | 163 | void logAdRevenueEvent() { 164 | try { 165 | Map customParams = { 166 | 'ad_platform': 'Admob', 167 | 'ad_currency': 'USD', 168 | }; 169 | 170 | AdRevenueData adRevenueData = AdRevenueData( 171 | monetizationNetwork: 'SpongeBob', 172 | mediationNetwork: AFMediationNetwork.googleAdMob.value, 173 | currencyIso4217Code: 'USD', 174 | revenue: 100.3, 175 | additionalParameters: customParams); 176 | _appsflyerSdk.logAdRevenue(adRevenueData); 177 | print("Ad Revenue event logged with no errors"); 178 | } catch (e) { 179 | print("Failed to log event: $e"); 180 | } 181 | } 182 | 183 | void showMessage(String message) { 184 | ScaffoldMessenger.of(context).showSnackBar(SnackBar( 185 | content: Text(message), 186 | )); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /example/lib/text_border.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // ignore: must_be_immutable 4 | class TextBorder extends StatelessWidget { 5 | final TextEditingController controller; 6 | final String labelText; 7 | 8 | const TextBorder({ 9 | required this.controller, 10 | required this.labelText, 11 | Key? key 12 | }) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Container( 17 | margin: const EdgeInsets.symmetric(vertical: 8.0), // Add some vertical margin 18 | child: TextField( 19 | controller: controller, 20 | enabled: false, 21 | maxLines: null, 22 | decoration: InputDecoration( 23 | labelText: labelText, 24 | labelStyle: TextStyle(color: Colors.blueGrey), // Change the color of the label 25 | border: OutlineInputBorder( 26 | borderSide: BorderSide( 27 | width: 1.0 28 | ), 29 | ), 30 | focusedBorder: OutlineInputBorder( 31 | borderSide: BorderSide( 32 | width: 1.0 33 | ), 34 | ), 35 | ), 36 | ), 37 | ); 38 | } 39 | } -------------------------------------------------------------------------------- /example/lib/utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | class Utils { 4 | static String formatJson(jsonObj) { 5 | // ignore: prefer_final_locals 6 | JsonEncoder encoder = new JsonEncoder.withIndent(' '); 7 | return encoder.convert(jsonObj); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: appsflyer_sdk_example 2 | description: Demonstrates how to use the appsflyer_sdk plugin. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # Read more about versioning at semver.org. 10 | version: 1.0.0+1 11 | 12 | publish_to: none 13 | 14 | environment: 15 | sdk: '>=2.12.0 <4.0.0' 16 | 17 | dependencies: 18 | flutter: 19 | sdk: flutter 20 | appsflyer_sdk: 21 | path: ../ 22 | 23 | # The following adds the Cupertino Icons font to your application. 24 | # Use with the CupertinoIcons class for iOS style icons. 25 | cupertino_icons: ^1.0.6 26 | flutter_dotenv: ^5.1.0 27 | 28 | dev_dependencies: 29 | flutter_test: 30 | sdk: flutter 31 | 32 | # For information on the generic Dart part of this file, see the 33 | # following page: https://www.dartlang.org/tools/pub/pubspec 34 | 35 | # The following section is specific to Flutter. 36 | flutter: 37 | # The following line ensures that the Material Icons font is 38 | # included with your application, so that you can use the icons in 39 | # the material Icons class. 40 | uses-material-design: true 41 | # To add assets to your application, add an assets section, like this: 42 | assets: 43 | - .env 44 | # - images/a_dot_burr.jpeg 45 | # - images/a_dot_ham.jpeg 46 | # An image asset can refer to one or more resolution-specific "variants", see 47 | # https://flutter.io/assets-and-images/#resolution-aware. 48 | # For details regarding adding assets from package dependencies, see 49 | # https://flutter.io/assets-and-images/#from-packages 50 | # To add custom fonts to your application, add a fonts section here, 51 | # in this "flutter" section. Each entry in this list should have a 52 | # "family" key with the font family name, and a "fonts" key with a 53 | # list giving the asset and other descriptors for the font. For 54 | # example: 55 | # fonts: 56 | # - family: Schyler 57 | # fonts: 58 | # - asset: fonts/Schyler-Regular.ttf 59 | # - asset: fonts/Schyler-Italic.ttf 60 | # style: italic 61 | # - family: Trajan Pro 62 | # fonts: 63 | # - asset: fonts/TrajanPro.ttf 64 | # - asset: fonts/TrajanPro_Bold.ttf 65 | # weight: 700 66 | # 67 | # For details regarding fonts from package dependencies, 68 | # see https://flutter.io/custom-fonts/#from-packages 69 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | // import 'package:flutter/material.dart'; 9 | // import 'package:flutter_test/flutter_test.dart'; 10 | 11 | // import 'package:example/main.dart'; 12 | 13 | // void main() { 14 | // testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // // Build our app and trigger a frame. 16 | // await tester.pumpWidget(MyApp()); 17 | 18 | // // Verify that our counter starts at 0. 19 | // expect(find.text('0'), findsOneWidget); 20 | // expect(find.text('1'), findsNothing); 21 | 22 | // // Tap the '+' icon and trigger a frame. 23 | // await tester.tap(find.byIcon(Icons.add)); 24 | // await tester.pump(); 25 | 26 | // // Verify that our counter has incremented. 27 | // expect(find.text('0'), findsNothing); 28 | // expect(find.text('1'), findsOneWidget); 29 | // }); 30 | // } 31 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/5ab2580041e87f41bc313f3d09ee0ce615f887a9/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/AppsFlyerAttribution.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppsFlyerAttribution.h 3 | // Pods 4 | // 5 | // Created by Amit Kremer on 11/02/2021. 6 | // 7 | 8 | #ifndef AppsFlyerAttribution_h 9 | #define AppsFlyerAttribution_h 10 | #endif /* AppsFlyerAttribution_h */ 11 | 12 | #import 13 | 14 | @interface AppsFlyerAttribution : NSObject 15 | @property NSUserActivity*_Nullable userActivity; 16 | @property (nonatomic, copy) void (^ _Nullable restorationHandler)(NSArray *_Nullable ); 17 | @property NSURL * _Nullable url; 18 | @property NSDictionary * _Nullable options; 19 | @property NSString* _Nullable sourceApplication; 20 | @property id _Nullable annotation; 21 | @property BOOL isBridgeReady; 22 | 23 | + (AppsFlyerAttribution *_Nullable)shared; 24 | - (void) continueUserActivity: (NSUserActivity*_Nullable) userActivity restorationHandler: (void (^_Nullable)(NSArray * _Nullable))restorationHandler; 25 | - (void) handleOpenUrl:(NSURL*_Nullable)url options:(NSDictionary*_Nullable) options; 26 | - (void) handleOpenUrl: (NSURL *_Nullable)url sourceApplication:(NSString*_Nullable)sourceApplication annotation:(id _Nullable )annotation; 27 | 28 | @end 29 | 30 | static NSString * _Nullable const AF_BRIDGE_SET = @"bridge is set"; 31 | -------------------------------------------------------------------------------- /ios/Classes/AppsFlyerAttribution.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppsFlyerAttribution.m 3 | // flutter-appsflyer 4 | // 5 | // Created by Amit Kremer on 11/02/2021. 6 | // 7 | 8 | #import 9 | #import "AppsFlyerAttribution.h" 10 | 11 | @implementation AppsFlyerAttribution 12 | 13 | + (id)shared { 14 | static AppsFlyerAttribution *shared = nil; 15 | static dispatch_once_t onceToken; 16 | dispatch_once(&onceToken, ^{ 17 | shared = [[self alloc] init]; 18 | }); 19 | return shared; 20 | } 21 | 22 | - (id)init { 23 | if (self = [super init]) { 24 | self.options = nil; 25 | self.restorationHandler = nil; 26 | self.url = nil; 27 | self.userActivity = nil; 28 | self.annotation = nil; 29 | self.sourceApplication = nil; 30 | self.isBridgeReady = NO; 31 | [[NSNotificationCenter defaultCenter] addObserver:self 32 | selector:@selector(receiveBridgeReadyNotification:) 33 | name:AF_BRIDGE_SET 34 | object:nil]; 35 | } 36 | return self; 37 | } 38 | 39 | - (void) continueUserActivity: (NSUserActivity*_Nullable) userActivity restorationHandler: (void (^_Nullable)(NSArray * _Nullable))restorationHandler{ 40 | if(self.isBridgeReady == YES){ 41 | [[AppsFlyerLib shared] continueUserActivity:userActivity restorationHandler:restorationHandler]; 42 | }else{ 43 | [AppsFlyerAttribution shared].userActivity = userActivity; 44 | [AppsFlyerAttribution shared].restorationHandler = restorationHandler; 45 | } 46 | } 47 | 48 | - (void) handleOpenUrl:(NSURL *)url options:(NSDictionary *)options{ 49 | if(self.isBridgeReady == YES){ 50 | [[AppsFlyerLib shared] handleOpenUrl:url options:options]; 51 | }else{ 52 | [AppsFlyerAttribution shared].url = url; 53 | [AppsFlyerAttribution shared].options = options; 54 | } 55 | } 56 | 57 | - (void) handleOpenUrl:(NSURL *)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation{ 58 | if(self.isBridgeReady == YES){ 59 | [[AppsFlyerLib shared] handleOpenURL:url sourceApplication:sourceApplication withAnnotation:annotation]; 60 | }else{ 61 | [AppsFlyerAttribution shared].url = url; 62 | [AppsFlyerAttribution shared].sourceApplication = sourceApplication; 63 | [AppsFlyerAttribution shared].annotation = annotation; 64 | } 65 | 66 | } 67 | 68 | - (void) receiveBridgeReadyNotification:(NSNotification *) notification 69 | { 70 | NSLog (@"AppsFlyer Debug: handle deep link"); 71 | if(self.url && self.sourceApplication && self.annotation){ 72 | [[AppsFlyerLib shared] handleOpenURL:self.url sourceApplication:self.sourceApplication withAnnotation:self.annotation]; 73 | self.url = nil; 74 | self.sourceApplication = nil; 75 | self.annotation = nil; 76 | }else if(self.options && self.url){ 77 | [[AppsFlyerLib shared] handleOpenUrl:self.url options:self.options]; 78 | self.options = nil; 79 | self.url = nil; 80 | }else if(self.userActivity){ 81 | [[AppsFlyerLib shared] continueUserActivity:self.userActivity restorationHandler:nil]; 82 | self.userActivity = nil; 83 | self.restorationHandler = nil; 84 | } 85 | } 86 | @end 87 | -------------------------------------------------------------------------------- /ios/Classes/AppsFlyerStreamHandler.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppsFlyerStreamHandler.h 3 | // appsflyer_sdk 4 | // 5 | // Created by Shahar Cohen on 05/09/2019. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | // I will change it to seperate file with #defines 12 | #import "AppsflyerSdkPlugin.h" 13 | #import "AppsFlyerAttribution.h" 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface AppsFlyerStreamHandler: NSObject 18 | 19 | - (void)sendResponseToFlutter:(NSString *)responseID status:(NSString *)status data:(NSDictionary *)data; 20 | - (NSString*) getStatusAsString:(AFSDKDeepLinkResultStatus)value; 21 | 22 | @end 23 | 24 | NS_ASSUME_NONNULL_END 25 | -------------------------------------------------------------------------------- /ios/Classes/AppsFlyerStreamHandler.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppsFlyerStreamHandler.m 3 | // appsflyer_sdk 4 | // 5 | // Created by Shahar Cohen on 05/09/2019. 6 | // 7 | 8 | #import "AppsFlyerStreamHandler.h" 9 | 10 | @implementation AppsFlyerStreamHandler { 11 | 12 | } 13 | 14 | - (void)onConversionDataSuccess:(NSDictionary *)installData { 15 | NSError *error; 16 | 17 | //use callbacks 18 | if([AppsflyerSdkPlugin gcdCallback]){ 19 | NSString *installDataJson = [self mapToJson:installData withError:error]; 20 | NSDictionary *fullResponse = @{ 21 | @"id": afGCDCallback, 22 | @"data": installDataJson, 23 | @"status": afSuccess 24 | }; 25 | NSString *JSONString = [self mapToJson:fullResponse withError:error]; 26 | [AppsflyerSdkPlugin.callbackChannel invokeMethod:@"callListener" arguments:JSONString]; 27 | return; 28 | }else if (error) { 29 | return; 30 | } 31 | } 32 | 33 | - (NSString *)mapToJson:(NSDictionary *)data withError:(NSError *)error{ 34 | NSData *JSON = [NSJSONSerialization dataWithJSONObject:data options:0 error:&error]; 35 | NSString *JSONString = [[NSString alloc] initWithData:JSON encoding:NSUTF8StringEncoding]; 36 | return JSONString; 37 | } 38 | 39 | - (void)onConversionDataFail:(NSError *)error { 40 | //use callbacks 41 | if([AppsflyerSdkPlugin gcdCallback]){ 42 | NSDictionary *fullResponse = @{ 43 | @"id": afGCDCallback, 44 | @"data": error.localizedDescription, 45 | @"status": afSuccess 46 | }; 47 | NSString *JSONString = [self mapToJson:fullResponse withError:error]; 48 | [AppsflyerSdkPlugin.callbackChannel invokeMethod:@"callListener" arguments:JSONString]; 49 | return; 50 | } 51 | 52 | if (error) { 53 | return; 54 | } 55 | } 56 | 57 | 58 | - (void)onAppOpenAttribution:(NSDictionary *)attributionData { 59 | NSError *error; 60 | //use callbacks 61 | if([AppsflyerSdkPlugin oaoaCallback]){ 62 | NSString* attributionDataJson = [self mapToJson:attributionData withError:error]; 63 | NSDictionary *fullResponse = @{ 64 | @"id": afOAOACallback, 65 | @"data": attributionDataJson, 66 | @"status": afSuccess 67 | }; 68 | NSString *JSONString = [self mapToJson:fullResponse withError:error]; 69 | [AppsflyerSdkPlugin.callbackChannel invokeMethod:@"callListener" arguments:JSONString]; 70 | return; 71 | } 72 | 73 | if (error) { 74 | return; 75 | } 76 | } 77 | 78 | - (void)onAppOpenAttributionFailure:(NSError *)error { 79 | if([AppsflyerSdkPlugin oaoaCallback]){ 80 | NSDictionary *fullResponse = @{ 81 | @"id": afOAOACallback, 82 | @"data": error.localizedDescription, 83 | @"status": afSuccess 84 | }; 85 | NSString *JSONString = [self mapToJson:fullResponse withError:error]; 86 | [AppsflyerSdkPlugin.callbackChannel invokeMethod:@"callListener" arguments:JSONString]; 87 | return; 88 | } 89 | 90 | } 91 | 92 | - (void)didResolveDeepLink:(AppsFlyerDeepLinkResult* _Nonnull) deepLinkResult { 93 | NSError *error; 94 | if([AppsflyerSdkPlugin udpCallback]){ 95 | NSMutableDictionary *fullResponse = [[NSMutableDictionary alloc] initWithCapacity:4]; 96 | 97 | fullResponse[ @"id"] = afUDPCallback; 98 | fullResponse[ @"deepLinkStatus"] = [self getStatusAsString:deepLinkResult.status]; 99 | if(deepLinkResult.deepLink != nil){ 100 | NSMutableDictionary *dic = [[NSMutableDictionary alloc] initWithCapacity: deepLinkResult.deepLink.clickEvent.count + 1]; 101 | [dic addEntriesFromDictionary:deepLinkResult.deepLink.clickEvent]; 102 | dic[@"is_deferred"] = [NSNumber numberWithBool:deepLinkResult.deepLink.isDeferred]; 103 | fullResponse [@"deepLinkObj"] = dic; 104 | 105 | } 106 | if (deepLinkResult.error != nil && deepLinkResult.error.localizedDescription) { 107 | fullResponse [@"deepLinkError"] = deepLinkResult.error.localizedDescription; 108 | 109 | } 110 | NSString *JSONString = [self mapToJson:fullResponse withError:error]; 111 | [AppsflyerSdkPlugin.callbackChannel invokeMethod:@"callListener" arguments:JSONString]; 112 | return; 113 | } 114 | 115 | if (error) { 116 | return; 117 | } 118 | } 119 | 120 | 121 | - (void)sendResponseToFlutter:(NSString *)responseID status:(NSString *)status data:(NSDictionary *)data{ 122 | NSError *error; 123 | NSString *JSONdata; 124 | 125 | if(data != nil){ 126 | JSONdata = [self mapToJson:data withError:error]; 127 | }else{ 128 | JSONdata = @"empty data"; 129 | } 130 | if (error) { 131 | return; 132 | } 133 | NSDictionary *fullResponse = @{ 134 | @"id": responseID, 135 | @"data": JSONdata, 136 | @"status": status 137 | }; 138 | JSONdata = [self mapToJson:fullResponse withError:error]; 139 | [AppsflyerSdkPlugin.callbackChannel invokeMethod:@"callListener" arguments:JSONdata]; 140 | } 141 | 142 | - (NSString*) getStatusAsString:(AFSDKDeepLinkResultStatus)value{ 143 | switch (value) { 144 | case AFSDKDeepLinkResultStatusFound: 145 | return @"FOUND"; 146 | case AFSDKDeepLinkResultStatusNotFound: 147 | return @"NOT_FOUND"; 148 | default: 149 | return @"ERROR"; 150 | 151 | } 152 | } 153 | 154 | 155 | 156 | @end 157 | -------------------------------------------------------------------------------- /ios/Classes/AppsflyerSdkPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "AppsFlyerAttribution.h" 3 | #if __has_include() // from Pod 4 | #import 5 | #else 6 | #import "AppsFlyerLib.h" 7 | #endif 8 | 9 | @interface AppsflyerSdkPlugin: NSObject 10 | 11 | @property (readwrite, nonatomic) BOOL isManualStart; 12 | 13 | + (FlutterMethodChannel*)callbackChannel; 14 | + (BOOL)gcdCallback; 15 | + (BOOL)oaoaCallback; 16 | + (BOOL)udpCallback; 17 | 18 | @end 19 | 20 | // Appsflyer JS objects 21 | #define kAppsFlyerPluginVersion @"6.16.2" 22 | #define afDevKey @"afDevKey" 23 | #define afAppId @"afAppId" 24 | #define afIsDebug @"isDebug" 25 | #define afManualStart @"manualStart" 26 | #define afTimeToWaitForATTUserAuthorization @"timeToWaitForATTUserAuthorization" 27 | #define afEventName @"eventName" 28 | #define afEventValues @"eventValues" 29 | #define afConversionData @"GCD" 30 | #define afUDL @"UDL" 31 | #define afInviteOneLink @"appInviteOneLink" 32 | #define afDisableCollectASA @"disableCollectASA" 33 | #define afDisableAdvertisingIdentifier @"disableAdvertisingIdentifier" 34 | 35 | // Appsflyer native objects 36 | #define afOnInstallConversionData @"onInstallConversionData" 37 | #define afSuccess @"success" 38 | #define afFailure @"failure" 39 | #define afOnAttributionFailure @"onAttributionFailure" 40 | #define afValidatePurchase @"validatePurchase" 41 | #define afOnAppOpenAttribution @"onAppOpenAttribution" 42 | #define afOnDeepLinking @"onDeepLinking" 43 | #define afOnInstallConversionFailure @"onInstallConversionFailure" 44 | #define afOnInstallConversionDataLoaded @"onInstallConversionDataLoaded" 45 | #define afGCDCallback @"onInstallConversionData" 46 | #define afOAOACallback @"onAppOpenAttribution" 47 | #define afUDPCallback @"onDeepLinking" 48 | #define afGenerateInviteLinkSuccess @"generateInviteLinkSuccess" 49 | #define afGenerateInviteLinkFailure @"generateInviteLinkFailure" 50 | #define afAppInviteOneLinkID @"setAppInviteOneLinkIDCallback" 51 | 52 | // Stream Channels 53 | #define afMethodChannel @"af-api" 54 | #define afCallbacksMethodChannel @"callbacks" 55 | #define afEventChannel @"af-events" 56 | #define afValidatePurchaseChannel @"af-validate-purchase" 57 | 58 | -------------------------------------------------------------------------------- /ios/Classes/FlutterAppDelegate+AppsFlyerStreamHandler.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlutterAppDelegate+AppsFlyerStreamHandler.h 3 | // appsflyer_sdk 4 | // 5 | // Created by Shahar Cohen on 05/09/2019. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface FlutterAppDelegate () 13 | 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /ios/appsflyer_sdk.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 3 | # 4 | Pod::Spec.new do |s| 5 | s.name = 'appsflyer_sdk' 6 | s.version = '6.16.2' 7 | s.summary = 'AppsFlyer Integration for Flutter' 8 | s.description = <<-DESC 9 | AppsFlyer is the market leader in mobile advertising attribution & analytics, helping marketers to pinpoint their targeting, optimize their ad spend and boost their ROI. 10 | DESC 11 | s.homepage = 'https://github.com/AppsFlyerSDK/flutter_appsflyer_sdk' 12 | s.license = { :type => 'MIT', :file => '../LICENSE' } 13 | s.author = { "Appsflyer" => "build@appsflyer.com" } 14 | s.source = { :git => "https://github.com/AppsFlyerSDK/flutter_appsflyer_sdk.git", :tag => s.version.to_s } 15 | 16 | 17 | s.ios.deployment_target = '12.0' 18 | s.requires_arc = true 19 | s.static_framework = true 20 | 21 | s.source_files = 'Classes/**/*' 22 | s.public_header_files = 'Classes/**/*.h' 23 | s.dependency 'Flutter' 24 | s.ios.dependency 'AppsFlyerFramework','6.16.2' 25 | end 26 | -------------------------------------------------------------------------------- /lib/appsflyer_sdk.dart: -------------------------------------------------------------------------------- 1 | library appsflyer_sdk; 2 | 3 | import 'dart:convert'; 4 | import 'dart:core'; 5 | import 'dart:io'; 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter/services.dart'; 9 | 10 | import 'src/callbacks.dart'; 11 | 12 | part 'src/appsflyer_ad_revenue_data.dart'; 13 | part 'src/appsflyer_constants.dart'; 14 | part 'src/appsflyer_consent.dart'; 15 | part 'src/appsflyer_invite_link_params.dart'; 16 | part 'src/appsflyer_options.dart'; 17 | part 'src/appsflyer_request_listener.dart'; 18 | part 'src/appsflyer_sdk.dart'; 19 | part 'src/udl/deep_link_result.dart'; 20 | part 'src/udl/deeplink.dart'; 21 | -------------------------------------------------------------------------------- /lib/src/appsflyer_ad_revenue_data.dart: -------------------------------------------------------------------------------- 1 | part of appsflyer_sdk; 2 | 3 | class AdRevenueData { 4 | final String monetizationNetwork; 5 | final String mediationNetwork; 6 | final String currencyIso4217Code; 7 | final double revenue; 8 | final Map? additionalParameters; 9 | 10 | AdRevenueData({ 11 | required this.monetizationNetwork, 12 | required this.mediationNetwork, 13 | required this.currencyIso4217Code, 14 | required this.revenue, 15 | this.additionalParameters 16 | }); 17 | 18 | Map toMap() { 19 | return { 20 | 'monetizationNetwork': monetizationNetwork, 21 | 'mediationNetwork': mediationNetwork, 22 | 'currencyIso4217Code': currencyIso4217Code, 23 | 'revenue': revenue, 24 | 'additionalParameters': additionalParameters 25 | }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/appsflyer_consent.dart: -------------------------------------------------------------------------------- 1 | part of appsflyer_sdk; 2 | 3 | class AppsFlyerConsent { 4 | final bool isUserSubjectToGDPR; 5 | final bool hasConsentForDataUsage; 6 | final bool hasConsentForAdsPersonalization; 7 | 8 | AppsFlyerConsent._({ 9 | required this.isUserSubjectToGDPR, 10 | required this.hasConsentForDataUsage, 11 | required this.hasConsentForAdsPersonalization, 12 | }); 13 | 14 | // Factory constructors 15 | factory AppsFlyerConsent.forGDPRUser({ 16 | required bool hasConsentForDataUsage, 17 | required bool hasConsentForAdsPersonalization 18 | }){ 19 | return AppsFlyerConsent._( 20 | isUserSubjectToGDPR: true, 21 | hasConsentForDataUsage: hasConsentForDataUsage, 22 | hasConsentForAdsPersonalization: hasConsentForAdsPersonalization 23 | ); 24 | } 25 | 26 | factory AppsFlyerConsent.nonGDPRUser(){ 27 | return AppsFlyerConsent._( 28 | isUserSubjectToGDPR: false, 29 | hasConsentForDataUsage: false, 30 | hasConsentForAdsPersonalization: false 31 | ); 32 | } 33 | 34 | // Converts object to a map 35 | Map toMap() { 36 | return { 37 | 'isUserSubjectToGDPR': isUserSubjectToGDPR, 38 | 'hasConsentForDataUsage': hasConsentForDataUsage, 39 | 'hasConsentForAdsPersonalization': hasConsentForAdsPersonalization, 40 | }; 41 | } 42 | } -------------------------------------------------------------------------------- /lib/src/appsflyer_constants.dart: -------------------------------------------------------------------------------- 1 | part of appsflyer_sdk; 2 | 3 | enum EmailCryptType { EmailCryptTypeNone, EmailCryptTypeSHA256 } 4 | 5 | class AppsflyerConstants { 6 | static const String PLUGIN_VERSION = "6.16.2"; 7 | static const String AF_DEV_KEY = "afDevKey"; 8 | static const String AF_APP_Id = "afAppId"; 9 | static const String AF_IS_DEBUG = "isDebug"; 10 | static const String AF_MANUAL_START = "manualStart"; 11 | static const String AF_TIME_TO_WAIT_FOR_ATT_USER_AUTHORIZATION = 12 | "timeToWaitForATTUserAuthorization"; 13 | static const String AF_GCD = "GCD"; 14 | static const String AF_UDL = "UDL"; 15 | static const String AF_SUCCESS = "success"; 16 | static const String AF_FAILURE = "failure"; 17 | static const String AF_GET_CONVERSION_DATA = "onInstallConversionDataLoaded"; 18 | static const String AF_ON_APP_OPEN_ATTRIBUTION = "onAppOpenAttribution"; 19 | static const String AF_ON_DEEP_LINK = "onDeepLinking"; 20 | 21 | static const String AF_EVENTS_CHANNEL = "af-events"; 22 | static const String AF_METHOD_CHANNEL = "af-api"; 23 | 24 | static const String AF_VALIDATE_PURCHASE = "validatePurchase"; 25 | static const String APP_INVITE_ONE_LINK = "appInviteOneLink"; 26 | 27 | static const String DISABLE_COLLECT_ASA = "disableCollectASA"; 28 | static const String DISABLE_ADVERTISING_IDENTIFIER = 29 | "disableAdvertisingIdentifier"; 30 | } 31 | 32 | enum AFMediationNetwork { 33 | ironSource, 34 | applovinMax, 35 | googleAdMob, 36 | fyber, 37 | appodeal, 38 | admost, 39 | topon, 40 | tradplus, 41 | yandex, 42 | chartboost, 43 | unity, 44 | toponPte, 45 | customMediation, 46 | directMonetizationNetwork; 47 | 48 | String get value { 49 | switch (this) { 50 | case AFMediationNetwork.ironSource: 51 | return "ironsource"; 52 | case AFMediationNetwork.applovinMax: 53 | return "applovin_max"; 54 | case AFMediationNetwork.googleAdMob: 55 | return "google_admob"; 56 | case AFMediationNetwork.fyber: 57 | return "fyber"; 58 | case AFMediationNetwork.appodeal: 59 | return "appodeal"; 60 | case AFMediationNetwork.admost: 61 | return "admost"; 62 | case AFMediationNetwork.topon: 63 | return "topon"; 64 | case AFMediationNetwork.tradplus: 65 | return "tradplus"; 66 | case AFMediationNetwork.yandex: 67 | return "yandex"; 68 | case AFMediationNetwork.chartboost: 69 | return "chartboost"; 70 | case AFMediationNetwork.unity: 71 | return "unity"; 72 | case AFMediationNetwork.toponPte: 73 | return "topon_pte"; 74 | case AFMediationNetwork.customMediation: 75 | return "custom_mediation"; 76 | case AFMediationNetwork.directMonetizationNetwork: 77 | return "direct_monetization_network"; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/src/appsflyer_invite_link_params.dart: -------------------------------------------------------------------------------- 1 | part of appsflyer_sdk; 2 | 3 | /// This class represents parameters that are used to generate a user invite link. 4 | class AppsFlyerInviteLinkParams { 5 | final String? channel; 6 | final String? campaign; 7 | final String? referrerName; 8 | final String? referrerImageUrl; 9 | final String? customerID; 10 | final String? baseDeepLink; 11 | final String? brandDomain; 12 | final Map? customParams; 13 | 14 | /// Creates an [AppsFlyerInviteLinkParams] instance. 15 | /// All parameters are optional, allowing greater flexibility when 16 | /// invoking the constructor. 17 | AppsFlyerInviteLinkParams({ 18 | this.campaign, 19 | this.channel, 20 | this.referrerName, 21 | this.baseDeepLink, 22 | this.brandDomain, 23 | this.customerID, 24 | this.referrerImageUrl, 25 | this.customParams 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/appsflyer_options.dart: -------------------------------------------------------------------------------- 1 | part of appsflyer_sdk; 2 | 3 | /// The options used to configure the AppsFlyer SDK. 4 | class AppsFlyerOptions { 5 | final String afDevKey; 6 | final bool showDebug; 7 | final String appId; 8 | final double? timeToWaitForATTUserAuthorization; 9 | final String? appInviteOneLink; 10 | final bool? disableAdvertisingIdentifier; 11 | final bool? disableCollectASA; 12 | final bool? manualStart; 13 | 14 | /// Creates an [AppsFlyerOptions] instance. 15 | /// Requires [afDevKey] and [appId] as mandatory Named parameters. 16 | /// All other parameters are optional, it's allows greater flexibility 17 | /// when invoking the constructor. 18 | /// When [manualStart] is true the startSDK method must be called 19 | AppsFlyerOptions({ 20 | required this.afDevKey, 21 | this.showDebug = false, 22 | this.appId = "", 23 | this.timeToWaitForATTUserAuthorization, 24 | this.appInviteOneLink, 25 | this.disableAdvertisingIdentifier, 26 | this.disableCollectASA, 27 | this.manualStart = false, 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/appsflyer_request_listener.dart: -------------------------------------------------------------------------------- 1 | part of appsflyer_sdk; 2 | 3 | class AppsFlyerRequestListener { 4 | RequestSuccessListener onSuccess; 5 | RequestErrorListener onError; 6 | 7 | AppsFlyerRequestListener({ 8 | required this.onSuccess, 9 | required this.onError, 10 | }); 11 | } -------------------------------------------------------------------------------- /lib/src/callbacks.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:flutter/services.dart'; 5 | 6 | import '../appsflyer_sdk.dart'; 7 | 8 | const _channel = MethodChannel('callbacks'); 9 | 10 | typedef MultiUseCallback = void Function(dynamic msg); 11 | typedef UDLCallback = void Function(DeepLinkResult deepLinkResult); 12 | typedef CancelListening = void Function(); 13 | typedef RequestSuccessListener = void Function(); 14 | typedef RequestErrorListener = void Function(int errorCode, String errorMessage); 15 | 16 | Map _callbacksById = { 17 | }; 18 | UDLCallback? _udlCallback; 19 | 20 | Future _methodCallHandler(MethodCall call) async { 21 | switch (call.method) { 22 | case 'callListener': 23 | try { 24 | dynamic callMap = jsonDecode(call.arguments); 25 | switch (callMap["id"]) { 26 | case "onAppOpenAttribution": 27 | case "onInstallConversionData": 28 | case "validatePurchase": 29 | case "generateInviteLinkSuccess": 30 | String data = callMap["data"]; 31 | Map? decodedData = jsonDecode(data); 32 | Map fullResponse = { 33 | "status": callMap['status'], 34 | "payload": decodedData 35 | }; 36 | _callbacksById[callMap["id"]]!(fullResponse); 37 | break; 38 | case "onDeepLinking": 39 | Error? error = (callMap["deepLinkError"] as String?) 40 | ?.errorFromString(); 41 | Status? status = (callMap["deepLinkStatus"] as String?) 42 | ?.statusFromString() ?? Status.PARSE_ERROR; 43 | Map? map = callMap["deepLinkObj"] as Map< 44 | String, 45 | dynamic>?; 46 | DeepLink? deepLink = map != null ? DeepLink(map) : null; 47 | var dp = DeepLinkResult(error, deepLink, status); 48 | if (_udlCallback != null) { 49 | _udlCallback!(dp); 50 | } 51 | break; 52 | default: 53 | _callbacksById[callMap["id"]]!(callMap["data"]); 54 | break; 55 | } 56 | } on Exception catch (e) { 57 | print("Exception $e"); 58 | } 59 | break; 60 | default: 61 | print('Ignoring invoke from native. This normally shouldn\'t happen.'); 62 | } 63 | } 64 | 65 | Future startListening(MultiUseCallback callback, 66 | String callbackName) async { 67 | _channel.setMethodCallHandler(_methodCallHandler); 68 | 69 | _callbacksById[callbackName] = callback; 70 | 71 | await _channel.invokeMethod("startListening", callbackName); 72 | 73 | return () { 74 | _channel.invokeMethod("cancelListening", callbackName); 75 | _callbacksById.remove(callbackName); 76 | }; 77 | } 78 | 79 | Future startListeningToUDL(UDLCallback callback, 80 | String callbackName) async { 81 | _channel.setMethodCallHandler(_methodCallHandler); 82 | 83 | _udlCallback = callback; 84 | 85 | await _channel.invokeMethod("startListening", callbackName); 86 | 87 | return () { 88 | _channel.invokeMethod("cancelListening", callbackName); 89 | _callbacksById.remove(callbackName); 90 | }; 91 | } 92 | -------------------------------------------------------------------------------- /lib/src/udl/deep_link_result.dart: -------------------------------------------------------------------------------- 1 | part of appsflyer_sdk; 2 | 3 | 4 | class DeepLinkResult { 5 | final Error? _error; 6 | final DeepLink? _deepLink; 7 | final Status _status; 8 | 9 | DeepLinkResult(this._error, this._deepLink, this._status); 10 | 11 | Error? get error => _error; 12 | 13 | DeepLink? get deepLink => _deepLink; 14 | 15 | Status get status => _status; 16 | 17 | DeepLinkResult.fromJson(Map json) 18 | : _error = json['error'], 19 | _status = json['status'], 20 | _deepLink = json['deepLink']; 21 | 22 | Map toJson() => 23 | { 24 | 'status': _status.toShortString(), 25 | 'error': _error?.toShortString(), 26 | 'deepLink': _deepLink?.clickEvent, 27 | }; 28 | 29 | @override 30 | String toString() { 31 | return "DeepLinkResult:${jsonEncode(toJson())}"; 32 | } 33 | 34 | 35 | } 36 | 37 | enum Error { 38 | TIMEOUT, 39 | NETWORK, 40 | HTTP_STATUS_CODE, 41 | UNEXPECTED, 42 | DEVELOPER_ERROR 43 | } 44 | enum Status { 45 | FOUND, 46 | NOT_FOUND, 47 | ERROR, 48 | PARSE_ERROR 49 | } 50 | 51 | extension ParseStatusToString on Status { 52 | String toShortString() { 53 | return this.toString().split('.').last; 54 | } 55 | } 56 | 57 | extension ParseErrorToString on Error { 58 | String toShortString() { 59 | return this.toString().split('.').last; 60 | } 61 | } 62 | 63 | extension ParseEnumFromString on String { 64 | Status? statusFromString() { 65 | return Status.values.firstWhere( 66 | (s) => _describeEnum(s) == this, orElse: null); 67 | } 68 | 69 | Error? errorFromString() { 70 | return Error.values.firstWhere((e) => _describeEnum(e) == this, 71 | orElse: null); 72 | } 73 | 74 | String _describeEnum(Object enumEntry) { 75 | final String description = enumEntry.toString(); 76 | final int indexOfDot = description.indexOf('.'); 77 | assert( 78 | indexOfDot != -1 && indexOfDot < description.length - 1, 79 | 'The provided object "$enumEntry" is not an enum.', 80 | ); 81 | return description.substring(indexOfDot + 1); 82 | } 83 | } -------------------------------------------------------------------------------- /lib/src/udl/deeplink.dart: -------------------------------------------------------------------------------- 1 | part of appsflyer_sdk; 2 | 3 | 4 | class DeepLink{ 5 | 6 | final Map _clickEvent; 7 | 8 | DeepLink(this._clickEvent); 9 | 10 | Map get clickEvent => _clickEvent; 11 | 12 | String? getStringValue(String key) { 13 | return _clickEvent[key] as String?; 14 | } 15 | 16 | String? get deepLinkValue => _clickEvent["deep_link_value"] as String?; 17 | 18 | 19 | String? get matchType => _clickEvent["match_type"] as String?; 20 | 21 | 22 | String? get clickHttpReferrer => _clickEvent["click_http_referrer"] as String?; 23 | 24 | 25 | String? get mediaSource => _clickEvent["media_source"] as String?; 26 | 27 | 28 | String? get campaign => _clickEvent["campaign"] as String?; 29 | 30 | 31 | String? get campaignId => _clickEvent["campaign_id"] as String?; 32 | 33 | 34 | String? get afSub1 => _clickEvent["af_sub1"] as String?; 35 | 36 | 37 | String? get afSub2 => _clickEvent["af_sub2"] as String?; 38 | 39 | 40 | String? get afSub3 => _clickEvent["af_sub3"] as String?; 41 | 42 | 43 | String? get afSub4 => _clickEvent["af_sub4"] as String?; 44 | 45 | 46 | String? get afSub5 => _clickEvent["af_sub5"] as String?; 47 | 48 | 49 | bool? get isDeferred => _clickEvent["is_deferred"] as bool?; 50 | 51 | @override 52 | String toString() { 53 | return 'DeepLink: ${jsonEncode(_clickEvent)}'; 54 | } 55 | } -------------------------------------------------------------------------------- /local.properties: -------------------------------------------------------------------------------- 1 | ## This file must *NOT* be checked into Version Control Systems, 2 | # as it contains information specific to your local configuration. 3 | # 4 | # Location of the SDK. This is only used by Gradle. 5 | # For customization when using a Version Control System, please read the 6 | # header note. 7 | #Thu Aug 24 14:09:09 IDT 2023 8 | sdk.dir=/Users/amitlevy/Library/Android/sdk 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flutter_appsflyer_sdk", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "https://github.com/AppsFlyerSDK/flutter_appsflyer_sdk.git", 6 | "author": "ShaharAF ", 7 | "license": "MIT", 8 | "dependencies": { 9 | "lcov2badge": "^0.1.2" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: appsflyer_sdk 2 | description: A Flutter plugin for AppsFlyer SDK. Supports iOS and Android. 3 | version: 6.16.2 4 | 5 | homepage: https://github.com/AppsFlyerSDK/flutter_appsflyer_sdk 6 | 7 | environment: 8 | sdk: '>=2.17.0 <4.0.0' 9 | flutter: ">=1.10.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | test: ^1.16.5 19 | mockito: ^5.4.4 20 | effective_dart: ^1.3.0 21 | 22 | 23 | flutter: 24 | plugin: 25 | platforms: 26 | android: 27 | package: com.appsflyer.appsflyersdk 28 | pluginClass: AppsflyerSdkPlugin 29 | ios: 30 | pluginClass: AppsflyerSdkPlugin 31 | package: com.appsflyer.appsflyersdk 32 | -------------------------------------------------------------------------------- /test/appsflyer_sdk_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:appsflyer_sdk/appsflyer_sdk.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | void main() { 6 | TestWidgetsFlutterBinding.ensureInitialized(); 7 | 8 | late AppsflyerSdk instance; 9 | String selectedMethod = ""; 10 | const MethodChannel methodChannel = MethodChannel('af-api'); 11 | const MethodChannel callbacksChannel = MethodChannel('callbacks'); 12 | const EventChannel eventChannel = EventChannel('af-events'); 13 | const MethodChannel eventMethodChannel = MethodChannel('af-events'); 14 | 15 | setUp(() { 16 | //test map options way 17 | instance = AppsflyerSdk.private(methodChannel, eventChannel, 18 | mapOptions: {'afDevKey': 'sdfhj2342cx'}); 19 | 20 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger 21 | .setMockMethodCallHandler(methodChannel, (methodCall) async { 22 | String method = methodCall.method; 23 | switch (method) { 24 | case 'initSdk': 25 | case 'setOneLinkCustomDomain': 26 | case 'logCrossPromotionAndOpenStore': 27 | case 'logCrossPromotionImpression': 28 | case 'setAppInviteOneLinkID': 29 | case 'generateInviteLink': 30 | case 'setSharingFilterForAllPartners': 31 | case 'setSharingFilter': 32 | case 'getSDKVersion': 33 | case 'getAppsFlyerUID': 34 | case 'validateAndLogInAppAndroidPurchase': 35 | case 'setMinTimeBetweenSessions': 36 | case 'getHostPrefix': 37 | case 'getHostName': 38 | case 'setCollectIMEI': 39 | case 'setCollectAndroidId': 40 | case 'setUserEmails': 41 | case 'setAdditionalData': 42 | case 'waitForCustomerUserId': 43 | case 'setCustomerUserId': 44 | case 'setAndroidIdData': 45 | case 'setImeiData': 46 | case 'updateServerUninstallToken': 47 | case 'stop': 48 | case 'setIsUpdate': 49 | case 'setCurrencyCode': 50 | case 'setHost': 51 | case 'logEvent': 52 | case 'setOutOfStore': 53 | case 'getOutOfStore': 54 | case 'logAdRevenue': 55 | case 'setConsentData': 56 | case 'enableTCFDataCollection': 57 | case 'setDisableNetworkData': 58 | case 'setPartnerData': 59 | case 'setResolveDeepLinkURLs': 60 | case 'setPushNotification': 61 | case 'sendPushNotificationData': 62 | case 'enableFacebookDeferredApplinks': 63 | case 'disableSKAdNetwork': 64 | case 'setDisableAdvertisingIdentifiers': 65 | selectedMethod = method; 66 | break; 67 | } 68 | return null; 69 | }); 70 | 71 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger 72 | .setMockMethodCallHandler(eventMethodChannel, (methodCall) async { 73 | String method = methodCall.method; 74 | if (method == 'listen') { 75 | selectedMethod = method; 76 | } 77 | return null; 78 | }); 79 | }); 80 | 81 | test('check initSdk call', () async { 82 | await instance.initSdk( 83 | registerConversionDataCallback: true, 84 | registerOnAppOpenAttributionCallback: true, 85 | registerOnDeepLinkingCallback: false); 86 | 87 | expect('initSdk', selectedMethod); 88 | }); 89 | 90 | group('AppsFlyerSdk', () { 91 | setUp(() { 92 | selectedMethod = ""; 93 | }); 94 | 95 | tearDown(() { 96 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger 97 | .setMockMethodCallHandler(methodChannel, null); 98 | }); 99 | 100 | test('check logEvent call', () async { 101 | await instance.logEvent("eventName", {"key": "val"}); 102 | 103 | expect(selectedMethod, 'logEvent'); 104 | }); 105 | 106 | test('check setHost call', () async { 107 | instance.setHost("", ""); 108 | 109 | expect(selectedMethod, 'setHost'); 110 | }); 111 | 112 | test('check setCurrencyCode call', () async { 113 | instance.setCurrencyCode("currencyCode"); 114 | 115 | expect(selectedMethod, 'setCurrencyCode'); 116 | }); 117 | 118 | test('check setIsUpdate call', () async { 119 | instance.setIsUpdate(true); 120 | 121 | expect(selectedMethod, 'setIsUpdate'); 122 | }); 123 | 124 | test('check stop call', () async { 125 | instance.stop(true); 126 | 127 | expect(selectedMethod, 'stop'); 128 | }); 129 | 130 | test('check updateServerUninstallToken call', () async { 131 | instance.updateServerUninstallToken("token"); 132 | 133 | expect(selectedMethod, 'updateServerUninstallToken'); 134 | }); 135 | 136 | test('check setOneLinkCustomDomain call', () async { 137 | instance.setOneLinkCustomDomain(["brandDomains"]); 138 | 139 | expect(selectedMethod, 'setOneLinkCustomDomain'); 140 | }); 141 | 142 | test('check logCrossPromotionAndOpenStore call', () async { 143 | instance.logCrossPromotionAndOpenStore("appId", "campaign", null); 144 | 145 | expect(selectedMethod, 'logCrossPromotionAndOpenStore'); 146 | }); 147 | 148 | test('check logCrossPromotionImpression call', () async { 149 | instance.logCrossPromotionImpression("appId", "campaign", null); 150 | 151 | expect(selectedMethod, 'logCrossPromotionImpression'); 152 | }); 153 | 154 | test('check setAppInviteOneLinkID call', () async { 155 | instance.setAppInviteOneLinkID("oneLinkID", (msg) {}); 156 | 157 | expect(selectedMethod, 'setAppInviteOneLinkID'); 158 | }); 159 | 160 | test('check generateInviteLink call', () async { 161 | instance.generateInviteLink(null, (msg) {}, (err) {}); 162 | 163 | expect(selectedMethod, 'generateInviteLink'); 164 | }); 165 | 166 | test('check getSDKVersion call', () async { 167 | instance.getSDKVersion(); 168 | 169 | expect(selectedMethod, 'getSDKVersion'); 170 | }); 171 | 172 | test('check getAppsFlyerUID call', () async { 173 | instance.getAppsFlyerUID(); 174 | 175 | expect(selectedMethod, 'getAppsFlyerUID'); 176 | }); 177 | 178 | test('check validateAndLogInAppPurchase call', () async { 179 | instance.validateAndLogInAppAndroidPurchase( 180 | "publicKey", "signature", "purchaseData", "price", "currency", null); 181 | 182 | expect(selectedMethod, 'validateAndLogInAppAndroidPurchase'); 183 | }); 184 | 185 | test('check setMinTimeBetweenSessions call', () async { 186 | instance.setMinTimeBetweenSessions(1); 187 | 188 | expect(selectedMethod, 'setMinTimeBetweenSessions'); 189 | }); 190 | 191 | test('check getHostPrefix call', () async { 192 | instance.getHostPrefix(); 193 | 194 | expect(selectedMethod, 'getHostPrefix'); 195 | }); 196 | 197 | test('check getHostName call', () async { 198 | instance.getHostName(); 199 | 200 | expect(selectedMethod, 'getHostName'); 201 | }); 202 | 203 | test('check setCollectIMEI call', () async { 204 | instance.setCollectIMEI(true); 205 | 206 | expect(selectedMethod, 'setCollectIMEI'); 207 | }); 208 | 209 | test('check setCollectAndroidId call', () async { 210 | instance.setCollectAndroidId(true); 211 | 212 | expect(selectedMethod, 'setCollectAndroidId'); 213 | }); 214 | 215 | test('check setUserEmails call', () async { 216 | instance.setUserEmails(["emails"], EmailCryptType.EmailCryptTypeNone); 217 | 218 | expect(selectedMethod, 'setUserEmails'); 219 | }); 220 | 221 | test('check setAdditionalData call', () async { 222 | instance.setAdditionalData(null); 223 | 224 | expect(selectedMethod, 'setAdditionalData'); 225 | }); 226 | 227 | test('check waitForCustomerUserId call', () async { 228 | instance.waitForCustomerUserId(false); 229 | 230 | expect(selectedMethod, 'waitForCustomerUserId'); 231 | }); 232 | 233 | test('check setCustomerUserId call', () async { 234 | instance.setCustomerUserId("id"); 235 | 236 | expect(selectedMethod, 'setCustomerUserId'); 237 | }); 238 | 239 | test('check setImeiData call', () async { 240 | instance.setImeiData("imei"); 241 | 242 | expect(selectedMethod, 'setImeiData'); 243 | }); 244 | 245 | test('check setAndroidIdData call', () async { 246 | instance.setAndroidIdData("androidId"); 247 | 248 | expect(selectedMethod, 'setAndroidIdData'); 249 | }); 250 | 251 | test('check getOutOfStore call', () async { 252 | instance.getOutOfStore(); 253 | 254 | expect(selectedMethod, 'getOutOfStore'); 255 | }); 256 | 257 | test('check setOutOfStore call', () async { 258 | instance.setOutOfStore("source"); 259 | 260 | expect(selectedMethod, 'setOutOfStore'); 261 | }); 262 | 263 | test('check logAdRevenue call', () async { 264 | final adRevenueData = AdRevenueData( 265 | monetizationNetwork: 'GoogleAdMob', 266 | mediationNetwork: AFMediationNetwork.googleAdMob.value, 267 | currencyIso4217Code: 'USD', 268 | revenue: 1.23, 269 | additionalParameters: { 270 | 'adUnitId': 'ca-app-pub-XXXX/YYYY', 271 | 'ad_network_click_id': '12345' 272 | }); 273 | instance.logAdRevenue(adRevenueData); 274 | 275 | expect(selectedMethod, 'logAdRevenue'); 276 | }); 277 | 278 | test('check setConsentData call', () async { 279 | final consentData = AppsFlyerConsent.forGDPRUser( 280 | hasConsentForDataUsage: true, 281 | hasConsentForAdsPersonalization: true, 282 | ); 283 | instance.setConsentData(consentData); 284 | 285 | expect(selectedMethod, 'setConsentData'); 286 | }); 287 | 288 | test('check enableTCFDataCollection call', () async { 289 | instance.enableTCFDataCollection(true); 290 | 291 | expect(selectedMethod, 'enableTCFDataCollection'); 292 | }); 293 | 294 | test('check setDisableNetworkData call', () async { 295 | instance.setDisableNetworkData(true); 296 | 297 | expect(selectedMethod, 'setDisableNetworkData'); 298 | }); 299 | 300 | test('check setPartnerData call', () async { 301 | instance.setPartnerData('partnerId', {'key': 'value'}); 302 | 303 | expect(selectedMethod, 'setPartnerData'); 304 | }); 305 | 306 | test('check setResolveDeepLinkURLs call', () async { 307 | instance.setResolveDeepLinkURLs(['https://example.com']); 308 | 309 | expect(selectedMethod, 'setResolveDeepLinkURLs'); 310 | }); 311 | 312 | test('check setPushNotification call', () async { 313 | instance.setPushNotification(true); 314 | 315 | expect(selectedMethod, 'setPushNotification'); 316 | }); 317 | 318 | test('check sendPushNotificationData call', () async { 319 | instance.sendPushNotificationData({'key': 'value'}); 320 | 321 | expect(selectedMethod, 'sendPushNotificationData'); 322 | }); 323 | 324 | test('check enableFacebookDeferredApplinks call', () async { 325 | instance.enableFacebookDeferredApplinks(true); 326 | 327 | expect(selectedMethod, 'enableFacebookDeferredApplinks'); 328 | }); 329 | 330 | test('check disableSKAdNetwork call', () async { 331 | instance.disableSKAdNetwork(true); 332 | 333 | expect(selectedMethod, 'disableSKAdNetwork'); 334 | }); 335 | 336 | test('check setDisableAdvertisingIdentifiers call', () async { 337 | instance.setDisableAdvertisingIdentifiers(true); 338 | 339 | expect(selectedMethod, 'setDisableAdvertisingIdentifiers'); 340 | }); 341 | }); 342 | } 343 | --------------------------------------------------------------------------------