diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e41b0c2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,294 @@ +# ============================================================================= +# CI Workflow - Continuous Integration for Pull Requests and Pushes +# ============================================================================= +# +# Purpose: Validates code quality, runs tests, and builds example apps for +# both Android and iOS platforms on every PR and push to development/master. +# +# What it does: +# 1. Runs Dart/Flutter unit tests +# 2. Builds Android example app (APK) +# 3. Builds iOS example app (simulator + no-codesign IPA) +# 4. Caches dependencies for faster subsequent runs +# +# Triggers: +# - Pull requests to development or master branches +# - Direct pushes to development or master branches +# - Manual workflow dispatch for testing +# +# ============================================================================= + +name: CI - Build & Test + +on: + # Trigger on pull requests targeting main branches + pull_request: + branches: + - development + - master + paths-ignore: + - '**.md' + - 'doc/**' + - 'assets/**' + - '.github/workflows/close_inactive_issues.yml' + - '.github/workflows/responseToSupportIssue*.yml' + + # Trigger on direct pushes to main branches + push: + branches: + - development + - master + paths-ignore: + - '**.md' + - 'doc/**' + - 'assets/**' + + # Allow manual triggering for testing + workflow_dispatch: + + # Allow this workflow to be called by other workflows (reusable workflow) + workflow_call: + +# Ensure only one CI run per PR/branch at a time +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + # =========================================================================== + # Job 1: Run Unit Tests + # =========================================================================== + # Runs all Dart/Flutter unit tests for the plugin + # Uses: Ubuntu runner (fastest and free for public repos) + # =========================================================================== + + test: + name: ๐Ÿงช Run Unit Tests + runs-on: ubuntu-latest + + steps: + # Step 1: Checkout the repository code + - name: ๐Ÿ“ฅ Checkout repository + uses: actions/checkout@v4 + + # Step 2: Set up Flutter SDK + # Uses subosito/flutter-action which caches Flutter SDK automatically + - name: ๐Ÿ”ง Setup Flutter SDK + uses: subosito/flutter-action@v2 + with: + channel: 'stable' # Use latest stable Flutter + cache: true # Cache Flutter SDK for faster runs + + # Step 3: Verify Flutter installation + - name: โ„น๏ธ Display Flutter version + run: | + flutter --version + dart --version + + # Step 4: Get plugin dependencies + # This installs all packages defined in pubspec.yaml + - name: ๐Ÿ“ฆ Install plugin dependencies + run: flutter pub get + + # Step 5: Analyze code for issues + # Checks for code quality issues, unused imports, etc. + # Only fails on actual errors, not info-level warnings + - name: ๐Ÿ” Analyze code + run: flutter analyze --no-fatal-infos --no-fatal-warnings + + # Step 6: Format check + # Ensures code follows Dart formatting standards + - name: ๐Ÿ’… Check code formatting + run: dart format --set-exit-if-changed . + + # Step 7: Run unit tests + # Executes all tests in the test/ directory + - name: ๐Ÿงช Run unit tests + run: flutter test --coverage + + # Step 8: Upload coverage report (optional) + # Useful for tracking code coverage over time + - name: ๐Ÿ“Š Upload coverage to Codecov (optional) + if: success() + uses: codecov/codecov-action@v4 + with: + files: ./coverage/lcov.info + fail_ci_if_error: false # Don't fail CI if coverage upload fails + + # =========================================================================== + # Job 2: Build Android Example App + # =========================================================================== + # Builds the example app for Android to ensure plugin integration works + # Uses: Ubuntu runner with Java 17 + # =========================================================================== + + build-android: + name: ๐Ÿค– Build Android Example + runs-on: ubuntu-latest + needs: test # Only run if tests pass + + steps: + # Step 1: Checkout code + - name: ๐Ÿ“ฅ Checkout repository + uses: actions/checkout@v4 + + # Step 2: Set up Java (required for Android builds) + # Android builds require JDK 17 for modern Gradle versions + - name: โ˜• Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' # Eclipse Temurin (formerly AdoptOpenJDK) + java-version: '17' + cache: 'gradle' # Cache Gradle dependencies + + # Step 3: Set up Flutter SDK + - name: ๐Ÿ”ง Setup Flutter SDK + uses: subosito/flutter-action@v2 + with: + channel: 'stable' + cache: true + + # Step 4: Display versions for debugging + - name: โ„น๏ธ Display versions + run: | + flutter --version + java -version + echo "JAVA_HOME: $JAVA_HOME" + + # Step 5: Get plugin dependencies + - name: ๐Ÿ“ฆ Install plugin dependencies + run: flutter pub get + + # Step 6: Get example app dependencies + - name: ๐Ÿ“ฆ Install example app dependencies + working-directory: example + run: flutter pub get + + # Step 7: Build Android APK (debug mode) + # This validates that the plugin integrates correctly with Android + - name: ๐Ÿ”จ Build Android APK (debug) + working-directory: example + run: flutter build apk --debug + + # Step 8: Build Android App Bundle (release mode, no signing) + # App Bundle is the preferred format for Play Store + - name: ๐Ÿ”จ Build Android App Bundle (release) + working-directory: example + run: flutter build appbundle --release + + # Step 9: Upload build artifacts (optional) + # Useful for manual testing or archiving + - name: ๐Ÿ“ค Upload APK artifact + if: success() + uses: actions/upload-artifact@v4 + with: + name: android-apk-debug + path: example/build/app/outputs/flutter-apk/app-debug.apk + retention-days: 7 # Keep for 7 days + + # =========================================================================== + # Job 3: Build iOS Example App + # =========================================================================== + # Builds the example app for iOS to ensure plugin integration works + # Uses: macOS runner (required for Xcode and iOS builds) + # Note: macOS runners consume 10x minutes on private repos + # =========================================================================== + + build-ios: + name: ๐ŸŽ Build iOS Example + runs-on: macos-14 # macOS 14 (Sonoma) with Xcode 15+ + needs: test # Only run if tests pass + + steps: + # Step 1: Checkout code + - name: ๐Ÿ“ฅ Checkout repository + uses: actions/checkout@v4 + + # Step 2: Set up Flutter SDK + - name: ๐Ÿ”ง Setup Flutter SDK + uses: subosito/flutter-action@v2 + with: + channel: 'stable' + cache: true + + # Step 3: Display versions + - name: โ„น๏ธ Display versions + run: | + flutter --version + xcodebuild -version + pod --version + + # Step 4: Get plugin dependencies + - name: ๐Ÿ“ฆ Install plugin dependencies + run: flutter pub get + + # Step 5: Get example app dependencies + - name: ๐Ÿ“ฆ Install example app dependencies + working-directory: example + run: flutter pub get + + # Step 6: Update CocoaPods repo (ensures latest pod specs) + # This can be slow, so we only update if needed + - name: ๐Ÿ”„ Update CocoaPods repo + working-directory: example/ios + run: pod repo update + + # Step 7: Install CocoaPods dependencies + # This installs native iOS dependencies including AppsFlyer SDK + - name: ๐Ÿ“ฆ Install CocoaPods dependencies + working-directory: example/ios + run: pod install + + # Step 8: Build for iOS Simulator (fastest iOS build) + # Validates that the plugin compiles for iOS + - name: ๐Ÿ”จ Build iOS for Simulator + working-directory: example + run: flutter build ios --simulator --debug + + # Step 9: Build iOS IPA without code signing (release mode) + # This validates a full release build without requiring certificates + - name: ๐Ÿ”จ Build iOS IPA (no codesign) + working-directory: example + run: flutter build ipa --release --no-codesign + + # Step 10: Upload build artifacts (optional) + - name: ๐Ÿ“ค Upload iOS build artifact + if: success() + uses: actions/upload-artifact@v4 + with: + name: ios-app-unsigned + path: example/build/ios/archive/Runner.xcarchive + retention-days: 7 + + # =========================================================================== + # Job 4: Summary Report + # =========================================================================== + # Provides a summary of all CI jobs + # =========================================================================== + + ci-summary: + name: ๐Ÿ“‹ CI Summary + runs-on: ubuntu-latest + needs: [test, build-android, build-ios] + if: always() # Run even if previous jobs fail + + steps: + - name: ๐Ÿ“Š Check CI Results + run: | + echo "===================================" + echo "CI Pipeline Summary" + echo "===================================" + echo "Test Job: ${{ needs.test.result }}" + echo "Android Build: ${{ needs.build-android.result }}" + echo "iOS Build: ${{ needs.build-ios.result }}" + echo "===================================" + + # Fail this job if any required job failed + if [[ "${{ needs.test.result }}" != "success" ]] || \ + [[ "${{ needs.build-android.result }}" != "success" ]] || \ + [[ "${{ needs.build-ios.result }}" != "success" ]]; then + echo "โŒ CI Pipeline Failed" + exit 1 + fi + + echo "โœ… CI Pipeline Passed Successfully" diff --git a/.github/workflows/production-release.yml b/.github/workflows/production-release.yml new file mode 100644 index 0000000..0db7841 --- /dev/null +++ b/.github/workflows/production-release.yml @@ -0,0 +1,594 @@ +# ============================================================================= +# Production Release Workflow - Publish to pub.dev +# ============================================================================= +# +# Purpose: Publishes the Flutter plugin to pub.dev after successful QA testing. +# This workflow is triggered when a release PR is merged to master. +# +# What it does: +# 1. Validates the merge is from a release branch +# 2. Runs full CI pipeline one final time +# 3. Publishes the plugin to pub.dev +# 4. Creates a GitHub release with release notes +# 5. Notifies team via Slack (placeholder for future) +# +# Prerequisites: +# - Release branch has been tested and approved +# - PR from release branch to master has been created and approved +# - All CI checks have passed +# +# Triggers: +# - Pull request closed (merged) to master branch from releases/** branches +# - Manual workflow dispatch (for republishing or testing) +# +# ============================================================================= + +name: Production Release - Publish to pub.dev + +on: + # Trigger when PR to master is merged + pull_request: + types: + - closed + branches: + - master + + # Allow manual triggering + workflow_dispatch: + inputs: + version: + description: 'Version to release (must match pubspec.yaml)' + required: true + type: string + skip_tests: + description: 'Skip CI tests (use with caution!)' + required: false + type: boolean + default: false + dry_run: + description: 'Dry run (do not actually publish)' + required: false + type: boolean + default: false + +# Ensure only one production release runs at a time +concurrency: + group: production-release + cancel-in-progress: false + +jobs: + # =========================================================================== + # Job 1: Validate Release + # =========================================================================== + # Ensures this is a valid release merge from a release branch + # =========================================================================== + + validate-release: + name: ๐Ÿ” Validate Release + runs-on: ubuntu-latest + + # Only run if PR was actually merged (not just closed) + if: github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true + + outputs: + version: ${{ steps.get-version.outputs.version }} + is_valid: ${{ steps.validate.outputs.is_valid }} + is_release_branch: ${{ steps.validate.outputs.is_release_branch }} + + steps: + - name: ๐Ÿ“ฅ Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: ๐Ÿ” Validate release source + id: validate + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "Manual workflow dispatch - skipping branch validation" + echo "is_release_branch=true" >> $GITHUB_OUTPUT + echo "is_valid=true" >> $GITHUB_OUTPUT + else + # Check if the merged PR came from a release branch + SOURCE_BRANCH="${{ github.event.pull_request.head.ref }}" + echo "Source branch: $SOURCE_BRANCH" + + if [[ $SOURCE_BRANCH =~ ^releases/ ]]; then + echo "โœ… Valid release branch: $SOURCE_BRANCH" + echo "is_release_branch=true" >> $GITHUB_OUTPUT + echo "is_valid=true" >> $GITHUB_OUTPUT + else + echo "โš ๏ธ Not a release branch: $SOURCE_BRANCH" + echo "Production release should only be triggered from release branches" + echo "is_release_branch=false" >> $GITHUB_OUTPUT + echo "is_valid=false" >> $GITHUB_OUTPUT + exit 1 + fi + fi + + - name: ๐Ÿ“ Get version from pubspec.yaml + id: get-version + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + VERSION="${{ github.event.inputs.version }}" + echo "Using manual version: $VERSION" + else + # Extract version from pubspec.yaml + VERSION=$(grep "^version:" pubspec.yaml | sed 's/version: //' | tr -d ' ') + echo "Extracted version from pubspec.yaml: $VERSION" + fi + + # Validate version format (X.Y.Z) + if [[ $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "โœ… Valid version format: $VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT + else + echo "โŒ Invalid version format: $VERSION" + echo "Expected format: X.Y.Z (e.g., 6.17.6)" + exit 1 + fi + + - name: ๐Ÿท๏ธ Check if tag already exists + run: | + VERSION="${{ steps.get-version.outputs.version }}" + + # Check if tag exists locally or remotely + if git rev-parse "$VERSION" >/dev/null 2>&1; then + echo "โš ๏ธ Tag $VERSION already exists locally" + + if [[ "${{ github.event.inputs.dry_run }}" == "true" ]]; then + echo "Dry run mode - continuing anyway" + else + echo "โŒ Cannot create duplicate release" + exit 1 + fi + fi + + # Check remote tags + git fetch --tags + if git rev-parse "origin/$VERSION" >/dev/null 2>&1; then + echo "โš ๏ธ Tag $VERSION already exists remotely" + + if [[ "${{ github.event.inputs.dry_run }}" == "true" ]]; then + echo "Dry run mode - continuing anyway" + else + echo "โŒ Cannot create duplicate release" + exit 1 + fi + fi + + echo "โœ… Tag $VERSION does not exist - safe to proceed" + + # =========================================================================== + # Job 2: Run Final CI Check + # =========================================================================== + # Runs the full CI pipeline one last time before publishing + # =========================================================================== + + final-ci-check: + name: ๐Ÿš€ Final CI Check + needs: validate-release + if: needs.validate-release.outputs.is_valid == 'true' && github.event.inputs.skip_tests != 'true' + uses: ./.github/workflows/ci.yml + secrets: inherit + + # =========================================================================== + # Job 3: Publish to pub.dev + # =========================================================================== + # Publishes the Flutter plugin to pub.dev + # =========================================================================== + + publish-to-pubdev: + name: ๐Ÿ“ฆ Publish to pub.dev + runs-on: ubuntu-latest + needs: [validate-release, final-ci-check] + if: always() && needs.validate-release.outputs.is_valid == 'true' + + steps: + - name: ๐Ÿ“ฅ Checkout repository + uses: actions/checkout@v4 + + - name: ๐Ÿ”ง Setup Flutter SDK + uses: subosito/flutter-action@v2 + with: + channel: 'stable' + cache: true + + - name: โ„น๏ธ Display Flutter version + run: | + flutter --version + dart --version + + - name: ๐Ÿ“ฆ Get dependencies + run: flutter pub get + + - name: ๐Ÿ” Validate package + run: | + echo "Running pub publish dry-run to validate package..." + flutter pub publish --dry-run + + - name: ๐Ÿ“ Check pub.dev credentials + run: | + # Check if pub-credentials.json exists + # Note: This should be set up as a repository secret + if [[ -z "${{ secrets.PUB_DEV_CREDENTIALS }}" ]]; then + echo "โš ๏ธ PUB_DEV_CREDENTIALS secret not found" + echo "Please set up pub.dev credentials as a repository secret" + echo "See: https://dart.dev/tools/pub/automated-publishing" + + if [[ "${{ github.event.inputs.dry_run }}" != "true" ]]; then + exit 1 + fi + else + echo "โœ… pub.dev credentials found" + fi + + - name: ๐Ÿš€ Publish to pub.dev + if: github.event.inputs.dry_run != 'true' + run: | + VERSION="${{ needs.validate-release.outputs.version }}" + echo "Publishing version $VERSION to pub.dev..." + + # Set up credentials + mkdir -p ~/.config/dart + echo '${{ secrets.PUB_DEV_CREDENTIALS }}' > ~/.config/dart/pub-credentials.json + + # Publish to pub.dev (non-interactive) + flutter pub publish --force + + # Clean up credentials + rm ~/.config/dart/pub-credentials.json + + echo "โœ… Successfully published to pub.dev" + + - name: ๐Ÿท๏ธ Verify publication + if: github.event.inputs.dry_run != 'true' + run: | + VERSION="${{ needs.validate-release.outputs.version }}" + + echo "Waiting 30 seconds for pub.dev to index the package..." + sleep 30 + + # Check if the version is available on pub.dev + PACKAGE_INFO=$(curl -s "https://pub.dev/api/packages/appsflyer_sdk") + + if echo "$PACKAGE_INFO" | grep -q "\"version\":\"$VERSION\""; then + echo "โœ… Version $VERSION is now available on pub.dev" + else + echo "โš ๏ธ Version $VERSION not yet visible on pub.dev" + echo "This is normal - it may take a few minutes to appear" + fi + + # =========================================================================== + # Job 4: Create GitHub Release + # =========================================================================== + # Creates an official GitHub release with release notes + # =========================================================================== + + create-github-release: + name: ๐Ÿท๏ธ Create GitHub Release + runs-on: ubuntu-latest + needs: [validate-release, publish-to-pubdev] + if: always() && needs.validate-release.outputs.is_valid == 'true' && github.event.inputs.dry_run != 'true' + + steps: + - name: ๐Ÿ“ฅ Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: ๐Ÿ“ Extract release notes from CHANGELOG + id: changelog + run: | + VERSION="${{ needs.validate-release.outputs.version }}" + + echo "Extracting release notes for version $VERSION from CHANGELOG.md" + + # Try to extract the section for this version from CHANGELOG.md + if [ -f "CHANGELOG.md" ]; then + # Extract content between ## VERSION and the next ## heading + RELEASE_NOTES=$(awk "/## $VERSION/,/^## [0-9]/" CHANGELOG.md | sed '1d;$d') + + if [ -z "$RELEASE_NOTES" ]; then + echo "โš ๏ธ Could not find release notes for version $VERSION in CHANGELOG.md" + RELEASE_NOTES="Release version $VERSION. See [CHANGELOG.md](CHANGELOG.md) for details." + fi + else + echo "โš ๏ธ CHANGELOG.md not found" + RELEASE_NOTES="Release version $VERSION." + fi + + # Save to file for use in release + echo "$RELEASE_NOTES" > release_notes.md + + echo "Release notes extracted" + + - name: ๐Ÿ“ Enhance release notes + run: | + VERSION="${{ needs.validate-release.outputs.version }}" + + # Get SDK versions from files + ANDROID_SDK_VERSION=$(grep "implementation 'com.appsflyer:af-android-sdk:" android/build.gradle | sed -n "s/.*af-android-sdk:\([^']*\).*/\1/p" | head -1) + IOS_SDK_VERSION=$(grep "s.ios.dependency 'AppsFlyerFramework'" ios/appsflyer_sdk.podspec | sed -n "s/.*AppsFlyerFramework',.*'\([^']*\)'.*/\1/p" | head -1) + + # Create enhanced release notes + cat > final_release_notes.md << EOF + # AppsFlyer Flutter Plugin v$VERSION + + ## ๐Ÿ“ฆ Installation + + Add to your \`pubspec.yaml\`: + + \`\`\`yaml + dependencies: + appsflyer_sdk: ^$VERSION + \`\`\` + + Then run: + \`\`\`bash + flutter pub get + \`\`\` + + ## ๐Ÿ“‹ Changes in This Release + + $(cat release_notes.md) + + ## ๐Ÿ”ง SDK Versions + + - **Android**: AppsFlyer SDK v$ANDROID_SDK_VERSION + - **iOS**: AppsFlyer SDK v$IOS_SDK_VERSION + + ## ๐Ÿ“š Documentation + + - [Installation Guide](https://github.com/${{ github.repository }}/blob/master/doc/Installation.md) + - [Basic Integration](https://github.com/${{ github.repository }}/blob/master/doc/BasicIntegration.md) + - [API Documentation](https://github.com/${{ github.repository }}/blob/master/doc/API.md) + - [Sample App](https://github.com/${{ github.repository }}/tree/master/example) + + ## ๐Ÿ”— Links + + - [pub.dev Package](https://pub.dev/packages/appsflyer_sdk) + - [API Reference](https://pub.dev/documentation/appsflyer_sdk/latest/) + - [GitHub Repository](https://github.com/${{ github.repository }}) + - [AppsFlyer Developer Hub](https://dev.appsflyer.com/) + + ## ๐Ÿ’ฌ Support + + For issues and questions, please contact + EOF + + echo "Enhanced release notes created" + + - name: ๐Ÿท๏ธ Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ needs.validate-release.outputs.version }} + name: v${{ needs.validate-release.outputs.version }} + body_path: final_release_notes.md + draft: false + prerelease: false + generate_release_notes: false + token: ${{ secrets.GITHUB_TOKEN }} + + - name: โœ… Release created + run: | + VERSION="${{ needs.validate-release.outputs.version }}" + echo "โœ… GitHub release v$VERSION created successfully" + echo "๐Ÿ”— https://github.com/${{ github.repository }}/releases/tag/$VERSION" + + # =========================================================================== + # Job 5: Notify Team (Placeholder for Slack Integration) + # =========================================================================== + # Sends notification to Slack channel about the production release + # =========================================================================== + + notify-team: + name: ๐Ÿ“ข Notify Team + runs-on: ubuntu-latest + needs: [validate-release, publish-to-pubdev, create-github-release] + if: always() && github.event.inputs.dry_run != 'true' + + steps: + - name: ๐Ÿ“ฅ Checkout repository + uses: actions/checkout@v4 + + - name: ๐Ÿ“ Extract SDK versions and changelog + id: extract-info + run: | + VERSION="${{ needs.validate-release.outputs.version }}" + + # Extract Android SDK version + ANDROID_SDK_VERSION=$(grep "implementation 'com.appsflyer:af-android-sdk:" android/build.gradle | sed -n "s/.*af-android-sdk:\([^']*\).*/\1/p" | head -1) + echo "android_sdk=$ANDROID_SDK_VERSION" >> $GITHUB_OUTPUT + + # Extract iOS SDK version from podspec + IOS_SDK_VERSION=$(grep "s.ios.dependency 'AppsFlyerFramework'" ios/appsflyer_sdk.podspec | sed -n "s/.*AppsFlyerFramework',.*'\([^']*\)'.*/\1/p" | head -1) + echo "ios_sdk=$IOS_SDK_VERSION" >> $GITHUB_OUTPUT + + # Extract Purchase Connector versions from build files + ANDROID_PC_VERSION=$(grep "implementation 'com.appsflyer:purchase-connector:" android/build.gradle | sed -n "s/.*purchase-connector:\([^']*\).*/\1/p" | head -1) + if [ -z "$ANDROID_PC_VERSION" ]; then + ANDROID_PC_VERSION="N/A" + fi + echo "android_pc=$ANDROID_PC_VERSION" >> $GITHUB_OUTPUT + + IOS_PC_VERSION=$(grep "s.ios.dependency 'PurchaseConnector'" ios/appsflyer_sdk.podspec | sed -n "s/.*PurchaseConnector',.*'\([^']*\)'.*/\1/p" | head -1) + if [ -z "$IOS_PC_VERSION" ]; then + IOS_PC_VERSION="N/A" + fi + echo "ios_pc=$IOS_PC_VERSION" >> $GITHUB_OUTPUT + + # Extract changelog for this version + if [ -f "CHANGELOG.md" ]; then + # Extract bullet points for this version + CHANGELOG=$(awk "/## $VERSION/,/^## [0-9]/" CHANGELOG.md | grep "^-" | sed 's/^- /โ€ข /' | head -5) + if [ -z "$CHANGELOG" ]; then + CHANGELOG="โ€ข Check CHANGELOG.md for details" + fi + else + CHANGELOG="โ€ข Check release notes for details" + fi + + # Save to file and encode for JSON + echo "$CHANGELOG" > /tmp/changelog.txt + echo "changelog<> $GITHUB_OUTPUT + echo "$CHANGELOG" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: ๐ŸŽซ Fetch Jira tickets + id: jira-tickets + continue-on-error: true # Don't fail CI if Jira fetch fails + run: | + set +e # Don't exit on errors + + VERSION="${{ needs.validate-release.outputs.version }}" + # Use full version with 'v' prefix (matches your Jira convention) + # For production release, version is X.Y.Z without -rc suffix + JIRA_FIX_VERSION="Flutter SDK v$VERSION" + + echo "๐Ÿ” Looking for Jira tickets with fix version: $JIRA_FIX_VERSION" + + # Check if Jira credentials are available + if [[ -z "${{ secrets.CI_JIRA_EMAIL }}" ]] || [[ -z "${{ secrets.CI_JIRA_TOKEN }}" ]]; then + echo "โš ๏ธ Jira credentials not configured" + echo "tickets=No assigned fix version found" >> $GITHUB_OUTPUT + exit 0 + fi + + # Fetch tickets from Jira with this fix version + JIRA_DOMAIN="${{ secrets.CI_JIRA_DOMAIN || 'appsflyer.atlassian.net' }}" + + # URL-encode the JQL query properly + JQL_QUERY="fixVersion=\"${JIRA_FIX_VERSION}\"" + # Use jq for proper URL encoding + ENCODED_JQL=$(echo "$JQL_QUERY" | jq -sRr @uri) + + echo "๐Ÿ“ก Querying Jira API..." + echo "Domain: $JIRA_DOMAIN" + echo "JQL: $JQL_QUERY" + + # Query Jira API with error handling and verbose output + # Using the new /search/jql endpoint as per Jira API v3 + RESPONSE=$(curl -s -w "\n%{http_code}" \ + -u "${{ secrets.CI_JIRA_EMAIL }}:${{ secrets.CI_JIRA_TOKEN }}" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + "https://${JIRA_DOMAIN}/rest/api/3/search/jql?jql=${ENCODED_JQL}&fields=key,summary&maxResults=20") + + HTTP_CODE=$(echo "$RESPONSE" | tail -n1) + BODY=$(echo "$RESPONSE" | sed '$d') + + echo "HTTP Status: $HTTP_CODE" + + if [[ "$HTTP_CODE" != "200" ]]; then + echo "โš ๏ธ Jira API request failed with status $HTTP_CODE" + echo "Response body (first 500 chars):" + echo "$BODY" | head -c 500 + echo "tickets=No assigned fix version found" >> $GITHUB_OUTPUT + exit 0 + fi + + # Extract ticket keys and create links with summaries + TICKETS=$(echo "$BODY" | jq -r '.issues[]? | "โ€ข https://'"${JIRA_DOMAIN}"'/browse/\(.key) - \(.fields.summary)"' 2>/dev/null | head -10) + + if [ -z "$TICKETS" ]; then + echo "โ„น๏ธ No linked tickets found for version: $JIRA_FIX_VERSION" + echo "tickets=No assigned fix version found" >> $GITHUB_OUTPUT + else + echo "โœ… Found Jira tickets:" + echo "$TICKETS" + echo "tickets<> $GITHUB_OUTPUT + echo "$TICKETS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + + - name: ๐Ÿ“ข Determine status + id: status + run: | + PUBLISH_STATUS="${{ needs.publish-to-pubdev.result }}" + RELEASE_STATUS="${{ needs.create-github-release.result }}" + + if [[ "$PUBLISH_STATUS" == "success" ]] && [[ "$RELEASE_STATUS" == "success" ]]; then + echo "success=true" >> $GITHUB_OUTPUT + else + echo "success=false" >> $GITHUB_OUTPUT + fi + + - name: ๐Ÿ“จ Send Slack notification + if: steps.status.outputs.success == 'true' + uses: slackapi/slack-github-action@v1 + with: + payload: | + { + "text": "\n:flutter::flutter::flutter::flutter::flutter::flutter::flutter::flutter::flutter::flutter::flutter::flutter:\n\n*Flutter:*\nappsflyer_sdk: ^${{ needs.validate-release.outputs.version }} is published to Production.\n\n*Sources:*\n:github: https://github.com/${{ github.repository }}/tree/master\n:pubdev: https://pub.dev/packages/appsflyer_sdk/versions/${{ needs.validate-release.outputs.version }}\n\n*Changes and fixes:*\n${{ steps.extract-info.outputs.changelog }}\n\n*Tests:*\n:white_check_mark: CI pipeline passed.\n:white_check_mark: Unit tests passed.\n:white_check_mark: Android and iOS builds successful.\n\n*Linked tickets and issues:*\n${{ steps.jira-tickets.outputs.tickets }}\n\n*Native SDK's:*\n:android: ${{ steps.extract-info.outputs.android_sdk }}\n:apple: ${{ steps.extract-info.outputs.ios_sdk }}\n\n*Purchase Connector:*\n:android: ${{ steps.extract-info.outputs.android_pc }}\n:apple: ${{ steps.extract-info.outputs.ios_pc }}\n\n:flutter::flutter::flutter::flutter::flutter::flutter::flutter::flutter::flutter::flutter::flutter::flutter:" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.CI_SLACK_WEBHOOK_URL }} + + - name: ๐Ÿ“จ Send failure notification + if: steps.status.outputs.success == 'false' + uses: slackapi/slack-github-action@v1 + with: + payload: | + { + "text": "\n:warning: *Flutter Plugin Release Failed*\n\nVersion: ${{ needs.validate-release.outputs.version }}\nStatus: Release encountered issues\n\nPlease check the workflow logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.CI_SLACK_WEBHOOK_URL }} + + # =========================================================================== + # Job 6: Production Release Summary + # =========================================================================== + # Provides a comprehensive summary of the production release + # =========================================================================== + + release-summary: + name: ๐Ÿ“‹ Release Summary + runs-on: ubuntu-latest + needs: [validate-release, final-ci-check, publish-to-pubdev, create-github-release] + if: always() + + steps: + - name: ๐Ÿ“Š Display Release Summary + run: | + echo "=========================================" + echo "Production Release Summary" + echo "=========================================" + echo "Version: ${{ needs.validate-release.outputs.version }}" + echo "Dry Run: ${{ github.event.inputs.dry_run }}" + echo "-----------------------------------------" + echo "Validation: ${{ needs.validate-release.result }}" + echo "Final CI Check: ${{ needs.final-ci-check.result }}" + echo "pub.dev Publish: ${{ needs.publish-to-pubdev.result }}" + echo "GitHub Release: ${{ needs.create-github-release.result }}" + echo "=========================================" + + if [[ "${{ github.event.inputs.dry_run }}" == "true" ]]; then + echo "โ„น๏ธ This was a DRY RUN - no actual publishing occurred" + exit 0 + fi + + # Check if all critical jobs succeeded + if [[ "${{ needs.validate-release.result }}" == "success" ]] && \ + [[ "${{ needs.publish-to-pubdev.result }}" == "success" ]] && \ + [[ "${{ needs.create-github-release.result }}" == "success" ]]; then + echo "" + echo "โœ… Production Release Completed Successfully!" + echo "" + echo "๐ŸŽ‰ Version ${{ needs.validate-release.outputs.version }} is now live!" + echo "" + echo "๐Ÿ“ฆ pub.dev: https://pub.dev/packages/appsflyer_sdk" + echo "๐Ÿท๏ธ GitHub: https://github.com/${{ github.repository }}/releases/tag/${{ needs.validate-release.outputs.version }}" + echo "" + echo "Next Steps:" + echo "1. Verify the package on pub.dev" + echo "2. Update documentation if needed" + echo "3. Announce the release to the community" + echo "4. Monitor for any issues or feedback" + else + echo "" + echo "โŒ Production Release Failed" + echo "" + echo "Please check the logs above for details and retry if necessary" + exit 1 + fi diff --git a/.github/workflows/rc-release.yml b/.github/workflows/rc-release.yml new file mode 100644 index 0000000..3ed1686 --- /dev/null +++ b/.github/workflows/rc-release.yml @@ -0,0 +1,542 @@ +# ============================================================================= +# RC (Release Candidate) Workflow - Pre-Production Release +# ============================================================================= +# +# Purpose: Creates a release candidate for QA testing before production release. +# This workflow is triggered when a release branch is created or manually. +# +# What it does: +# 1. Validates the release branch naming convention +# 2. Runs full CI pipeline (tests + builds) +# 3. Updates version numbers in relevant files +# 4. Creates a pre-release tag on GitHub +# 5. Optionally notifies team via Slack (placeholder for future) +# +# Branch Convention: releases/X.x.x/X.Y.x/X.Y.Z-rcN +# Example: releases/6.x.x/6.18.x/6.18.0-rc1 +# Where: X, Y, Z are version numbers, and lowercase 'x' is literal +# +# Triggers: +# - Push to branches matching releases/** pattern +# - Manual workflow dispatch with version input +# +# ============================================================================= + +name: RC - Release Candidate + +on: + # Trigger on push to release branches + push: + branches: + - 'releases/**' + + # Allow manual triggering with version specification + workflow_dispatch: + inputs: + version: + description: 'Release version (e.g., 6.18.0-rc1)' + required: true + type: string + skip_tests: + description: 'Skip tests and builds (for testing workflow only)' + required: false + type: boolean + default: false + +# Prevent multiple RC workflows from running simultaneously +concurrency: + group: rc-release-${{ github.ref }} + cancel-in-progress: false # Don't cancel, let it finish + +jobs: + # =========================================================================== + # Job 1: Validate Release Branch + # =========================================================================== + # Ensures the branch follows naming conventions and extracts version info + # =========================================================================== + + validate-release: + name: ๐Ÿ” Validate Release Branch + runs-on: ubuntu-latest + + outputs: + version: ${{ steps.extract-version.outputs.version }} + is_rc: ${{ steps.extract-version.outputs.is_rc }} + is_valid: ${{ steps.extract-version.outputs.is_valid }} + + steps: + - name: ๐Ÿ“ฅ Checkout repository + uses: actions/checkout@v4 + + - name: ๐Ÿ” Extract and validate version + id: extract-version + run: | + # Determine version from branch name or manual input + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + VERSION="${{ github.event.inputs.version }}" + echo "Using manual version: $VERSION" + else + # Extract version from branch name: releases/X.x.x/X.Y.x/X.Y.Z-rcN + # Example: releases/6.x.x/6.18.x/6.18.0-rc1 + BRANCH_NAME="${{ github.ref_name }}" + echo "Branch name: $BRANCH_NAME" + + # Extract version using regex + # Pattern: releases/X.x.x/X.Y.x/X.Y.Z-rcN where x is literal 'x' + if [[ $BRANCH_NAME =~ releases/([0-9]+)\.x\.x/([0-9]+\.[0-9]+)\.x/([0-9]+\.[0-9]+\.[0-9]+-rc[0-9]+)$ ]]; then + VERSION="${BASH_REMATCH[3]}" + MAJOR="${BASH_REMATCH[1]}" + MAJOR_MINOR="${BASH_REMATCH[2]}" + + echo "Extracted version: $VERSION" + echo "Major version: $MAJOR" + echo "Major.Minor: $MAJOR_MINOR" + + # Validate that version starts with the correct major.minor + VERSION_PREFIX=$(echo "$VERSION" | grep -oE '^[0-9]+\.[0-9]+') + if [[ "$VERSION_PREFIX" != "$MAJOR_MINOR" ]]; then + echo "โŒ Version mismatch!" + echo "Expected version to start with: $MAJOR_MINOR" + echo "Got: $VERSION" + exit 1 + fi + + # Validate that major version matches + VERSION_MAJOR=$(echo "$VERSION" | grep -oE '^[0-9]+') + if [[ "$VERSION_MAJOR" != "$MAJOR" ]]; then + echo "โŒ Major version mismatch!" + echo "Expected major version: $MAJOR" + echo "Got: $VERSION_MAJOR" + exit 1 + fi + else + echo "โŒ Invalid branch name format!" + echo "Expected: releases/X.x.x/X.Y.x/X.Y.Z-rcN" + echo "Example: releases/6.x.x/6.18.x/6.18.0-rc1" + echo "Got: $BRANCH_NAME" + exit 1 + fi + fi + + # Validate version format + if [[ $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?$ ]]; then + echo "โœ… Valid version format: $VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "is_valid=true" >> $GITHUB_OUTPUT + + # Check if it's an RC version + if [[ $VERSION =~ -rc[0-9]+$ ]]; then + echo "is_rc=true" >> $GITHUB_OUTPUT + echo "๐Ÿ“ฆ This is a Release Candidate" + else + echo "is_rc=false" >> $GITHUB_OUTPUT + echo "๐Ÿ“ฆ This is a production version" + fi + else + echo "โŒ Invalid version format: $VERSION" + echo "is_valid=false" >> $GITHUB_OUTPUT + exit 1 + fi + + # =========================================================================== + # Job 2: Run CI Pipeline + # =========================================================================== + # Reuses the main CI workflow to run tests and builds + # =========================================================================== + + run-ci: + name: ๐Ÿš€ Run CI Pipeline + needs: validate-release + if: ${{ needs.validate-release.outputs.is_valid == 'true' && github.event.inputs.skip_tests != 'true' }} + uses: ./.github/workflows/ci.yml + secrets: inherit + + # =========================================================================== + # Job 3: Update Version Files + # =========================================================================== + # Updates pubspec.yaml and other version-related files + # =========================================================================== + + update-version: + name: ๐Ÿ“ Update Version Files + runs-on: ubuntu-latest + needs: [validate-release, run-ci] + if: always() && needs.validate-release.outputs.is_valid == 'true' + + steps: + - name: ๐Ÿ“ฅ Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 # Fetch all history for proper tagging + + - name: ๐Ÿ”ง Setup Flutter SDK + uses: subosito/flutter-action@v2 + with: + channel: 'stable' + cache: true + + - name: ๐Ÿ“ Update pubspec.yaml version + run: | + VERSION="${{ needs.validate-release.outputs.version }}" + + # Remove -rcN suffix for pubspec.yaml (pub.dev doesn't support pre-release tags) + PUBSPEC_VERSION=$(echo $VERSION | sed 's/-rc[0-9]*$//') + + echo "Updating pubspec.yaml to version: $PUBSPEC_VERSION" + + # Update version in pubspec.yaml + sed -i.bak "s/^version: .*/version: $PUBSPEC_VERSION/" pubspec.yaml + rm pubspec.yaml.bak + + # Verify the change + echo "Updated pubspec.yaml:" + grep "^version:" pubspec.yaml + + - name: ๐Ÿ“ Update Android plugin version constant + run: | + VERSION="${{ needs.validate-release.outputs.version }}" + + # Find and update kPluginVersion in Android constants + ANDROID_CONSTANTS_FILE="android/src/main/java/com/appsflyer/appsflyersdk/AppsflyerConstants.java" + + if [ -f "$ANDROID_CONSTANTS_FILE" ]; then + echo "Updating Android plugin version to: $VERSION" + sed -i.bak "s/kPluginVersion = \".*\"/kPluginVersion = \"$VERSION\"/" "$ANDROID_CONSTANTS_FILE" + rm "${ANDROID_CONSTANTS_FILE}.bak" + + echo "Updated Android constants:" + grep "kPluginVersion" "$ANDROID_CONSTANTS_FILE" || echo "Pattern not found" + else + echo "โš ๏ธ Android constants file not found, skipping" + fi + + - name: ๐Ÿ“ Update iOS plugin version constant + run: | + VERSION="${{ needs.validate-release.outputs.version }}" + + # Find and update kPluginVersion in iOS constants + IOS_PLUGIN_FILE="ios/Classes/AppsflyerSdkPlugin.m" + + if [ -f "$IOS_PLUGIN_FILE" ]; then + echo "Updating iOS plugin version to: $VERSION" + sed -i.bak "s/kPluginVersion = @\".*\"/kPluginVersion = @\"$VERSION\"/" "$IOS_PLUGIN_FILE" + rm "${IOS_PLUGIN_FILE}.bak" + + echo "Updated iOS plugin file:" + grep "kPluginVersion" "$IOS_PLUGIN_FILE" || echo "Pattern not found" + else + echo "โš ๏ธ iOS plugin file not found, skipping" + fi + + - name: ๐Ÿ“ Update podspec version + run: | + VERSION="${{ needs.validate-release.outputs.version }}" + PODSPEC_VERSION=$(echo $VERSION | sed 's/-rc[0-9]*$//') + + PODSPEC_FILE="ios/appsflyer_sdk.podspec" + + if [ -f "$PODSPEC_FILE" ]; then + echo "Updating podspec to version: $PODSPEC_VERSION" + sed -i.bak "s/s\.version.*=.*/s.version = '$PODSPEC_VERSION'/" "$PODSPEC_FILE" + rm "${PODSPEC_FILE}.bak" + + echo "Updated podspec:" + grep "s.version" "$PODSPEC_FILE" + else + echo "โš ๏ธ Podspec file not found, skipping" + fi + + - name: ๐Ÿ’พ Commit version changes + run: | + VERSION="${{ needs.validate-release.outputs.version }}" + + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + + # Check if there are changes to commit + if [[ -n $(git status -s) ]]; then + git add pubspec.yaml android/ ios/ + git commit -m "chore: bump version to $VERSION" + git push + echo "โœ… Version changes committed and pushed" + else + echo "โ„น๏ธ No version changes to commit" + fi + + # =========================================================================== + # Job 4: Create Pre-Release + # =========================================================================== + # Creates a GitHub pre-release with the RC tag + # =========================================================================== + + create-prerelease: + name: ๐Ÿท๏ธ Create Pre-Release + runs-on: ubuntu-latest + needs: [validate-release, run-ci, update-version] + if: always() && needs.validate-release.outputs.is_rc == 'true' + + steps: + - name: ๐Ÿ“ฅ Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.ref }} + + - name: ๐Ÿ“ Generate release notes + id: release-notes + run: | + VERSION="${{ needs.validate-release.outputs.version }}" + + # Extract changes from CHANGELOG.md for this version + echo "Extracting release notes for version $VERSION" + + # Create release notes + cat > release_notes.md << EOF + # AppsFlyer Flutter Plugin - Release Candidate $VERSION + + ## ๐Ÿš€ Release Candidate for Testing + + This is a pre-release version for QA testing. Please do not use in production. + + ## ๐Ÿ“‹ Changes + + Please refer to [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/${{ github.ref_name }}/CHANGELOG.md) for detailed changes. + + ## ๐Ÿงช Testing Instructions + + 1. Add to your \`pubspec.yaml\`: + \`\`\`yaml + dependencies: + appsflyer_sdk: + git: + url: https://github.com/${{ github.repository }}.git + ref: $VERSION + \`\`\` + + 2. Run \`flutter pub get\` + 3. Test the integration thoroughly + 4. Report any issues to the development team + + ## ๐Ÿ“ฆ SDK Versions + + - Android AppsFlyer SDK: (check android/build.gradle) + - iOS AppsFlyer SDK: (check ios/appsflyer_sdk.podspec) + + --- + + **Note**: This is a pre-release and should not be used in production applications. + EOF + + echo "Release notes generated" + + - name: ๐Ÿท๏ธ Create GitHub Pre-Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ needs.validate-release.outputs.version }} + name: Release Candidate ${{ needs.validate-release.outputs.version }} + body_path: release_notes.md + draft: false + prerelease: true # Mark as pre-release + generate_release_notes: false + token: ${{ secrets.GITHUB_TOKEN }} + + # =========================================================================== + # Job 5: Notify Team (Placeholder for Slack Integration) + # =========================================================================== + # Sends notification to Slack channel about the RC release + # =========================================================================== + + notify-team: + name: ๐Ÿ“ข Notify Team + runs-on: ubuntu-latest + needs: [validate-release, create-prerelease] + if: always() + + steps: + - name: ๐Ÿ“ฅ Checkout repository + uses: actions/checkout@v4 + + - name: ๐Ÿ“ Extract SDK versions and changelog + id: extract-info + run: | + VERSION="${{ needs.validate-release.outputs.version }}" + BASE_VERSION=$(echo "$VERSION" | sed 's/-rc[0-9]*$//') + + # Extract Android SDK version + ANDROID_SDK_VERSION=$(grep "implementation 'com.appsflyer:af-android-sdk:" android/build.gradle | sed -n "s/.*af-android-sdk:\([^']*\).*/\1/p" | head -1) + echo "android_sdk=$ANDROID_SDK_VERSION" >> $GITHUB_OUTPUT + + # Extract iOS SDK version + IOS_SDK_VERSION=$(grep "s.ios.dependency 'AppsFlyerFramework'" ios/appsflyer_sdk.podspec | sed -n "s/.*AppsFlyerFramework',.*'\([^']*\)'.*/\1/p" | head -1) + echo "ios_sdk=$IOS_SDK_VERSION" >> $GITHUB_OUTPUT + + # Extract Purchase Connector versions from build files + ANDROID_PC_VERSION=$(grep "implementation 'com.appsflyer:purchase-connector:" android/build.gradle | sed -n "s/.*purchase-connector:\([^']*\).*/\1/p" | head -1) + if [ -z "$ANDROID_PC_VERSION" ]; then + ANDROID_PC_VERSION="N/A" + fi + echo "android_pc=$ANDROID_PC_VERSION" >> $GITHUB_OUTPUT + + IOS_PC_VERSION=$(grep "s.ios.dependency 'PurchaseConnector'" ios/appsflyer_sdk.podspec | sed -n "s/.*PurchaseConnector',.*'\([^']*\)'.*/\1/p" | head -1) + if [ -z "$IOS_PC_VERSION" ]; then + IOS_PC_VERSION="N/A" + fi + echo "ios_pc=$IOS_PC_VERSION" >> $GITHUB_OUTPUT + + # Extract changelog for this version (use base version without -rc suffix) + if [ -f "CHANGELOG.md" ]; then + CHANGELOG=$(awk "/## $BASE_VERSION/,/^## [0-9]/" CHANGELOG.md | grep "^-" | sed 's/^- /โ€ข /' | head -5) + if [ -z "$CHANGELOG" ]; then + CHANGELOG="โ€ข Check CHANGELOG.md for details" + fi + else + CHANGELOG="โ€ข Check release notes for details" + fi + + echo "changelog<> $GITHUB_OUTPUT + echo "$CHANGELOG" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: ๐ŸŽซ Fetch Jira tickets + id: jira-tickets + continue-on-error: true # Don't fail CI if Jira fetch fails + run: | + set +e # Don't exit on errors + + VERSION="${{ needs.validate-release.outputs.version }}" + # Remove -rc suffix for Jira lookup (e.g., 99.99.99-rc1 โ†’ 99.99.99) + BASE_VERSION=$(echo "$VERSION" | sed 's/-rc[0-9]*$//') + # Use base version with 'v' prefix (matches your Jira convention) + JIRA_FIX_VERSION="Flutter SDK v$BASE_VERSION" + + echo "๐Ÿ” Looking for Jira tickets with fix version: $JIRA_FIX_VERSION" + + # Check if Jira credentials are available + if [[ -z "${{ secrets.CI_JIRA_EMAIL }}" ]] || [[ -z "${{ secrets.CI_JIRA_TOKEN }}" ]]; then + echo "โš ๏ธ Jira credentials not configured" + echo "tickets=No assigned fix version found" >> $GITHUB_OUTPUT + exit 0 + fi + + JIRA_DOMAIN="${{ secrets.CI_JIRA_DOMAIN || 'appsflyer.atlassian.net' }}" + + # URL-encode the JQL query properly + JQL_QUERY="fixVersion=\"${JIRA_FIX_VERSION}\"" + # Use curl's --data-urlencode for proper encoding + ENCODED_JQL=$(echo "$JQL_QUERY" | jq -sRr @uri) + + echo "๐Ÿ“ก Querying Jira API..." + echo "Domain: $JIRA_DOMAIN" + echo "JQL: $JQL_QUERY" + + # Query Jira API with error handling and verbose output + # Using the new /search/jql endpoint as per Jira API v3 + RESPONSE=$(curl -s -w "\n%{http_code}" \ + -u "${{ secrets.CI_JIRA_EMAIL }}:${{ secrets.CI_JIRA_TOKEN }}" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + "https://${JIRA_DOMAIN}/rest/api/3/search/jql?jql=${ENCODED_JQL}&fields=key,summary&maxResults=20") + + HTTP_CODE=$(echo "$RESPONSE" | tail -n1) + BODY=$(echo "$RESPONSE" | sed '$d') + + echo "HTTP Status: $HTTP_CODE" + + if [[ "$HTTP_CODE" != "200" ]]; then + echo "โš ๏ธ Jira API request failed with status $HTTP_CODE" + echo "Response body (first 500 chars):" + echo "$BODY" | head -c 500 + echo "tickets=No assigned fix version found" >> $GITHUB_OUTPUT + exit 0 + fi + + # Extract ticket keys and create links with summaries + TICKETS=$(echo "$BODY" | jq -r '.issues[]? | "โ€ข https://'"${JIRA_DOMAIN}"'/browse/\(.key) - \(.fields.summary)"' 2>/dev/null | head -10) + + if [ -z "$TICKETS" ]; then + echo "โ„น๏ธ No linked tickets found for version: $JIRA_FIX_VERSION" + echo "tickets=No assigned fix version found" >> $GITHUB_OUTPUT + else + echo "โœ… Found Jira tickets:" + echo "$TICKETS" + echo "tickets<> $GITHUB_OUTPUT + echo "$TICKETS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + + - name: ๐Ÿ“ข Determine status + id: status + run: | + STATUS="${{ needs.create-prerelease.result }}" + + if [[ "$STATUS" == "success" ]]; then + echo "success=true" >> $GITHUB_OUTPUT + else + echo "success=false" >> $GITHUB_OUTPUT + fi + + - name: ๐Ÿ“จ Send Slack notification (Success) + if: steps.status.outputs.success == 'true' + uses: slackapi/slack-github-action@v1 + with: + payload: | + { + "text": "\n:flutter::flutter::flutter::flutter::flutter::flutter::flutter::flutter::flutter::flutter::flutter::flutter:\n\n*Flutter Release Candidate:*\nappsflyer_sdk: ${{ needs.validate-release.outputs.version }} is ready for QA testing.\n\n*Testing Instructions:*\nAdd to pubspec.yaml:\n```\ndependencies:\n appsflyer_sdk:\n git:\n url: https://github.com/${{ github.repository }}.git\n ref: ${{ needs.validate-release.outputs.version }}\n```\n\n*Sources:*\n:github: https://github.com/${{ github.repository }}/tree/${{ github.ref_name }}\n:github: Release: https://github.com/${{ github.repository }}/releases/tag/${{ needs.validate-release.outputs.version }}\n\n*Changes and fixes:*\n${{ steps.extract-info.outputs.changelog }}\n\n*Linked tickets and issues:*\n${{ steps.jira-tickets.outputs.tickets }}\n\n*Native SDK's:*\n:android: ${{ steps.extract-info.outputs.android_sdk }}\n:apple: ${{ steps.extract-info.outputs.ios_sdk }}\n\n*Purchase Connector:*\n:android: ${{ steps.extract-info.outputs.android_pc }}\n:apple: ${{ steps.extract-info.outputs.ios_pc }}\n\n:flutter::flutter::flutter::flutter::flutter::flutter::flutter::flutter::flutter::flutter::flutter::flutter:" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.CI_SLACK_WEBHOOK_URL }} + + - name: ๐Ÿ“จ Send failure notification + if: steps.status.outputs.success == 'false' + uses: slackapi/slack-github-action@v1 + with: + payload: | + { + "text": "\n:warning: *Flutter RC Creation Failed*\n\nVersion: ${{ needs.validate-release.outputs.version }}\nBranch: ${{ github.ref_name }}\n\nPlease check the workflow logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.CI_SLACK_WEBHOOK_URL }} + + # =========================================================================== + # Job 6: RC Summary + # =========================================================================== + # Provides a summary of the RC release process + # =========================================================================== + + rc-summary: + name: ๐Ÿ“‹ RC Summary + runs-on: ubuntu-latest + needs: [validate-release, run-ci, update-version, create-prerelease] + if: always() + + steps: + - name: ๐Ÿ“Š Display RC Summary + run: | + echo "=========================================" + echo "RC Release Summary" + echo "=========================================" + echo "Version: ${{ needs.validate-release.outputs.version }}" + echo "Is RC: ${{ needs.validate-release.outputs.is_rc }}" + echo "Is Valid: ${{ needs.validate-release.outputs.is_valid }}" + echo "-----------------------------------------" + echo "Validation: ${{ needs.validate-release.result }}" + echo "CI Pipeline: ${{ needs.run-ci.result }}" + echo "Version Update: ${{ needs.update-version.result }}" + echo "Pre-Release: ${{ needs.create-prerelease.result }}" + echo "=========================================" + + # Check if all critical jobs succeeded + if [[ "${{ needs.validate-release.result }}" == "success" ]] && \ + [[ "${{ needs.create-prerelease.result }}" == "success" ]]; then + echo "โœ… RC Release Process Completed Successfully" + echo "" + echo "Next Steps:" + echo "1. Notify QA team to begin testing" + echo "2. Test the RC version thoroughly" + echo "3. If approved, proceed with production release" + else + echo "โŒ RC Release Process Failed" + echo "Please check the logs above for details" + exit 1 + fi diff --git a/.gitignore b/.gitignore index db41b25..bd18aee 100644 --- a/.gitignore +++ b/.gitignore @@ -93,8 +93,6 @@ android/\.classpath example/ios/Flutter/flutter_export_environment.sh -example/ios/Runner.xcodeproj/project.pbxproj - example/.flutter-plugins-dependencies example/macos/* diff --git a/CHANGELOG.md b/CHANGELOG.md index 92557ee..5a96323 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ # Versions +## 6.17.7 + +- Updated to AppsFlyer SDK v6.17.7 for iOS and Flutter plugin version +- Android AppsFlyer SDK remains at v6.17.3 +- iOS AppsFlyer SDK upgraded to v6.17.7 +- Android Purchase Connector module updated (to support Google Billing Library 8) +- Code cleanups +- **Documentation Updates:** + - Enhanced push notification measurement documentation with clear separation between traditional `af` object approach and OneLink URL approach + - Added comprehensive iOS-specific requirements for OneLink push notification integration + - Clarified the need to call `sendPushNotificationData()` on iOS when using `addPushNotificationDeepLinkPath()` + - Added complete code examples for both push notification integration approaches with Firebase Cloud Messaging + - Added Flutter 3.27+ breaking change documentation for deep linking (must disable Flutter's built-in deep linking to avoid conflicts with AppsFlyer) + - Replaced `effective_dart` with `flutter_lints` in development dependencies + + ## 6.17.5 + - Updated to AppsFlyer SDK v6.17.5 for iOS ## 6.17.3 diff --git a/README.md b/README.md index 54b1bc8..eddca45 100644 --- a/README.md +++ b/README.md @@ -5,19 +5,19 @@ [![pub package](https://img.shields.io/pub/v/appsflyer_sdk.svg)](https://pub.dartlang.org/packages/appsflyer_sdk) ![Coverage](https://raw.githubusercontent.com/AppsFlyerSDK/appsflyer-flutter-plugin/master/coverage_badge.svg) -๐Ÿ›  In order for us to provide optimal support, we would kindly ask you to submit any issues to +๐Ÿ›  In order for us to provide optimal support, please contact AppsFlyer support through the Customer Assistant Chatbot for assistance with troubleshooting issues or product guidance.
+To do so, please follow [this article](https://support.appsflyer.com/hc/en-us/articles/23583984402193-Using-the-Customer-Assistant-Chatbot) -> *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.* ## SDK Versions - Android AppsFlyer SDK **v6.17.3** -- iOS AppsFlyer SDK **v6.17.5** +- iOS AppsFlyer SDK **v6.17.7** ### Purchase Connector versions -- Android 2.1.1 -- iOS 6.17.5 +- Android 2.2.0 +- iOS 6.17.7 ## โ—โ— Breaking changes when updating to v6.x.xโ—โ— diff --git a/analysis_options.yaml b/analysis_options.yaml index aff6623..732a61c 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,4 +1,5 @@ -include: package:effective_dart/analysis_options.yaml +include: package:flutter_lints/flutter.yaml + linter: rules: public_member_api_docs: false diff --git a/android/build.gradle b/android/build.gradle index a5d3aa6..784df06 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -57,6 +57,6 @@ dependencies { implementation 'com.android.installreferrer:installreferrer:2.2' // implementation 'androidx.core:core-ktx:1.13.1' if (includeConnector) { - implementation 'com.appsflyer:purchase-connector:2.1.1' + implementation 'com.appsflyer:purchase-connector:2.2.0' } } \ No newline at end of file diff --git a/android/src/main/java/com/appsflyer/appsflyersdk/AppsFlyerConstants.java b/android/src/main/java/com/appsflyer/appsflyersdk/AppsFlyerConstants.java index 434d73d..24673a8 100644 --- a/android/src/main/java/com/appsflyer/appsflyersdk/AppsFlyerConstants.java +++ b/android/src/main/java/com/appsflyer/appsflyersdk/AppsFlyerConstants.java @@ -1,7 +1,7 @@ package com.appsflyer.appsflyersdk; public final class AppsFlyerConstants { - final static String PLUGIN_VERSION = "6.17.5"; + final static String PLUGIN_VERSION = "6.17.6"; final static String AF_APP_INVITE_ONE_LINK = "appInviteOneLink"; final static String AF_HOST_PREFIX = "hostPrefix"; final static String AF_HOST_NAME = "hostName"; diff --git a/doc/API.md b/doc/API.md index a57323c..4717ae3 100644 --- a/doc/API.md +++ b/doc/API.md @@ -687,27 +687,37 @@ try { --- ## ** `void sendPushNotificationData(Map? userInfo)`** -Push-notification campaigns are used to create re-engagements with existing users -> [Learn more here](https://support.appsflyer.com/hc/en-us/articles/207364076-Measuring-Push-Notification-Re-Engagement-Campaigns) +Push-notification campaigns are used to create re-engagements with existing users โ†’ [Learn more here](https://support.appsflyer.com/hc/en-us/articles/207364076-Measuring-Push-Notification-Re-Engagement-Campaigns) -๐ŸŸฉ **Android:**
-The AppsFlyer SDK **requires a** **valid Activity context** to process the push payload. -**Do NOT call this method from the background isolate** (e.g., _firebaseMessagingBackgroundHandler), as the activity is not yet created. +### Platform-Specific Requirements + +๐ŸŸฉ **Android:** +The AppsFlyer SDK **requires a valid Activity context** to process the push payload. +**Do NOT call this method from the background isolate** (e.g., `_firebaseMessagingBackgroundHandler`), as the activity is not yet created. Instead, **delay calling this method** until the Flutter app is fully resumed and the activity is alive. -๐ŸŽ **iOS:**
+๐ŸŽ **iOS:** This method can be safely called at any point during app launch or when receiving a push notification. +--- + +## Integration Approaches + +AppsFlyer supports two approaches for measuring push notification campaigns: + +### Approach 1: Traditional Attribution Parameters (`af` object) -_**Usage example with Firebase Cloud Messaging:**_
-Given the fact that push message data contains custom key called `af` that contains the attribution data you want to send to AppsFlyer in JSON format. The following attribution parameters are required: `pid`, `is_retargeting`, `c`. +Use this approach when your push payload contains a custom `af` object with attribution parameters. -๐Ÿ“ฆ **Example Push Message Payload** +**Required parameters:** `pid`, `is_retargeting`, `c` + +๐Ÿ“ฆ **Example Push Payload with `af` Object:** ```json { - "af": { + "af": { "c": "test_campaign", "is_retargeting": true, - "pid": "push_provider_int", + "pid": "push_provider_int" }, "aps": { "alert": "Get 5000 Coins", @@ -717,38 +727,167 @@ Given the fact that push message data contains custom key called `af` that conta } ``` -1๏ธโƒฃ Handle Foreground Messages +**Implementation (Android & iOS):** + ```dart +// 1๏ธโƒฃ Handle Foreground Messages FirebaseMessaging.onMessage.listen((RemoteMessage message) { appsFlyerSdk.sendPushNotificationData(message.data); }); -``` -2๏ธโƒฃ Handle Notification Taps (App in Background) -```dart + +// 2๏ธโƒฃ Handle Notification Taps (App in Background) FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { appsFlyerSdk.sendPushNotificationData(message.data); }); + +// 3๏ธโƒฃ Handle App Launch from Push (Terminated State) +// Store payload in background handler, then pass to AppsFlyer when app resumes +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString('pending_af_push', jsonEncode(message.data)); +} + +// In your main() or splash screen after Flutter is initialized: +void handlePendingPush() async { + final prefs = await SharedPreferences.getInstance(); + final json = prefs.getString('pending_af_push'); + if (json != null) { + final payload = jsonDecode(json); + appsFlyerSdk.sendPushNotificationData(payload); + await prefs.remove('pending_af_push'); + } +} ``` -3๏ธโƒฃ Handle App Launch from Push (Terminated State) -Store the payload using `_firebaseMessagingBackgroundHandler`, then pass it to AppsFlyer once the app is resumed. + +Call `handlePendingPush()` during app startup (e.g., in your `main()` or inside your splash screen after ensuring Flutter is initialized). + +--- + +### Approach 2: OneLink URL in Push Payload (Recommended) + +Use this approach when your push payload contains a **OneLink URL** for deep linking. This method provides a unified deep linking experience. + +> โš ๏ธ **Important:** This approach requires calling **two different methods** depending on the platform! + +#### **Step 1: Configure Deep Link Path (BOTH Platforms)** + +Call `addPushNotificationDeepLinkPath` **BEFORE** initializing the SDK to tell AppsFlyer where to find the OneLink URL in your push payload. + ```dart +// Must be called BEFORE initSdk() or startSDK() +appsFlyerSdk.addPushNotificationDeepLinkPath(["deeply", "nested", "deep_link"]); + +// Then initialize the SDK +await appsFlyerSdk.initSdk( + registerOnDeepLinkingCallback: true // Enable deep linking callback +); +``` + +#### **Step 2: Send Push Payload to SDK** + +**๐ŸŸฉ Android:** +On Android, calling `addPushNotificationDeepLinkPath` is **sufficient**. The SDK automatically extracts and processes the OneLink URL. + +**๐ŸŽ iOS:** +On iOS, you **MUST also call** `sendPushNotificationData(userInfo)` to pass the push payload to the SDK. The SDK then internally calls `handlePushNotification` to extract and process the OneLink URL. + +๐Ÿ“ฆ **Example Push Payload with OneLink URL:** +```json +{ + "deeply": { + "nested": { + "deep_link": "https://yourapp.onelink.me/ABC/campaign123" + } + }, + "aps": { + "alert": "Check out our new feature!", + "badge": "1", + "sound": "default" + } +} +``` + +**Complete Implementation Example:** + +```dart +// ======================================== +// 1. Configure SDK (in main.dart or app initialization) +// ======================================== +void initializeAppsFlyer() async { + // STEP 1: Configure the deep link path BEFORE starting SDK + appsFlyerSdk.addPushNotificationDeepLinkPath(["deeply", "nested", "deep_link"]); + + // STEP 2: Initialize SDK with deep linking callback + await appsFlyerSdk.initSdk( + registerOnDeepLinkingCallback: true + ); + + // STEP 3: Set up deep linking callback to handle the OneLink URL + appsFlyerSdk.onDeepLinking((DeepLinkResult result) { + if (result.status == Status.FOUND) { + print("Deep link found: ${result.deepLink?.deepLinkValue}"); + // Handle deep link navigation here + } + }); +} + +// ======================================== +// 2. Handle Push Notifications +// ======================================== + +// ๐ŸŽ iOS: MUST call sendPushNotificationData +// ๐ŸŸฉ Android: Optional (SDK auto-handles), but recommended for consistency + +// 1๏ธโƒฃ Foreground Messages +FirebaseMessaging.onMessage.listen((RemoteMessage message) { + // iOS: Required to process OneLink URL + // Android: SDK processes automatically, but calling doesn't hurt + appsFlyerSdk.sendPushNotificationData(message.data); +}); + +// 2๏ธโƒฃ Background Notification Taps (App in Background) +FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { + // iOS: Required to process OneLink URL + appsFlyerSdk.sendPushNotificationData(message.data); +}); + +// 3๏ธโƒฃ App Launch from Push (Terminated State) Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString('pending_af_push', jsonEncode(message.data)); } -// In your main() or splash screen after Flutter is initialized: +// In main() or splash screen: void handlePendingPush() async { final prefs = await SharedPreferences.getInstance(); final json = prefs.getString('pending_af_push'); if (json != null) { final payload = jsonDecode(json); + // iOS: Required to process OneLink URL from terminated state appsFlyerSdk.sendPushNotificationData(payload); await prefs.remove('pending_af_push'); } } ``` -Call handlePendingPush() during app startup (e.g., in your main() or inside your splash screen after ensuring Flutter is initialized). + +#### **Key Differences Between Approaches:** + +|| Traditional `af` Object | OneLink URL (Recommended) | +|---|---|---| +| **Android** | `sendPushNotificationData(data)` | `addPushNotificationDeepLinkPath()` (auto-handles) | +| **iOS** | `sendPushNotificationData(data)` | `addPushNotificationDeepLinkPath()` **+** `sendPushNotificationData(data)` | +| **Deep Linking** | Basic attribution only | Full deep linking with `onDeepLinking` callback | +| **Use Case** | Simple re-engagement | Re-engagement + in-app navigation | + +--- + +### Summary + +- **Traditional approach**: Always call `sendPushNotificationData(payload)` on both platforms +- **OneLink approach (Recommended)**: + - โœ… **Both platforms**: Call `addPushNotificationDeepLinkPath()` before SDK init + - โœ… **iOS only**: Also call `sendPushNotificationData(payload)` when push is received + - โœ… **Both platforms**: Handle deep links in `onDeepLinking` callback --- diff --git a/doc/DeepLink.md b/doc/DeepLink.md index a356e18..28f9a99 100644 --- a/doc/DeepLink.md +++ b/doc/DeepLink.md @@ -1,8 +1,29 @@ # Deep linking +> โš ๏ธ **IMPORTANT: Flutter 3.27+ Breaking Change** +> +> Starting from Flutter 3.27, the default value for Flutter's deep linking option has changed from `false` to `true`. This means Flutter's built-in deep linking is now enabled by default, which can conflict with third-party deep linking plugins like AppsFlyer. +> +> **If you're using Flutter 3.27 or higher, you MUST disable Flutter's built-in deep linking** by adding the following configurations: +> +> **Android** - Add to your `AndroidManifest.xml` inside the `` tag: +> +> ```xml +> +> ``` +> +> **iOS** - Add to your `Info.plist` file: +> +> ```xml +> FlutterDeepLinkingEnabled +> +> ``` +> +> For more details, see the [official Flutter documentation](https://docs.flutter.dev/release/breaking-changes/deep-links-flag-change). + Deep Linking vs Deferred Deep Linking: -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. +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. 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. diff --git a/doc/Guides.md b/doc/Guides.md index ce5519d..750124c 100644 --- a/doc/Guides.md +++ b/doc/Guides.md @@ -69,6 +69,28 @@ Please make sure to go over [this guide](https://support.appsflyer.com/hc/en-us/ --- ##
Deep Linking + +> โš ๏ธ **IMPORTANT: Flutter 3.27+ Breaking Change** +> +> Starting from Flutter 3.27, the default value for Flutter's deep linking option has changed from `false` to `true`. This means Flutter's built-in deep linking is now enabled by default, which can conflict with third-party deep linking plugins like AppsFlyer. +> +> **If you're using Flutter 3.27 or higher, you MUST disable Flutter's built-in deep linking** by adding the following configurations: +> +> **Android** - Add to your `AndroidManifest.xml` inside the `` tag: +> +> ```xml +> +> ``` +> +> **iOS** - Add to your `Info.plist` file: +> +> ```xml +> FlutterDeepLinkingEnabled +> +> ``` +> +> For more details, see the [official Flutter documentation](https://docs.flutter.dev/release/breaking-changes/deep-links-flag-change). + diff --git a/doc/PurchaseConnector.md b/doc/PurchaseConnector.md index b4ef8f7..6c63c52 100644 --- a/doc/PurchaseConnector.md +++ b/doc/PurchaseConnector.md @@ -45,9 +45,16 @@ support@appsflyer.com ## โš ๏ธ โš ๏ธ Important Note โš ๏ธ โš ๏ธ +> **๐Ÿšจ BREAKING CHANGE**: Starting with Purchase Connector version 2.2.0, the module now uses **Google Play Billing Library 8.x.x**. While Gradle will automatically resolve to version 8.x.x in your final APK, **we strongly recommend that your app also upgrades to Billing Library 8.x.x or higher** to ensure API compatibility. +> +> **Why this matters:** +> - If your app code still uses **older Billing Library APIs** (e.g., `querySkuDetailsAsync()` from versions 5-7), these APIs were **removed in version 8** and **will cause runtime crashes** (`NoSuchMethodError`). +> - **Version 8 introduced new APIs** like `queryProductDetailsAsync()` that replace the deprecated methods. +> - **Recommendation**: Update your app's billing integration to use Billing Library 8.x.x APIs to prevent runtime issues. + The Purchase Connector feature of the AppsFlyer SDK depends on specific libraries provided by Google and Apple for managing in-app purchases: -- For Android, it depends on the [Google Play Billing Library](https://developer.android.com/google/play/billing/integrate) (Supported versions: 5.x.x - 7.x.x). +- For Android, it depends on the [Google Play Billing Library](https://developer.android.com/google/play/billing/integrate) (Minimum required version: 8.x.x and higher). - For iOS, it depends on [StoreKit](https://developer.apple.com/documentation/storekit). (Supported versions are StoreKit V1 + V2) However, these dependencies aren't actively included with the SDK. This means that the responsibility of managing these dependencies and including the necessary libraries in your project falls on you as the consumer of the SDK. @@ -345,7 +352,7 @@ To test purchases in an iOS environment on a real device with a TestFlight sandb final purchaseConnector = PurchaseConnector( config: PurchaseConnectorConfiguration( sandbox: true, // Enable sandbox for testing - storeKitVersion: StoreKitVersion.storeKit2, // Use StoreKit 2 for enhanced testing + storeKitVersion: StoreKitVersion.storeKit2, logSubscriptions: true, logInApps: true, ), diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 94adc3a..d74c11a 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4096M -XX:MaxMetaspaceSize=1024m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/example/ios/Podfile b/example/ios/Podfile index a72f9d7..a4220ff 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '12.0' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -32,6 +32,10 @@ target 'Runner' do use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + + # Google Ads On-Device Conversion SDK for testing AppsFlyer integration + pod 'GoogleAdsOnDeviceConversion', '~> 3.2.0' + target 'RunnerTests' do inherit! :search_paths end @@ -41,7 +45,7 @@ post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) target.build_configurations.each do |config| - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0' + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' end end end diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..b194b79 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,770 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 76EE207FCCACA26B86D9DB82 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64AB0D3E7E64D32945839514 /* Pods_RunnerTests.framework */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + 8BCD5DFAB75A12A30E211076 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5039C24645EBF280C2BA493C /* Pods_Runner.framework */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 5039C24645EBF280C2BA493C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5BCE328CFF9133F9ED21F991 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 64AB0D3E7E64D32945839514 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9E8B4CB342EC3B37664BA0D8 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + B136DB7A9307DF7BCA0C7FBB /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + C9F42F6C3D50967F3C103876 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + F2807C307C2224904744BDAB /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + FB7689F8FA4D75CF997E4F5C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 2892185B0C8A362FF17F84E0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 76EE207FCCACA26B86D9DB82 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + 8BCD5DFAB75A12A30E211076 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9394675F9B08C1249B8F3C96 /* Pods */ = { + isa = PBXGroup; + children = ( + F2807C307C2224904744BDAB /* Pods-Runner.debug.xcconfig */, + FB7689F8FA4D75CF997E4F5C /* Pods-Runner.release.xcconfig */, + B136DB7A9307DF7BCA0C7FBB /* Pods-Runner.profile.xcconfig */, + 9E8B4CB342EC3B37664BA0D8 /* Pods-RunnerTests.debug.xcconfig */, + C9F42F6C3D50967F3C103876 /* Pods-RunnerTests.release.xcconfig */, + 5BCE328CFF9133F9ED21F991 /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 9394675F9B08C1249B8F3C96 /* Pods */, + B99305DCE4DB856ED0A78A75 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + B99305DCE4DB856ED0A78A75 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 5039C24645EBF280C2BA493C /* Pods_Runner.framework */, + 64AB0D3E7E64D32945839514 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + FEEC7F8166AF9F7CB31591F0 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 2892185B0C8A362FF17F84E0 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + C890491DE91035C837CBA73B /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + D2F636E2006BF2310FF8EF27 /* [CP] Copy Pods Resources */, + CBA4E0AD93E266A8CFF44591 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + C890491DE91035C837CBA73B /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + CBA4E0AD93E266A8CFF44591 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + D2F636E2006BF2310FF8EF27 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + FEEC7F8166AF9F7CB31591F0 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 6UQAD4B3U2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.appsflyer.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9E8B4CB342EC3B37664BA0D8 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.appsflyer.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C9F42F6C3D50967F3C103876 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.appsflyer.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5BCE328CFF9133F9ED21F991 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.appsflyer.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 6UQAD4B3U2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.appsflyer.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 6UQAD4B3U2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.appsflyer.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/example/lib/app_constants.dart b/example/lib/app_constants.dart index 1f04096..a3641a3 100644 --- a/example/lib/app_constants.dart +++ b/example/lib/app_constants.dart @@ -1,4 +1,4 @@ class AppConstants { - static const double TOP_PADDING = 12.0; - static const double CONTAINER_PADDING = 8.0; + static const double topPadding = 12.0; + static const double containerPadding = 8.0; } diff --git a/example/lib/home_container.dart b/example/lib/home_container.dart index 5a6b3f5..e3e5376 100644 --- a/example/lib/home_container.dart +++ b/example/lib/home_container.dart @@ -9,18 +9,20 @@ class HomeContainer extends StatefulWidget { final Future Function(String, Map) logEvent; final void Function() logAdRevenueEvent; final Future?> Function(String, String) validatePurchase; - Object deepLinkData; + final Object? deepLinkData; + // ignore: prefer_const_constructors_in_immutables HomeContainer({ + Key? key, required this.onData, required this.deepLinkData, required this.logEvent, required this.logAdRevenueEvent, required this.validatePurchase, - }); + }) : super(key: key); @override - _HomeContainerState createState() => _HomeContainerState(); + State createState() => _HomeContainerState(); } class _HomeContainerState extends State { @@ -52,12 +54,12 @@ class _HomeContainerState extends State { Widget build(BuildContext context) { return SingleChildScrollView( child: Container( - padding: const EdgeInsets.all(AppConstants.CONTAINER_PADDING), + padding: const EdgeInsets.all(AppConstants.containerPadding), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: EdgeInsets.all(20.0), + padding: const EdgeInsets.all(20.0), decoration: BoxDecoration( color: Colors.white, border: Border.all(color: Colors.blueGrey, width: 0.5), @@ -65,7 +67,7 @@ class _HomeContainerState extends State { ), child: Column( children: [ - Text( + const Text( "APPSFLYER SDK", style: TextStyle( fontSize: 18, @@ -73,7 +75,7 @@ class _HomeContainerState extends State { fontWeight: FontWeight.w500, ), ), - SizedBox(height: AppConstants.TOP_PADDING), + const SizedBox(height: AppConstants.topPadding), TextBorder( controller: TextEditingController( text: widget.onData.isNotEmpty @@ -82,7 +84,7 @@ class _HomeContainerState extends State { ), labelText: "CONVERSION DATA", ), - SizedBox(height: 12.0), + const SizedBox(height: 12.0), TextBorder( controller: TextEditingController( text: widget.deepLinkData != null @@ -94,9 +96,9 @@ class _HomeContainerState extends State { ], ), ), - SizedBox(height: 12.0), + const SizedBox(height: 12.0), Container( - padding: EdgeInsets.all(20.0), + padding: const EdgeInsets.all(20.0), decoration: BoxDecoration( color: Colors.white, border: Border.all(color: Colors.blueGrey, width: 0.5), @@ -104,7 +106,7 @@ class _HomeContainerState extends State { ), child: Column( children: [ - Text( + const Text( "EVENT LOGGER", style: TextStyle( fontSize: 18, @@ -112,64 +114,63 @@ class _HomeContainerState extends State { fontWeight: FontWeight.w500, ), ), - SizedBox(height: 12.0), + const SizedBox(height: 12.0), TextBorder( controller: TextEditingController( text: "Event Name: $eventName\nEvent Values: $eventValues"), labelText: "EVENT REQUEST", ), - SizedBox(height: 12.0), + const SizedBox(height: 12.0), TextBorder( labelText: "SERVER RESPONSE", controller: TextEditingController(text: _logEventResponse), ), - SizedBox(height: 20.0), + const SizedBox(height: 20.0), ElevatedButton( onPressed: () { widget.logEvent(eventName, eventValues).then((onValue) { setState(() { - _logEventResponse = - "Event Status: " + onValue.toString(); + _logEventResponse = "Event Status: $onValue"; }); }).catchError((onError) { setState(() { - _logEventResponse = "Error: " + onError.toString(); + _logEventResponse = "Error: $onError"; }); }); }, - child: Text("Trigger Purchase Event"), style: ElevatedButton.styleFrom( backgroundColor: Colors.white, - padding: - EdgeInsets.symmetric(horizontal: 20, vertical: 10), - textStyle: TextStyle( + padding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 10), + textStyle: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), + child: Text("Trigger Purchase Event"), ), ElevatedButton( onPressed: () { widget.logAdRevenueEvent(); }, - child: Text("Trigger AdRevenue Event"), style: ElevatedButton.styleFrom( backgroundColor: Colors.white, - padding: - EdgeInsets.symmetric(horizontal: 20, vertical: 10), - textStyle: TextStyle( + padding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 10), + textStyle: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), + child: Text("Trigger AdRevenue Event"), ), ], ), ), - SizedBox(height: 12.0), + const SizedBox(height: 12.0), Container( - padding: EdgeInsets.all(20.0), + padding: const EdgeInsets.all(20.0), decoration: BoxDecoration( color: Colors.white, border: Border.all(color: Colors.blueGrey, width: 0.5), @@ -177,7 +178,7 @@ class _HomeContainerState extends State { ), child: Column( children: [ - Text( + const Text( "PURCHASE VALIDATION V2", style: TextStyle( fontSize: 18, @@ -185,10 +186,10 @@ class _HomeContainerState extends State { fontWeight: FontWeight.w500, ), ), - SizedBox(height: 12.0), + const SizedBox(height: 12.0), TextFormField( controller: _purchaseTokenController, - decoration: InputDecoration( + decoration: const InputDecoration( labelText: "Purchase Token", border: OutlineInputBorder(), contentPadding: @@ -196,23 +197,23 @@ class _HomeContainerState extends State { ), maxLines: 2, ), - SizedBox(height: 12.0), + const SizedBox(height: 12.0), TextFormField( controller: _productIdController, - decoration: InputDecoration( + decoration: const InputDecoration( labelText: "Product ID", border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), ), - SizedBox(height: 12.0), + const SizedBox(height: 12.0), TextBorder( labelText: "VALIDATION RESPONSE", controller: TextEditingController(text: _validationResponse), ), - SizedBox(height: 20.0), + const SizedBox(height: 20.0), ElevatedButton( onPressed: () { final purchaseToken = @@ -241,17 +242,17 @@ class _HomeContainerState extends State { }); }); }, - child: Text("Validate Purchase V2"), style: ElevatedButton.styleFrom( backgroundColor: Colors.green, foregroundColor: Colors.white, - padding: - EdgeInsets.symmetric(horizontal: 20, vertical: 10), - textStyle: TextStyle( + padding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 10), + textStyle: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), + child: Text("Validate Purchase V2"), ), ], ), diff --git a/example/lib/home_container_streams.dart b/example/lib/home_container_streams.dart index b9c22d5..ccfc962 100644 --- a/example/lib/home_container_streams.dart +++ b/example/lib/home_container_streams.dart @@ -9,14 +9,16 @@ class HomeContainerStreams extends StatefulWidget { final Stream onAttribution; final Future Function(String, Map) logEvent; + // ignore: prefer_const_constructors_in_immutables HomeContainerStreams({ + Key? key, required this.onData, required this.onAttribution, - required this.logEvent - }); + required this.logEvent, + }) : super(key: key); @override - _HomeContainerStreamsState createState() => _HomeContainerStreamsState(); + State createState() => _HomeContainerStreamsState(); } class _HomeContainerStreamsState extends State { @@ -28,30 +30,28 @@ class _HomeContainerStreamsState extends State { "af_revenue": "2" }; - String _logEventResponse = "Event status will be shown here once it's triggered."; + String _logEventResponse = + "Event status will be shown here once it's triggered."; @override Widget build(BuildContext context) { return SingleChildScrollView( child: Container( - padding: EdgeInsets.all(AppConstants.CONTAINER_PADDING), + padding: const EdgeInsets.all(AppConstants.containerPadding), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: EdgeInsets.all(20.0), + padding: const 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( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( + const Text( "APPSFLYER SDK", style: TextStyle( fontSize: 18, @@ -59,65 +59,64 @@ class _HomeContainerStreamsState extends State { fontWeight: FontWeight.w500, ), ), - SizedBox(height: AppConstants.TOP_PADDING), + const SizedBox(height: AppConstants.topPadding), StreamBuilder( stream: widget.onData.asBroadcastStream(), - builder: (BuildContext context, AsyncSnapshot snapshot) { + builder: (BuildContext context, + AsyncSnapshot snapshot) { return TextBorder( controller: TextEditingController( - text: snapshot.hasData ? Utils.formatJson(snapshot.data) : "Waiting for conversion data..." - ), + text: snapshot.hasData + ? Utils.formatJson(snapshot.data) + : "Waiting for conversion data..."), labelText: "CONVERSION DATA", ); }, ), - SizedBox(height: 12.0), + const SizedBox(height: 12.0), StreamBuilder( stream: widget.onAttribution.asBroadcastStream(), - builder: (BuildContext context, AsyncSnapshot snapshot) { + builder: (BuildContext context, + AsyncSnapshot snapshot) { return TextBorder( controller: TextEditingController( - text: snapshot.hasData ? Utils.formatJson(snapshot.data) : "Waiting for attribution data..." - ), + text: snapshot.hasData + ? Utils.formatJson(snapshot.data) + : "Waiting for attribution data..."), labelText: "ATTRIBUTION DATA", ); - } - ), + }), ], - ) - ), - SizedBox(height: 12.0), + )), + const SizedBox(height: 12.0), Container( - padding: EdgeInsets.all(20.0), + padding: const EdgeInsets.all(20.0), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(5), - border: Border.all( - color: Colors.grey, - width: 0.5 - ), + border: Border.all(color: Colors.grey, width: 0.5), ), child: Column(children: [ - Text( + const Text( "EVENT LOGGER", style: TextStyle( fontSize: 18, fontWeight: FontWeight.w500, ), ), - SizedBox(height: 12.0), + const 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), + const SizedBox(height: 12.0), TextBorder( labelText: "SERVER RESPONSE", controller: TextEditingController(text: _logEventResponse), ), - SizedBox(height: 20), + const SizedBox(height: 20), ElevatedButton( onPressed: () { widget.logEvent(eventName, eventValues).then((onValue) { @@ -130,15 +129,16 @@ class _HomeContainerStreamsState extends State { }); }); }, - child: Text("Trigger Event"), style: ElevatedButton.styleFrom( backgroundColor: Colors.white, - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), - textStyle: TextStyle( + padding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 10), + textStyle: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), + child: const Text("Trigger Event"), ), ]), ) @@ -147,4 +147,4 @@ class _HomeContainerStreamsState extends State { ), ); } -} \ No newline at end of file +} diff --git a/example/lib/main.dart b/example/lib/main.dart index a3e17ee..82ec4ac 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -7,14 +7,16 @@ import './main_page.dart'; Future main() async { await dotenv.load(fileName: '.env'); - runApp(MyApp()); + runApp(const MyApp()); } class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { - return new MaterialApp( - home: new MainPage(), + return const MaterialApp( + home: MainPage(), ); } } diff --git a/example/lib/main_page.dart b/example/lib/main_page.dart index 3cc37d4..a3fd113 100644 --- a/example/lib/main_page.dart +++ b/example/lib/main_page.dart @@ -7,6 +7,8 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'home_container.dart'; class MainPage extends StatefulWidget { + const MainPage({Key? key}) : super(key: key); + @override State createState() { return MainPageState(); @@ -62,7 +64,7 @@ class MainPageState extends State { // Conversion data callback _appsflyerSdk.onInstallConversionData((res) { - print("onInstallConversionData res: " + res.toString()); + print("onInstallConversionData res: $res"); setState(() { _gcd = res; }); @@ -70,7 +72,7 @@ class MainPageState extends State { // App open attribution callback _appsflyerSdk.onAppOpenAttribution((res) { - print("onAppOpenAttribution res: " + res.toString()); + print("onAppOpenAttribution res: $res"); setState(() { _deepLinkData = res; }); @@ -93,7 +95,7 @@ class MainPageState extends State { print("deep link status parsing error"); break; } - print("onDeepLinking res: " + dp.toString()); + print("onDeepLinking res: $dp"); setState(() { _deepLinkData = dp.toJson(); }); @@ -110,7 +112,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, ), @@ -140,7 +142,7 @@ class MainPageState extends State { }, ); }, - child: Text("START SDK"), + child: const Text("START SDK"), ) ], ), diff --git a/example/lib/text_border.dart b/example/lib/text_border.dart index 6643352..cf0c23c 100644 --- a/example/lib/text_border.dart +++ b/example/lib/text_border.dart @@ -5,35 +5,31 @@ class TextBorder extends StatelessWidget { final TextEditingController controller; final String labelText; - const TextBorder({ - required this.controller, - required this.labelText, - Key? key - }) : super(key: key); + const TextBorder( + {required this.controller, required this.labelText, Key? key}) + : super(key: key); @override Widget build(BuildContext context) { return Container( - margin: const EdgeInsets.symmetric(vertical: 8.0), // Add some vertical margin + margin: + const EdgeInsets.symmetric(vertical: 8.0), // Add some vertical margin child: TextField( controller: controller, enabled: false, maxLines: null, decoration: InputDecoration( labelText: labelText, - labelStyle: TextStyle(color: Colors.blueGrey), // Change the color of the label - border: OutlineInputBorder( - borderSide: BorderSide( - width: 1.0 - ), + labelStyle: const TextStyle( + color: Colors.blueGrey), // Change the color of the label + border: const OutlineInputBorder( + borderSide: BorderSide(width: 1.0), ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - width: 1.0 - ), + focusedBorder: const OutlineInputBorder( + borderSide: BorderSide(width: 1.0), ), ), ), ); } -} \ No newline at end of file +} 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..c33344d 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -28,6 +28,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + flutter_lints: ^4.0.0 # For information on the generic Dart part of this file, see the # following page: https://www.dartlang.org/tools/pub/pubspec diff --git a/ios/Classes/AppsflyerSdkPlugin.h b/ios/Classes/AppsflyerSdkPlugin.h index 5bc2e99..37fb867 100644 --- a/ios/Classes/AppsflyerSdkPlugin.h +++ b/ios/Classes/AppsflyerSdkPlugin.h @@ -18,7 +18,7 @@ @end // Appsflyer JS objects -#define kAppsFlyerPluginVersion @"6.17.5" +#define kAppsFlyerPluginVersion @"6.17.6" #define afDevKey @"afDevKey" #define afAppId @"afAppId" #define afIsDebug @"isDebug" diff --git a/ios/appsflyer_sdk.podspec b/ios/appsflyer_sdk.podspec index 64c081e..dcca906 100644 --- a/ios/appsflyer_sdk.podspec +++ b/ios/appsflyer_sdk.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'appsflyer_sdk' - s.version = '6.17.3' + s.version = '6.17.7' s.summary = 'AppsFlyer Integration for Flutter' s.description = 'AppsFlyer is the market leader in mobile advertising attribution & analytics, helping marketers to pinpoint their targeting, optimize their ad spend and boost their ROI.' s.homepage = 'https://github.com/AppsFlyerSDK/flutter_appsflyer_sdk' @@ -21,12 +21,12 @@ Pod::Spec.new do |s| ss.source_files = 'Classes/**/*' ss.public_header_files = 'Classes/**/*.h' ss.dependency 'Flutter' - ss.ios.dependency 'AppsFlyerFramework','6.17.5' + ss.ios.dependency 'AppsFlyerFramework','6.17.7' end s.subspec 'PurchaseConnector' do |ss| ss.dependency 'Flutter' - ss.ios.dependency 'PurchaseConnector', '6.17.5' + ss.ios.dependency 'PurchaseConnector', '6.17.7' ss.source_files = 'PurchaseConnector/**/*' ss.public_header_files = 'PurchaseConnector/**/*.h' diff --git a/lib/src/appsflyer_ad_revenue_data.dart b/lib/src/appsflyer_ad_revenue_data.dart index 7482cf7..c759caa 100644 --- a/lib/src/appsflyer_ad_revenue_data.dart +++ b/lib/src/appsflyer_ad_revenue_data.dart @@ -7,13 +7,12 @@ class AdRevenueData { final double revenue; final Map? additionalParameters; - AdRevenueData({ - required this.monetizationNetwork, - required this.mediationNetwork, - required this.currencyIso4217Code, - required this.revenue, - this.additionalParameters - }); + AdRevenueData( + {required this.monetizationNetwork, + required this.mediationNetwork, + required this.currencyIso4217Code, + required this.revenue, + this.additionalParameters}); Map toMap() { return { diff --git a/lib/src/appsflyer_consent.dart b/lib/src/appsflyer_consent.dart index 300c708..416ce67 100644 --- a/lib/src/appsflyer_consent.dart +++ b/lib/src/appsflyer_consent.dart @@ -12,23 +12,20 @@ class AppsFlyerConsent { }); // Factory constructors - factory AppsFlyerConsent.forGDPRUser({ - required bool hasConsentForDataUsage, - required bool hasConsentForAdsPersonalization - }){ + factory AppsFlyerConsent.forGDPRUser( + {required bool hasConsentForDataUsage, + required bool hasConsentForAdsPersonalization}) { return AppsFlyerConsent._( isUserSubjectToGDPR: true, hasConsentForDataUsage: hasConsentForDataUsage, - hasConsentForAdsPersonalization: hasConsentForAdsPersonalization - ); + hasConsentForAdsPersonalization: hasConsentForAdsPersonalization); } - factory AppsFlyerConsent.nonGDPRUser(){ + factory AppsFlyerConsent.nonGDPRUser() { return AppsFlyerConsent._( isUserSubjectToGDPR: false, hasConsentForDataUsage: false, - hasConsentForAdsPersonalization: false - ); + hasConsentForAdsPersonalization: false); } // Converts object to a map @@ -39,4 +36,4 @@ class AppsFlyerConsent { 'hasConsentForAdsPersonalization': hasConsentForAdsPersonalization, }; } -} \ No newline at end of file +} diff --git a/lib/src/appsflyer_constants.dart b/lib/src/appsflyer_constants.dart index e4ea22b..7dbe6a4 100644 --- a/lib/src/appsflyer_constants.dart +++ b/lib/src/appsflyer_constants.dart @@ -3,7 +3,7 @@ part of appsflyer_sdk; enum EmailCryptType { EmailCryptTypeNone, EmailCryptTypeSHA256 } class AppsflyerConstants { - static const String PLUGIN_VERSION = "6.17.3"; + static const String PLUGIN_VERSION = "6.17.7"; static const String AF_DEV_KEY = "afDevKey"; static const String AF_APP_Id = "afAppId"; static const String AF_IS_DEBUG = "isDebug"; diff --git a/lib/src/appsflyer_invite_link_params.dart b/lib/src/appsflyer_invite_link_params.dart index 772d23c..43d8e32 100644 --- a/lib/src/appsflyer_invite_link_params.dart +++ b/lib/src/appsflyer_invite_link_params.dart @@ -14,14 +14,13 @@ class AppsFlyerInviteLinkParams { /// Creates an [AppsFlyerInviteLinkParams] instance. /// All parameters are optional, allowing greater flexibility when /// invoking the constructor. - AppsFlyerInviteLinkParams({ - this.campaign, - this.channel, - this.referrerName, - this.baseDeepLink, - this.brandDomain, - this.customerID, - this.referrerImageUrl, - this.customParams - }); + AppsFlyerInviteLinkParams( + {this.campaign, + this.channel, + this.referrerName, + this.baseDeepLink, + this.brandDomain, + this.customerID, + this.referrerImageUrl, + this.customParams}); } diff --git a/lib/src/appsflyer_request_listener.dart b/lib/src/appsflyer_request_listener.dart index 3bf8f19..45e6af6 100644 --- a/lib/src/appsflyer_request_listener.dart +++ b/lib/src/appsflyer_request_listener.dart @@ -8,4 +8,4 @@ class AppsFlyerRequestListener { required this.onSuccess, required this.onError, }); -} \ No newline at end of file +} diff --git a/lib/src/appsflyer_sdk.dart b/lib/src/appsflyer_sdk.dart index 5336fa4..50e5472 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; @@ -9,8 +10,8 @@ class AppsflyerSdk { AppsFlyerOptions? afOptions; Map? mapOptions; - ///Returns the [AppsflyerSdk] instance, initialized with a custom options - ///provided by the user + /// Returns the [AppsflyerSdk] instance, initialized with a custom options + /// provided by the user factory AppsflyerSdk(options) { if (_instance == null) { MethodChannel methodChannel = diff --git a/lib/src/purchase_connector/connector_callbacks.dart b/lib/src/purchase_connector/connector_callbacks.dart index c908cfe..554e504 100644 --- a/lib/src/purchase_connector/connector_callbacks.dart +++ b/lib/src/purchase_connector/connector_callbacks.dart @@ -2,9 +2,11 @@ part of appsflyer_sdk; /// Type definition for a general purpose listener. typedef PurchaseConnectorListener = Function(dynamic); + /// Type definition for a listener which gets called when the `PurchaseConnectorImpl` receives purchase revenue validation info for iOS. typedef DidReceivePurchaseRevenueValidationInfo = Function( Map? validationInfo, IosError? error); + /// Invoked when a 200 OK response is received from the server. /// Note: An INVALID purchase is considered to be a successful response and will also be returned by this callback. /// diff --git a/lib/src/purchase_connector/missing_configuration_exception.dart b/lib/src/purchase_connector/missing_configuration_exception.dart index eae2367..8d10458 100644 --- a/lib/src/purchase_connector/missing_configuration_exception.dart +++ b/lib/src/purchase_connector/missing_configuration_exception.dart @@ -1,4 +1,5 @@ part of appsflyer_sdk; + /// Exception thrown when the configuration is missing. class MissingConfigurationException implements Exception { final String message; diff --git a/lib/src/purchase_connector/models/in_app_purchase_validation_result.dart b/lib/src/purchase_connector/models/in_app_purchase_validation_result.dart index 0846a18..4cf6e4a 100644 --- a/lib/src/purchase_connector/models/in_app_purchase_validation_result.dart +++ b/lib/src/purchase_connector/models/in_app_purchase_validation_result.dart @@ -2,32 +2,28 @@ part of appsflyer_sdk; @JsonSerializable() class InAppPurchaseValidationResult { - bool success; ProductPurchase? productPurchase; ValidationFailureData? failureData; InAppPurchaseValidationResult( - this.success, - this.productPurchase, - this.failureData - ); - - + this.success, this.productPurchase, this.failureData); - factory InAppPurchaseValidationResult.fromJson(Map json) => _$InAppPurchaseValidationResultFromJson(json); + factory InAppPurchaseValidationResult.fromJson(Map json) => + _$InAppPurchaseValidationResultFromJson(json); Map toJson() => _$InAppPurchaseValidationResultToJson(this); - } @JsonSerializable() -class InAppPurchaseValidationResultMap{ +class InAppPurchaseValidationResultMap { Map result; InAppPurchaseValidationResultMap(this.result); - factory InAppPurchaseValidationResultMap.fromJson(Map json) => _$InAppPurchaseValidationResultMapFromJson(json); - - Map toJson() => _$InAppPurchaseValidationResultMapToJson(this); + factory InAppPurchaseValidationResultMap.fromJson( + Map json) => + _$InAppPurchaseValidationResultMapFromJson(json); + Map toJson() => + _$InAppPurchaseValidationResultMapToJson(this); } diff --git a/lib/src/purchase_connector/models/ios_error.dart b/lib/src/purchase_connector/models/ios_error.dart index aa67106..fd9b1a7 100644 --- a/lib/src/purchase_connector/models/ios_error.dart +++ b/lib/src/purchase_connector/models/ios_error.dart @@ -1,15 +1,15 @@ part of appsflyer_sdk; @JsonSerializable() -class IosError{ +class IosError { String localizedDescription; String domain; int code; - IosError(this.localizedDescription, this.domain, this.code); - factory IosError.fromJson(Map json) => _$IosErrorFromJson(json); + factory IosError.fromJson(Map json) => + _$IosErrorFromJson(json); Map toJson() => _$IosErrorToJson(this); -} \ No newline at end of file +} diff --git a/lib/src/purchase_connector/models/jvm_throwable.dart b/lib/src/purchase_connector/models/jvm_throwable.dart index 04fb07e..d587819 100644 --- a/lib/src/purchase_connector/models/jvm_throwable.dart +++ b/lib/src/purchase_connector/models/jvm_throwable.dart @@ -1,7 +1,7 @@ part of appsflyer_sdk; @JsonSerializable() -class JVMThrowable{ +class JVMThrowable { String type; String message; String stacktrace; @@ -9,8 +9,8 @@ class JVMThrowable{ JVMThrowable(this.type, this.message, this.stacktrace, this.cause); - factory JVMThrowable.fromJson(Map json) => _$JVMThrowableFromJson(json); + factory JVMThrowable.fromJson(Map json) => + _$JVMThrowableFromJson(json); Map toJson() => _$JVMThrowableToJson(this); - -} \ No newline at end of file +} diff --git a/lib/src/purchase_connector/models/product_purchase.dart b/lib/src/purchase_connector/models/product_purchase.dart index e6e3ceb..9e7bcc2 100644 --- a/lib/src/purchase_connector/models/product_purchase.dart +++ b/lib/src/purchase_connector/models/product_purchase.dart @@ -2,7 +2,6 @@ part of appsflyer_sdk; @JsonSerializable() class ProductPurchase { - String kind; String purchaseTimeMillis; int purchaseState; @@ -32,13 +31,10 @@ class ProductPurchase { this.quantity, this.obfuscatedExternalAccountId, this.obfuscatedExternalProfileId, - this.regionCode - ); - + this.regionCode); - - factory ProductPurchase.fromJson(Map json) => _$ProductPurchaseFromJson(json); + factory ProductPurchase.fromJson(Map json) => + _$ProductPurchaseFromJson(json); Map toJson() => _$ProductPurchaseToJson(this); - -} \ No newline at end of file +} diff --git a/lib/src/purchase_connector/models/subscription_purchase.dart b/lib/src/purchase_connector/models/subscription_purchase.dart index 55ede1e..ec3b153 100644 --- a/lib/src/purchase_connector/models/subscription_purchase.dart +++ b/lib/src/purchase_connector/models/subscription_purchase.dart @@ -2,7 +2,6 @@ part of appsflyer_sdk; @JsonSerializable() class SubscriptionPurchase { - String acknowledgementState; CanceledStateContext? canceledStateContext; ExternalAccountIdentifiers? externalAccountIdentifiers; @@ -30,21 +29,16 @@ class SubscriptionPurchase { this.startTime, this.subscribeWithGoogleInfo, this.subscriptionState, - this.testPurchase - ); - + this.testPurchase); - - factory SubscriptionPurchase.fromJson(Map json) => _$SubscriptionPurchaseFromJson(json); + factory SubscriptionPurchase.fromJson(Map json) => + _$SubscriptionPurchaseFromJson(json); Map toJson() => _$SubscriptionPurchaseToJson(this); - } - @JsonSerializable() class CanceledStateContext { - DeveloperInitiatedCancellation? developerInitiatedCancellation; ReplacementCancellation? replacementCancellation; SystemInitiatedCancellation? systemInitiatedCancellation; @@ -54,104 +48,84 @@ class CanceledStateContext { this.developerInitiatedCancellation, this.replacementCancellation, this.systemInitiatedCancellation, - this.userInitiatedCancellation - ); - - + this.userInitiatedCancellation); - factory CanceledStateContext.fromJson(Map json) => _$CanceledStateContextFromJson(json); + factory CanceledStateContext.fromJson(Map json) => + _$CanceledStateContextFromJson(json); Map toJson() => _$CanceledStateContextToJson(this); - } @JsonSerializable() -class DeveloperInitiatedCancellation{ +class DeveloperInitiatedCancellation { DeveloperInitiatedCancellation(); - factory DeveloperInitiatedCancellation.fromJson(Map json) => _$DeveloperInitiatedCancellationFromJson(json); + factory DeveloperInitiatedCancellation.fromJson(Map json) => + _$DeveloperInitiatedCancellationFromJson(json); Map toJson() => _$DeveloperInitiatedCancellationToJson(this); } @JsonSerializable() -class ReplacementCancellation{ +class ReplacementCancellation { ReplacementCancellation(); - factory ReplacementCancellation.fromJson(Map json) => _$ReplacementCancellationFromJson(json); + factory ReplacementCancellation.fromJson(Map json) => + _$ReplacementCancellationFromJson(json); Map toJson() => _$ReplacementCancellationToJson(this); } @JsonSerializable() -class SystemInitiatedCancellation{ +class SystemInitiatedCancellation { SystemInitiatedCancellation(); - factory SystemInitiatedCancellation.fromJson(Map json) => _$SystemInitiatedCancellationFromJson(json); + factory SystemInitiatedCancellation.fromJson(Map json) => + _$SystemInitiatedCancellationFromJson(json); Map toJson() => _$SystemInitiatedCancellationToJson(this); } - @JsonSerializable() class UserInitiatedCancellation { - CancelSurveyResult? cancelSurveyResult; String cancelTime; - UserInitiatedCancellation( - this.cancelSurveyResult, - this.cancelTime - ); - + UserInitiatedCancellation(this.cancelSurveyResult, this.cancelTime); - - factory UserInitiatedCancellation.fromJson(Map json) => _$UserInitiatedCancellationFromJson(json); + factory UserInitiatedCancellation.fromJson(Map json) => + _$UserInitiatedCancellationFromJson(json); Map toJson() => _$UserInitiatedCancellationToJson(this); - } @JsonSerializable() class CancelSurveyResult { - String reason; String reasonUserInput; - CancelSurveyResult( - this.reason, - this.reasonUserInput - ); - + CancelSurveyResult(this.reason, this.reasonUserInput); - - factory CancelSurveyResult.fromJson(Map json) => _$CancelSurveyResultFromJson(json); + factory CancelSurveyResult.fromJson(Map json) => + _$CancelSurveyResultFromJson(json); Map toJson() => _$CancelSurveyResultToJson(this); - } @JsonSerializable() class ExternalAccountIdentifiers { - String externalAccountId; String obfuscatedExternalAccountId; String obfuscatedExternalProfileId; - ExternalAccountIdentifiers( - this.externalAccountId, - this.obfuscatedExternalAccountId, - this.obfuscatedExternalProfileId - ); - + ExternalAccountIdentifiers(this.externalAccountId, + this.obfuscatedExternalAccountId, this.obfuscatedExternalProfileId); - - factory ExternalAccountIdentifiers.fromJson(Map json) => _$ExternalAccountIdentifiersFromJson(json); + factory ExternalAccountIdentifiers.fromJson(Map json) => + _$ExternalAccountIdentifiersFromJson(json); Map toJson() => _$ExternalAccountIdentifiersToJson(this); - } @JsonSerializable() class SubscriptionPurchaseLineItem { - AutoRenewingPlan? autoRenewingPlan; DeferredItemReplacement? deferredItemReplacement; String expiryTime; @@ -165,179 +139,130 @@ class SubscriptionPurchaseLineItem { this.expiryTime, this.offerDetails, this.prepaidPlan, - this.productId - ); - + this.productId); - - factory SubscriptionPurchaseLineItem.fromJson(Map json) => _$SubscriptionPurchaseLineItemFromJson(json); + factory SubscriptionPurchaseLineItem.fromJson(Map json) => + _$SubscriptionPurchaseLineItemFromJson(json); Map toJson() => _$SubscriptionPurchaseLineItemToJson(this); - } @JsonSerializable() class OfferDetails { - List? offerTags; String basePlanId; String? offerId; - OfferDetails( - this.offerTags, - this.basePlanId, - this.offerId - ); - + OfferDetails(this.offerTags, this.basePlanId, this.offerId); - - factory OfferDetails.fromJson(Map json) => _$OfferDetailsFromJson(json); + factory OfferDetails.fromJson(Map json) => + _$OfferDetailsFromJson(json); Map toJson() => _$OfferDetailsToJson(this); - } @JsonSerializable() class AutoRenewingPlan { - bool? autoRenewEnabled; SubscriptionItemPriceChangeDetails? priceChangeDetails; - AutoRenewingPlan( - this.autoRenewEnabled, - this.priceChangeDetails - ); + AutoRenewingPlan(this.autoRenewEnabled, this.priceChangeDetails); - - - factory AutoRenewingPlan.fromJson(Map json) => _$AutoRenewingPlanFromJson(json); + factory AutoRenewingPlan.fromJson(Map json) => + _$AutoRenewingPlanFromJson(json); Map toJson() => _$AutoRenewingPlanToJson(this); - } @JsonSerializable() class SubscriptionItemPriceChangeDetails { - String expectedNewPriceChargeTime; Money? newPrice; String priceChangeMode; String priceChangeState; - SubscriptionItemPriceChangeDetails( - this.expectedNewPriceChargeTime, - this.newPrice, - this.priceChangeMode, - this.priceChangeState - ); + SubscriptionItemPriceChangeDetails(this.expectedNewPriceChargeTime, + this.newPrice, this.priceChangeMode, this.priceChangeState); + factory SubscriptionItemPriceChangeDetails.fromJson( + Map json) => + _$SubscriptionItemPriceChangeDetailsFromJson(json); - - factory SubscriptionItemPriceChangeDetails.fromJson(Map json) => _$SubscriptionItemPriceChangeDetailsFromJson(json); - - Map toJson() => _$SubscriptionItemPriceChangeDetailsToJson(this); - + Map toJson() => + _$SubscriptionItemPriceChangeDetailsToJson(this); } @JsonSerializable() class Money { - String currencyCode; int nanos; int units; - Money( - this.currencyCode, - this.nanos, - this.units - ); - - + Money(this.currencyCode, this.nanos, this.units); factory Money.fromJson(Map json) => _$MoneyFromJson(json); Map toJson() => _$MoneyToJson(this); - } + @JsonSerializable() class DeferredItemReplacement { - String productId; - DeferredItemReplacement( - this.productId - ); - - + DeferredItemReplacement(this.productId); - factory DeferredItemReplacement.fromJson(Map json) => _$DeferredItemReplacementFromJson(json); + factory DeferredItemReplacement.fromJson(Map json) => + _$DeferredItemReplacementFromJson(json); Map toJson() => _$DeferredItemReplacementToJson(this); - } @JsonSerializable() class PrepaidPlan { - String? allowExtendAfterTime; - PrepaidPlan( - this.allowExtendAfterTime - ); - - + PrepaidPlan(this.allowExtendAfterTime); - factory PrepaidPlan.fromJson(Map json) => _$PrepaidPlanFromJson(json); + factory PrepaidPlan.fromJson(Map json) => + _$PrepaidPlanFromJson(json); Map toJson() => _$PrepaidPlanToJson(this); - } @JsonSerializable() class PausedStateContext { - String autoResumeTime; - PausedStateContext( - this.autoResumeTime - ); - - + PausedStateContext(this.autoResumeTime); - factory PausedStateContext.fromJson(Map json) => _$PausedStateContextFromJson(json); + factory PausedStateContext.fromJson(Map json) => + _$PausedStateContextFromJson(json); Map toJson() => _$PausedStateContextToJson(this); - } + @JsonSerializable() class SubscribeWithGoogleInfo { - String emailAddress; String familyName; String givenName; String profileId; String profileName; - SubscribeWithGoogleInfo( - this.emailAddress, - this.familyName, - this.givenName, - this.profileId, - this.profileName - ); - + SubscribeWithGoogleInfo(this.emailAddress, this.familyName, this.givenName, + this.profileId, this.profileName); - - factory SubscribeWithGoogleInfo.fromJson(Map json) => _$SubscribeWithGoogleInfoFromJson(json); + factory SubscribeWithGoogleInfo.fromJson(Map json) => + _$SubscribeWithGoogleInfoFromJson(json); Map toJson() => _$SubscribeWithGoogleInfoToJson(this); - } @JsonSerializable() -class TestPurchase{ +class TestPurchase { TestPurchase(); - factory TestPurchase.fromJson(Map json) => _$TestPurchaseFromJson(json); + factory TestPurchase.fromJson(Map json) => + _$TestPurchaseFromJson(json); Map toJson() => _$TestPurchaseToJson(this); -} \ No newline at end of file +} diff --git a/lib/src/purchase_connector/models/subscription_validation_result.dart b/lib/src/purchase_connector/models/subscription_validation_result.dart index b929417..4790b03 100644 --- a/lib/src/purchase_connector/models/subscription_validation_result.dart +++ b/lib/src/purchase_connector/models/subscription_validation_result.dart @@ -2,31 +2,27 @@ part of appsflyer_sdk; @JsonSerializable() class SubscriptionValidationResult { - bool success; SubscriptionPurchase? subscriptionPurchase; ValidationFailureData? failureData; SubscriptionValidationResult( - this.success, - this.subscriptionPurchase, - this.failureData - ); - + this.success, this.subscriptionPurchase, this.failureData); - - factory SubscriptionValidationResult.fromJson(Map json) => _$SubscriptionValidationResultFromJson(json); + factory SubscriptionValidationResult.fromJson(Map json) => + _$SubscriptionValidationResultFromJson(json); Map toJson() => _$SubscriptionValidationResultToJson(this); - } @JsonSerializable() -class SubscriptionValidationResultMap{ +class SubscriptionValidationResultMap { Map result; SubscriptionValidationResultMap(this.result); - factory SubscriptionValidationResultMap.fromJson(Map json) => _$SubscriptionValidationResultMapFromJson(json); + factory SubscriptionValidationResultMap.fromJson(Map json) => + _$SubscriptionValidationResultMapFromJson(json); - Map toJson() => _$SubscriptionValidationResultMapToJson(this); + Map toJson() => + _$SubscriptionValidationResultMapToJson(this); } diff --git a/lib/src/purchase_connector/models/validation_failure_data.dart b/lib/src/purchase_connector/models/validation_failure_data.dart index 57b3d13..5da921f 100644 --- a/lib/src/purchase_connector/models/validation_failure_data.dart +++ b/lib/src/purchase_connector/models/validation_failure_data.dart @@ -2,19 +2,13 @@ part of appsflyer_sdk; @JsonSerializable() class ValidationFailureData { - int status; String description; - ValidationFailureData( - this.status, - this.description - ); - + ValidationFailureData(this.status, this.description); - - factory ValidationFailureData.fromJson(Map json) => _$ValidationFailureDataFromJson(json); + factory ValidationFailureData.fromJson(Map json) => + _$ValidationFailureDataFromJson(json); Map toJson() => _$ValidationFailureDataToJson(this); - -} \ No newline at end of file +} diff --git a/lib/src/udl/deep_link_result.dart b/lib/src/udl/deep_link_result.dart index 388dada..9cfcefd 100644 --- a/lib/src/udl/deep_link_result.dart +++ b/lib/src/udl/deep_link_result.dart @@ -1,6 +1,5 @@ part of appsflyer_sdk; - class DeepLinkResult { final Error? _error; final DeepLink? _deepLink; @@ -19,8 +18,7 @@ class DeepLinkResult { _status = json['status'], _deepLink = json['deepLink']; - Map toJson() => - { + Map toJson() => { 'status': _status.toShortString(), 'error': _error?.toShortString(), 'deepLink': _deepLink?.clickEvent, @@ -30,23 +28,11 @@ class DeepLinkResult { String toString() { return "DeepLinkResult:${jsonEncode(toJson())}"; } - - } -enum Error { - TIMEOUT, - NETWORK, - HTTP_STATUS_CODE, - UNEXPECTED, - DEVELOPER_ERROR -} -enum Status { - FOUND, - NOT_FOUND, - ERROR, - PARSE_ERROR -} +enum Error { TIMEOUT, NETWORK, HTTP_STATUS_CODE, UNEXPECTED, DEVELOPER_ERROR } + +enum Status { FOUND, NOT_FOUND, ERROR, PARSE_ERROR } extension ParseStatusToString on Status { String toShortString() { @@ -62,22 +48,22 @@ extension ParseErrorToString on Error { extension ParseEnumFromString on String { Status? statusFromString() { - return Status.values.firstWhere( - (s) => _describeEnum(s) == this, orElse: null); + return Status.values + .firstWhere((s) => _describeEnum(s) == this, orElse: null); } Error? errorFromString() { - return Error.values.firstWhere((e) => _describeEnum(e) == this, - orElse: null); + return Error.values + .firstWhere((e) => _describeEnum(e) == this, orElse: null); } String _describeEnum(Object enumEntry) { final String description = enumEntry.toString(); final int indexOfDot = description.indexOf('.'); assert( - indexOfDot != -1 && indexOfDot < description.length - 1, - 'The provided object "$enumEntry" is not an enum.', + indexOfDot != -1 && indexOfDot < description.length - 1, + 'The provided object "$enumEntry" is not an enum.', ); return description.substring(indexOfDot + 1); } -} \ No newline at end of file +} diff --git a/lib/src/udl/deeplink.dart b/lib/src/udl/deeplink.dart index 88fdaff..40efe06 100644 --- a/lib/src/udl/deeplink.dart +++ b/lib/src/udl/deeplink.dart @@ -1,55 +1,43 @@ part of appsflyer_sdk; - -class DeepLink{ - - final Map _clickEvent; +class DeepLink { + final Map _clickEvent; DeepLink(this._clickEvent); - Map get clickEvent => _clickEvent; + Map get clickEvent => _clickEvent; String? getStringValue(String key) { return _clickEvent[key] as String?; } - String? get deepLinkValue => _clickEvent["deep_link_value"] as String?; - - - String? get matchType => _clickEvent["match_type"] as String?; - - - String? get clickHttpReferrer => _clickEvent["click_http_referrer"] as String?; - + String? get deepLinkValue => _clickEvent["deep_link_value"] as String?; - String? get mediaSource => _clickEvent["media_source"] as String?; + String? get matchType => _clickEvent["match_type"] as String?; + String? get clickHttpReferrer => + _clickEvent["click_http_referrer"] as String?; - String? get campaign => _clickEvent["campaign"] as String?; + String? get mediaSource => _clickEvent["media_source"] as String?; + String? get campaign => _clickEvent["campaign"] as String?; - String? get campaignId => _clickEvent["campaign_id"] as String?; - + String? get campaignId => _clickEvent["campaign_id"] as String?; String? get afSub1 => _clickEvent["af_sub1"] as String?; - - String? get afSub2 => _clickEvent["af_sub2"] as String?; - + String? get afSub2 => _clickEvent["af_sub2"] as String?; String? get afSub3 => _clickEvent["af_sub3"] as String?; + String? get afSub4 => _clickEvent["af_sub4"] as String?; - String? get afSub4 => _clickEvent["af_sub4"] as String?; - + String? get afSub5 => _clickEvent["af_sub5"] as String?; - String? get afSub5 => _clickEvent["af_sub5"] as String?; - - - bool? get isDeferred => _clickEvent["is_deferred"] as bool?; + bool? get isDeferred => _clickEvent["is_deferred"] as bool?; @override - String toString() { - return 'DeepLink: ${jsonEncode(_clickEvent)}'; - } -} \ No newline at end of file + String toString() { + return 'DeepLink: ${jsonEncode(_clickEvent)}'; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index bee08fa..f4c5aec 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: appsflyer_sdk description: A Flutter plugin for AppsFlyer SDK. Supports iOS and Android. -version: 6.17.5 +version: 6.17.7 homepage: https://github.com/AppsFlyerSDK/flutter_appsflyer_sdk @@ -18,7 +18,7 @@ dev_dependencies: sdk: flutter test: ^1.16.5 mockito: ^5.4.4 - effective_dart: ^1.3.0 + flutter_lints: ^5.0.0 build_runner: ^2.3.0 json_serializable: ^6.5.4 diff --git a/test/appsflyer_sdk_test.dart b/test/appsflyer_sdk_test.dart index bcd396e..80f3262 100644 --- a/test/appsflyer_sdk_test.dart +++ b/test/appsflyer_sdk_test.dart @@ -305,16 +305,6 @@ void main() { expect(capturedArguments['mediationNetwork'], 'applovin_max'); }); - test('check setConsentData call', () async { - final consentData = AppsFlyerConsent.forGDPRUser( - hasConsentForDataUsage: true, - hasConsentForAdsPersonalization: true, - ); - instance.setConsentData(consentData); - - expect(selectedMethod, 'setConsentData'); - }); - test('check enableTCFDataCollection call', () async { instance.enableTCFDataCollection(true); @@ -342,12 +332,6 @@ void main() { expect(capturedArguments, contains('https://example.com')); }); - test('check setPushNotification call', () async { - instance.setPushNotification(true); - - expect(selectedMethod, 'setPushNotification'); - }); - test('check sendPushNotificationData call', () async { instance.sendPushNotificationData({'key': 'value'});