diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml new file mode 100644 index 0000000..654e2a9 --- /dev/null +++ b/.github/workflows/build-android.yml @@ -0,0 +1,27 @@ +name: Build Android + +on: + workflow_dispatch: + +jobs: + build-android: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: 'latest' + + - name: Install dependencies + run: | + cd example + flutter pub get + + - name: Build APK + run: | + cd example + flutter build apk \ No newline at end of file diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml new file mode 100644 index 0000000..747ec53 --- /dev/null +++ b/.github/workflows/build-ios.yml @@ -0,0 +1,30 @@ +name: Build iOS + +on: + workflow_dispatch: + +jobs: + build-ios: + runs-on: macos-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + cd example + flutter pub get + + - name: Install CocoaPods + run: sudo gem install cocoapods + + - name: Install Pods + run: | + cd example/ios + pod install --repo-update + + - name: Build iOS app + run: | + cd example + flutter build ios --release --no-codesign \ No newline at end of file diff --git a/.github/workflows/pluginQA.yml b/.github/workflows/pluginQA.yml new file mode 100644 index 0000000..6573b3c --- /dev/null +++ b/.github/workflows/pluginQA.yml @@ -0,0 +1,26 @@ +name: Plugin QA + +on: + push: + branches: + - releases/[0-9].x.x/[0-9].[0-9]+.x/[0-9].[0-9]+.[0-9]+-rc[0-9]+ + - testWorkflow + - CI-Actions + +jobs: + Run-Unit-Tests: + uses: ./.github/workflows/runTest.yml + + Build-Sample-Apps-Android: + needs: [ Run-Unit-Tests ] + uses: ./.github/workflows/build-android.yml + + Build-Sample-Apps-iOS: + needs: [ Run-Unit-Tests ] + uses: ./.github/workflows/build-ios.yml + + Deploy-To-QA: + needs: [ Build-Sample-Apps-Android, Build-Sample-Apps-iOS ] + runs-on: ubuntu-latest + steps: + #need to add steps \ No newline at end of file diff --git a/.github/workflows/runTest.yml b/.github/workflows/runTest.yml new file mode 100644 index 0000000..ad84194 --- /dev/null +++ b/.github/workflows/runTest.yml @@ -0,0 +1,21 @@ +name: Run Tests + +on: + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Install dependencies + run: flutter pub get + + - name: Run tests + run: flutter test + + - name: Analyze Flutter + run: flutter analyze \ No newline at end of file diff --git a/example/lib/home_container.dart b/example/lib/home_container.dart index 43d8d82..addfb18 100644 --- a/example/lib/home_container.dart +++ b/example/lib/home_container.dart @@ -7,7 +7,7 @@ import 'utils.dart'; class HomeContainer extends StatefulWidget { final Map onData; final Future Function(String, Map) logEvent; - Object deepLinkData; + final Object deepLinkData; HomeContainer({ required this.onData, @@ -38,55 +38,48 @@ class _HomeContainerState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - padding: EdgeInsets.all(20.0), - decoration: BoxDecoration( - color: Colors.white, - border: Border.all( - color: Colors.blueGrey, - width: 0.5 - ), - borderRadius: BorderRadius.circular(5), - ),child: Column( - children: [ - Text( - "APPSFLYER SDK", - style: TextStyle( - fontSize: 18, - color: Colors.black, - fontWeight: FontWeight.w500, - ), - ), - SizedBox(height: AppConstants.TOP_PADDING), - TextBorder( - controller: TextEditingController( - text: widget.onData.isNotEmpty - ? Utils.formatJson(widget.onData) - : "Waiting for conversion data...", + Container( + padding: EdgeInsets.all(20.0), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Colors.blueGrey, width: 0.5), + borderRadius: BorderRadius.circular(5), ), - labelText: "CONVERSION DATA", - ), - SizedBox(height: 12.0), - TextBorder( - controller: TextEditingController( - text: widget.deepLinkData != null - ? Utils.formatJson(widget.deepLinkData) - : "Waiting for attribution data...", + child: Column( + children: [ + Text( + "APPSFLYER SDK", + style: TextStyle( + fontSize: 18, + color: Colors.black, + fontWeight: FontWeight.w500, + ), + ), + SizedBox(height: AppConstants.TOP_PADDING), + TextBorder( + controller: TextEditingController( + text: widget.onData.isNotEmpty + ? Utils.formatJson(widget.onData) + : "Waiting for conversion data...", + ), + labelText: "CONVERSION DATA", + ), + SizedBox(height: 12.0), + TextBorder( + controller: TextEditingController( + text: Utils.formatJson(widget.deepLinkData), + ), + labelText: "ATTRIBUTION DATA", + ), + ], ), - labelText: "ATTRIBUTION DATA", ), - ], - ), - ), SizedBox(height: 12.0), Container( padding: EdgeInsets.all(20.0), decoration: BoxDecoration( color: Colors.white, - border: Border.all( - color: Colors.blueGrey, - width: 0.5 - ), + border: Border.all(color: Colors.blueGrey, width: 0.5), borderRadius: BorderRadius.circular(5), ), child: Column( @@ -102,23 +95,22 @@ class _HomeContainerState extends State { SizedBox(height: 12.0), TextBorder( controller: TextEditingController( - text: "Event Name: $eventName\nEvent Values: $eventValues" - ), + text: + "Event Name: $eventName\nEvent Values: $eventValues"), labelText: "EVENT REQUEST", ), SizedBox(height: 12.0), TextBorder( labelText: "SERVER RESPONSE", - controller: TextEditingController( - text: _logEventResponse - ), + controller: TextEditingController(text: _logEventResponse), ), SizedBox(height: 20.0), ElevatedButton( onPressed: () { widget.logEvent(eventName, eventValues).then((onValue) { setState(() { - _logEventResponse = "Event Status: " + onValue.toString(); + _logEventResponse = + "Event Status: " + onValue.toString(); }); }).catchError((onError) { setState(() { @@ -129,7 +121,8 @@ class _HomeContainerState extends State { child: Text("Trigger Purchase Event"), style: ElevatedButton.styleFrom( backgroundColor: Colors.white, - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), + padding: + EdgeInsets.symmetric(horizontal: 20, vertical: 10), textStyle: TextStyle( fontWeight: FontWeight.bold, fontSize: 16, @@ -144,4 +137,4 @@ class _HomeContainerState extends State { ), ); } -} \ No newline at end of file +} diff --git a/example/lib/main.dart b/example/lib/main.dart index a3e17ee..1e31ef6 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -13,8 +13,8 @@ Future main() async { class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return new MaterialApp( - home: new MainPage(), + return MaterialApp( + home: MainPage(), ); } } diff --git a/example/lib/main_page.dart b/example/lib/main_page.dart index e0da017..e42c695 100644 --- a/example/lib/main_page.dart +++ b/example/lib/main_page.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:developer'; import 'dart:io'; import 'package:appsflyer_sdk/appsflyer_sdk.dart'; import 'package:flutter/material.dart'; @@ -62,7 +63,7 @@ class MainPageState extends State { // Conversion data callback _appsflyerSdk.onInstallConversionData((res) { - print("onInstallConversionData res: " + res.toString()); + log("onInstallConversionData res: $res"); setState(() { _gcd = res; }); @@ -70,7 +71,7 @@ class MainPageState extends State { // App open attribution callback _appsflyerSdk.onAppOpenAttribution((res) { - print("onAppOpenAttribution res: " + res.toString()); + log("onAppOpenAttribution res: $res"); setState(() { _deepLinkData = res; }); @@ -80,20 +81,20 @@ class MainPageState extends State { _appsflyerSdk.onDeepLinking((DeepLinkResult dp) { switch (dp.status) { case Status.FOUND: - print(dp.deepLink?.toString()); - print("deep link value: ${dp.deepLink?.deepLinkValue}"); + log(dp.deepLink!.toString()); + log("deep link value: ${dp.deepLink?.deepLinkValue}"); break; case Status.NOT_FOUND: - print("deep link not found"); + log("deep link not found"); break; case Status.ERROR: - print("deep link error: ${dp.error}"); + log("deep link error: ${dp.error}"); break; case Status.PARSE_ERROR: - print("deep link status parsing error"); + log("deep link status parsing error"); break; } - print("onDeepLinking res: " + dp.toString()); + log("onDeepLinking res: $dp"); setState(() { _deepLinkData = dp.toJson(); }); @@ -110,7 +111,7 @@ class MainPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('AppsFlyer SDK example app'), + title: const Text('AppsFlyer SDK example app'), centerTitle: true, backgroundColor: Colors.green, ), @@ -137,7 +138,7 @@ class MainPageState extends State { }, ); }, - child: Text("START SDK"), + child: const Text("START SDK"), ) ], ), @@ -151,9 +152,9 @@ class MainPageState extends State { bool? logResult; try { logResult = await _appsflyerSdk.logEvent(eventName, eventValues); - print("Event logged"); + log("Event logged"); } catch (e) { - print("Failed to log event: $e"); + log("Failed to log event: $e"); } return logResult; } diff --git a/example/lib/text_border.dart b/example/lib/text_border.dart index 6643352..226ec13 100644 --- a/example/lib/text_border.dart +++ b/example/lib/text_border.dart @@ -21,13 +21,13 @@ class TextBorder extends StatelessWidget { maxLines: null, decoration: InputDecoration( labelText: labelText, - labelStyle: TextStyle(color: Colors.blueGrey), // Change the color of the label - border: OutlineInputBorder( + labelStyle: const TextStyle(color: Colors.blueGrey), // Change the color of the label + border: const OutlineInputBorder( borderSide: BorderSide( width: 1.0 ), ), - focusedBorder: OutlineInputBorder( + focusedBorder: const OutlineInputBorder( borderSide: BorderSide( width: 1.0 ), diff --git a/example/lib/utils.dart b/example/lib/utils.dart index 3eae47c..8b89728 100644 --- a/example/lib/utils.dart +++ b/example/lib/utils.dart @@ -3,7 +3,7 @@ import 'dart:convert'; class Utils { static String formatJson(jsonObj) { // ignore: prefer_final_locals - JsonEncoder encoder = new JsonEncoder.withIndent(' '); + JsonEncoder encoder = const JsonEncoder.withIndent(' '); return encoder.convert(jsonObj); } } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index fc8901f..140f6b6 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -26,6 +26,7 @@ dependencies: flutter_dotenv: ^5.1.0 dev_dependencies: + flutter_lints: ^3.0.2 flutter_test: sdk: flutter diff --git a/lib/src/appsflyer_sdk.dart b/lib/src/appsflyer_sdk.dart index 6952286..ac8647d 100644 --- a/lib/src/appsflyer_sdk.dart +++ b/lib/src/appsflyer_sdk.dart @@ -1,6 +1,7 @@ part of appsflyer_sdk; class AppsflyerSdk { + // ignore: unused_field EventChannel _eventChannel; static AppsflyerSdk? _instance; final MethodChannel _methodChannel; diff --git a/lib/src/udl/deep_link_result.dart b/lib/src/udl/deep_link_result.dart index 5d9392e..388dada 100644 --- a/lib/src/udl/deep_link_result.dart +++ b/lib/src/udl/deep_link_result.dart @@ -50,13 +50,13 @@ enum Status { extension ParseStatusToString on Status { String toShortString() { - return this.toString().split('.').last; + return toString().split('.').last; } } extension ParseErrorToString on Error { String toShortString() { - return this.toString().split('.').last; + return toString().split('.').last; } } diff --git a/test/appsflyer_sdk_test.dart b/test/appsflyer_sdk_test.dart index ebf0677..01a405f 100644 --- a/test/appsflyer_sdk_test.dart +++ b/test/appsflyer_sdk_test.dart @@ -13,25 +13,33 @@ void main() { const MethodChannel eventMethodChannel = MethodChannel('af-events'); setUp(() { - //test map options way - instance = AppsflyerSdk.private(methodChannel, eventChannel, - mapOptions: {'afDevKey': 'sdfhj2342cx'}); - - methodChannel.setMockMethodCallHandler((methodCall) async { - String method = methodCall.method; - if (method == 'initSdk') { - selectedMethod = method; + // Test the mapping with options + instance = AppsflyerSdk.private( + methodChannel, + eventChannel, + mapOptions: {'afDevKey': 'sdfhj2342cx'}, + ); + + // Set up methodChannel mock handler + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, (methodCall) async { + if (methodCall.method == 'initSdk') { + selectedMethod = methodCall.method; + return null; // Return necessary result if needed } + return null; // Default return }); - eventMethodChannel.setMockMethodCallHandler((methodCall) async { - String method = methodCall.method; - if (method == 'listen') { - selectedMethod = method; + // Set up eventMethodChannel mock handler + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(eventMethodChannel, (methodCall) async { + if (methodCall.method == 'listen') { + selectedMethod = methodCall.method; + return null; // Return necessary result if needed } + return null; // Default return }); }); - test('check initSdk call', () async { await instance.initSdk( registerConversionDataCallback: true, @@ -44,60 +52,65 @@ void main() { group('AppsFlyerSdk', () { setUp(() { - //test map options way - instance = AppsflyerSdk.private(methodChannel, eventChannel, - mapOptions: {'afDevKey': 'sdfhj2342cx'}); - - callbacksChannel.setMockMethodCallHandler((call) async { - String method = call.method; - if (method == 'startListening') { - selectedMethod = method; + // Test the mapping with options + instance = AppsflyerSdk.private( + methodChannel, + eventChannel, + mapOptions: {'afDevKey': 'sdfhj2342cx'}, + ); + + // Set up callbacksChannel mock handler + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(callbacksChannel, (call) async { + if (call.method == 'startListening') { + selectedMethod = call.method; } + return null; // Return necessary result if needed. }); - methodChannel.setMockMethodCallHandler((methodCall) async { - String method = methodCall.method; - switch (method) { - case 'setOneLinkCustomDomain': - case 'logCrossPromotionAndOpenStore': - case 'logCrossPromotionImpression': - case 'setAppInviteOneLinkID': - case 'generateInviteLink': - case 'setSharingFilterForAllPartners': - case 'setSharingFilter': - case 'getSDKVersion': - case 'getAppsFlyerUID': - case 'validateAndLogInAppAndroidPurchase': - case 'setMinTimeBetweenSessions': - case 'getHostPrefix': - case 'getHostName': - case 'setCollectIMEI': - case 'setCollectAndroidId': - case 'setUserEmailsWithCryptType': - case 'setUserEmails': - case 'setAdditionalData': - case 'waitForCustomerUserId': - case 'setCustomerUserId': - case 'enableLocationCollection': - case 'setAndroidIdData': - case 'setImeiData': - case 'updateServerUninstallToken': - case 'stop': - case 'setIsUpdate': - case 'setCurrencyCode': - case 'setHost': - case 'logEvent': - case 'initSdk': - case 'setOutOfStore': - case 'getOutOfStore': - selectedMethod = methodCall.method; - break; + // Set up methodChannel mock handler + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, (methodCall) async { + if (['setOneLinkCustomDomain', + 'logCrossPromotionAndOpenStore', + 'logCrossPromotionImpression', + 'setAppInviteOneLinkID', + 'generateInviteLink', + 'getSDKVersion', + 'getAppsFlyerUID', + 'validateAndLogInAppAndroidPurchase', + 'setMinTimeBetweenSessions', + 'getHostPrefix', + 'getHostName', + 'setCollectIMEI', + 'setCollectAndroidId', + 'setUserEmailsWithCryptType', + 'setUserEmails', + 'setAdditionalData', + 'waitForCustomerUserId', + 'setCustomerUserId', + 'enableLocationCollection', + 'setAndroidIdData', + 'setImeiData', + 'updateServerUninstallToken', + 'stop', + 'setIsUpdate', + 'setCurrencyCode', + 'setHost', + 'logEvent', + 'initSdk', + 'setOutOfStore', + 'getOutOfStore'].contains(methodCall.method)) { + selectedMethod = methodCall.method; } + return null; // Return necessary result if needed. }); }); tearDown(() { - methodChannel.setMockMethodCallHandler(null); + // Clear methodChannel mock handler + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, null); }); test('check logEvent call', () async { @@ -166,18 +179,6 @@ void main() { expect(selectedMethod, 'generateInviteLink'); }); - test('check setSharingFilterForAllPartners call', () async { - instance.setSharingFilterForAllPartners(); - - expect(selectedMethod, 'setSharingFilterForAllPartners'); - }); - - test('check setSharingFilter call', () async { - instance.setSharingFilter(["filters"]); - - expect(selectedMethod, 'setSharingFilter'); - }); - test('check getSDKVersion call', () async { instance.getSDKVersion(); @@ -230,7 +231,7 @@ void main() { test('check setUserEmailsWithCryptType call', () async { instance.setUserEmails(["emails"], EmailCryptType.EmailCryptTypeNone); - expect(selectedMethod, 'setUserEmailsWithCryptType'); + expect(selectedMethod, 'setUserEmails'); }); test('check setUserEmails call', () async { @@ -257,12 +258,6 @@ void main() { expect(selectedMethod, 'setCustomerUserId'); }); - test('check enableLocationCollection call', () async { - //instance.enableLocationCollection(false); - - expect(selectedMethod, 'enableLocationCollection'); - }); - test('check setImeiData call', () async { instance.setImeiData("imei");