diff --git a/.github/actions/setup-android/action.yml b/.github/actions/setup-android/action.yml new file mode 100644 index 000000000..673cfd803 --- /dev/null +++ b/.github/actions/setup-android/action.yml @@ -0,0 +1,20 @@ +name: Setup Android +description: Setup CI with Android development tools to compile and test Android source code. + +runs: + using: "composite" + steps: + - name: Install Java + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + + - name: Install Android SDK + uses: android-actions/setup-android@v3 + + - name: Verify gradle scripts are valid gradle scripts + uses: gradle/wrapper-validation-action@v1 + + - name: Setup Gradle and cache dependencies between builds + uses: gradle/gradle-build-action@v2 \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..743055c27 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ +# Dependabot helps update dependencies to keep them up-to-date. +# Configuration docs: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 1 + reviewers: + - "customerio/mobile" + commit-message: + prefix: "chore" + include: "scope" + groups: + github-action-dependencies: + patterns: + - "*" diff --git a/.github/workflows/binary-validator.yml b/.github/workflows/binary-validator.yml index abdc78b2c..35d2cfd42 100644 --- a/.github/workflows/binary-validator.yml +++ b/.github/workflows/binary-validator.yml @@ -7,12 +7,7 @@ jobs: name: API check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Set up JDK - uses: actions/setup-java@v2 - with: - distribution: 'adopt' - java-version: '11' + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup-android - name: API check - run: ./gradlew apiCheck - + run: ./gradlew apiCheck \ No newline at end of file diff --git a/.github/workflows/build-sample-apps.yml b/.github/workflows/build-sample-apps.yml index 1480df477..e2748bf2a 100644 --- a/.github/workflows/build-sample-apps.yml +++ b/.github/workflows/build-sample-apps.yml @@ -68,21 +68,13 @@ jobs: runs-on: ubuntu-latest name: Building app...${{ matrix.sample-app }} steps: - - uses: actions/checkout@v3 - - - name: Set up JDK - uses: actions/setup-java@v2 - with: - distribution: 'temurin' - java-version: '11' + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup-android # CLI to replace strings in files. The CLI recommends using `cargo install` which is slow. This Action is fast because it downloads pre-built binaries. # If using sd on macos, "brew install" works great. for Linux, this is the recommended way. - name: Install sd CLI to use later in the workflow uses: kenji-miyake/setup-sd@v1 - - - name: Setup Android SDK - uses: android-actions/setup-android@v2 - name: Install tools from Gemfile (ruby language) used for building our apps with uses: ruby/setup-ruby@v1 @@ -95,12 +87,6 @@ jobs: touch "samples/local.properties" echo "siteId=${{ secrets[matrix.cio-siteid-secret-key] }}" >> "samples/local.properties" echo "apiKey=${{ secrets[matrix.cio-apikey-secret-key] }}" >> "samples/local.properties" - - - name: Verify gradle scripts are not modified - uses: gradle/wrapper-validation-action@v1 - - - name: Setup Gradle and cache dependencies between builds - uses: gradle/gradle-build-action@v2 - name: Dump GitHub Action metadata because Fastlane uses it. Viewing it here helps debug JSON parsing code in Firebase. run: cat $GITHUB_EVENT_PATH diff --git a/.github/workflows/deploy-sdk.yml b/.github/workflows/deploy-sdk.yml index 12d49258f..7410d8353 100644 --- a/.github/workflows/deploy-sdk.yml +++ b/.github/workflows/deploy-sdk.yml @@ -19,7 +19,7 @@ jobs: new_release_published: ${{ steps.semantic-release.outputs.new_release_published }} new_release_version: ${{ steps.semantic-release.outputs.new_release_version }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # If using sd on macos, "brew install" works great. for Linux, this is the recommended way. - name: Install sd CLI to use later in the workflow uses: kenji-miyake/setup-sd@v1 @@ -33,24 +33,23 @@ jobs: # 2. Updates metadata files. Such as updating the version number in package.json and adding entries to CHANGELOG.md file. # 3. Create git tag and push it to github. - name: Deploy git tag via semantic-release - uses: cycjimmy/semantic-release-action@v3 + uses: cycjimmy/semantic-release-action@v4 id: semantic-release with: dry_run: false # version numbers below can be in many forms: M, M.m, M.m.p - semantic_version: 18 extra_plugins: | - conventional-changelog-conventionalcommits@4 - @semantic-release/changelog@6 - @semantic-release/git@10 - @semantic-release/github@8 - @semantic-release/exec@6 + conventional-changelog-conventionalcommits + @semantic-release/changelog + @semantic-release/git + @semantic-release/github + @semantic-release/exec env: # Needs to push git commits to repo. Needs write access. GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Notify team of git tag being created - uses: slackapi/slack-github-action@v1.18.0 + uses: slackapi/slack-github-action@v1.24.0 if: steps.semantic-release.outputs.new_release_published == 'true' # only run if a git tag was made. with: # Use block kit for format of the JSON payloads: https://app.slack.com/block-kit-builder @@ -87,7 +86,7 @@ jobs: SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK - name: Notify team of failure - uses: slackapi/slack-github-action@v1.18.0 + uses: slackapi/slack-github-action@v1.24.0 if: ${{ failure() }} # only run this if any previous step failed with: # Use block kit for format of the JSON payloads: https://app.slack.com/block-kit-builder @@ -129,12 +128,8 @@ jobs: if: needs.deploy-git-tag.outputs.new_release_published == 'true' # only run if a git tag was made. runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Set up JDK - uses: actions/setup-java@v2 - with: - distribution: adopt - java-version: 11 + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup-android - name: Push to Sonatype servers run: MODULE_VERSION=${{ needs.deploy-git-tag.outputs.new_release_version }} ./scripts/deploy-code.sh env: @@ -146,7 +141,7 @@ jobs: SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }} - name: Notify team of successful deployment - uses: slackapi/slack-github-action@v1.18.0 + uses: slackapi/slack-github-action@v1.24.0 if: ${{ success() }} with: # Use block kit for format of the JSON payloads: https://app.slack.com/block-kit-builder @@ -183,7 +178,7 @@ jobs: SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK - name: Notify team of failure - uses: slackapi/slack-github-action@v1.18.0 + uses: slackapi/slack-github-action@v1.24.0 if: ${{ failure() }} # only run this if any previous step failed with: # Use block kit for format of the JSON payloads: https://app.slack.com/block-kit-builder diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8727c7879..ab998eb7d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,22 +10,16 @@ jobs: module: [sdk, messagingpush, messaginginapp] # android modules name: Android Lint (${{ matrix.module }}) steps: - - uses: actions/checkout@v3 - - name: Set up JDK - uses: actions/setup-java@v2 - with: - distribution: 'adopt' - java-version: '11' # Robolectric requires v9, but we choose LTS: https://adoptopenjdk.net/ - - name: Setup Android SDK - uses: android-actions/setup-android@v2 + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup-android - name: Run lint (${{ matrix.module }}) run: ./gradlew :${{ matrix.module }}:lintDebug - name: Parse lint results (${{ matrix.module }}) - uses: yutailang0119/action-android-lint@v1.0.2 + uses: yutailang0119/action-android-lint@v3.1.0 with: - xml_path: ${{ matrix.module }}/build/reports/lint-results-debug.xml + report-path: ${{ matrix.module }}/build/reports/lint-results-debug.xml if: ${{ always() }} # if running tests fails, we still want to parse the test results # Task to verify ktlint already ran for all commits. This verifies you have your git hooks installed. @@ -33,12 +27,8 @@ jobs: runs-on: ubuntu-latest name: Kotlin Lint steps: - - uses: actions/checkout@v3 - - name: Set up JDK - uses: actions/setup-java@v2 - with: - distribution: 'adopt' - java-version: '11' # Robolectric requires v9, but we choose LTS: https://adoptopenjdk.net/ + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup-android - name: Install and run ktlint run: make lint-install && make lint-no-format diff --git a/.github/workflows/manual-deployment.yml b/.github/workflows/manual-deployment.yml new file mode 100644 index 000000000..0472f4dd0 --- /dev/null +++ b/.github/workflows/manual-deployment.yml @@ -0,0 +1,103 @@ +name: Manual Deploy to Maven Central +# Trigger this manual deployment in scenarios where: +# A git tag exists, but the initial deployment failed and fix has been pushed to main. +# This avoids the need to recreate the tag for redeployment. +on: + workflow_dispatch: # allows for manual triggering + +jobs: + deploy-sonatype: + name: Deploy SDK to Maven Central + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 # fetches all history for all tags and branches + + - name: Get latest tag + id: get-latest-tag + run: | + echo '::group::Get latest tag' + TAG=$(git describe --tags --abbrev=0) + echo "TAG=$TAG" >> $GITHUB_ENV + echo '::endgroup::' + + - uses: ./.github/actions/setup-android + + - name: Push to Sonatype servers + run: MODULE_VERSION=${{ env.TAG }} ./scripts/deploy-code.sh + env: + OSSRH_USERNAME: ${{ secrets.GRADLE_PUBLISH_USERNAME }} + OSSRH_PASSWORD: ${{ secrets.GRADLE_PUBLISH_PASSWORD }} + SIGNING_KEY_ID: ${{ secrets.GRADLE_SIGNING_KEYID }} + SIGNING_PASSWORD: ${{ secrets.GRADLE_SIGNING_PASSPHRASE }} + SIGNING_KEY: ${{ secrets.GRADLE_SIGNING_PRIVATE_KEY }} + SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }} + + - name: Notify team of successful deployment + uses: slackapi/slack-github-action@v1.24.0 + if: ${{ success() }} + with: + payload: | + { + "text": "Android SDK deployed to Maven Central", + "username": "Android deployment bot", + "icon_url": "https://media.pocketgamer.com/artwork/na-qulrguj/android.jpg", + "channel": "#mobile-deployments", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Android* SDK deployed to Maven Central! (manual deployment)" + } + }, + { + "type": "divider" + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Version:* ${{ env.TAG }}\n\nAndroid SDK deployment progress:\n~1. Git tag already created~\n~2. Manual deploy to maven central~\n\n" + } + } + ] + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_NOTIFY_RELEASES_WEBHOOK_URL }} + SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK + - name: Notify team of failure + uses: slackapi/slack-github-action@v1.24.0 + if: ${{ failure() }} + with: + payload: | + { + "text": "Android SDK deployment failure", + "username": "Android deployment bot", + "icon_url": "https://media.pocketgamer.com/artwork/na-qulrguj/android.jpg", + "channel": "#mobile-deployments", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Android* SDK deployment :warning: failure :warning:" + } + }, + { + "type": "divider" + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Android SDK failed deployment during step *deploy to maven central*. View to learn why and fix the issue. ." + } + } + ] + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_NOTIFY_RELEASES_WEBHOOK_URL }} + SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK \ No newline at end of file diff --git a/.github/workflows/pr-helper.yml b/.github/workflows/pr-helper.yml index 5ee8dd26b..59f608979 100644 --- a/.github/workflows/pr-helper.yml +++ b/.github/workflows/pr-helper.yml @@ -4,15 +4,10 @@ on: pull_request: types: [opened, reopened, edited, synchronize, labeled] -permissions: - pull-requests: write # Write access needed to create a comment. - jobs: - pr-help: - name: Semantic PR helper + lint-pr-title: runs-on: ubuntu-latest - steps: - - name: Semantic PR helper - uses: levibostian/action-semantic-pr@v3 - with: - readToken: ${{ secrets.GITHUB_TOKEN }} + permissions: + pull-requests: write # to comment on PRs + steps: + - uses: levibostian/action-conventional-pr-linter@v4 diff --git a/.github/workflows/snapshot-release.yml b/.github/workflows/snapshot-release.yml index 92afa0778..dd8bfc905 100644 --- a/.github/workflows/snapshot-release.yml +++ b/.github/workflows/snapshot-release.yml @@ -11,12 +11,8 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v3 - - name: Set up JDK 11 - uses: actions/setup-java@v2 - with: - distribution: adopt - java-version: 11 + uses: actions/checkout@v4 + - uses: ./.github/actions/setup-android # Using branch name for name of snapshot. Makes it easy to remember and can easily trigger new builds of Remote Habits. - name: Set snapshot version @@ -45,7 +41,7 @@ jobs: body-includes: Build available to test - name: Inform pull request on build of SDK available to test - uses: peter-evans/create-or-update-comment@v2 + uses: peter-evans/create-or-update-comment@v3 with: comment-id: ${{ steps.find-previous-comment.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a871a118d..53281d665 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,25 +10,19 @@ jobs: module: [sdk, messagingpush, messaginginapp, base] name: Unit tests (${{ matrix.module }}) steps: - - uses: actions/checkout@v3 - - name: Set up JDK - uses: actions/setup-java@v2 - with: - distribution: 'adopt' - java-version: '11' # Robolectric requires v9, but we choose LTS: https://adoptopenjdk.net/ - - name: Setup Android SDK - uses: android-actions/setup-android@v2 + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup-android - name: Run unit tests (${{ matrix.module }}) run: ./gradlew :${{ matrix.module }}:runJacocoTestReport - name: Upload code coverage report - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_UPLOAD_TOKEN }} # not required for public repos, but sometimes uploads fail without it so include it anyway fail_ci_if_error: true # fail if upload fails so we can catch it and fix it right away. verbose: true files: ./${{ matrix.module }}/build/reports/jacoco/test/jacocoTestReport.xml,./${{ matrix.module }}/build/reports/jacoco/runJacocoTestReport/runJacocoTestReport.xml - name: Publish test results (${{ matrix.module }}) - uses: mikepenz/action-junit-report@v2 + uses: mikepenz/action-junit-report@v4 with: report_paths: '**/build/test-results/test*/TEST-*.xml' fail_on_failure: true @@ -43,16 +37,8 @@ jobs: matrix: sample: [kotlin_compose, java_layout] steps: - - uses: actions/checkout@v3 - - name: Set up JDK 17 - uses: actions/setup-java@v2 - with: - distribution: 'adopt' - java-version: '17' - - name: Setup Android SDK - uses: android-actions/setup-android@v2 - - name: Gradle cache - uses: gradle/gradle-build-action@v2 + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup-android - name: Start emulator uses: reactivecircus/android-emulator-runner@v2 with: @@ -70,7 +56,7 @@ jobs: # Run the instrumentation tests on the emulator. script: ./gradlew :samples:${{ matrix.sample }}:connectedDebugAndroidTest - name: Publish test results - uses: mikepenz/action-junit-report@v2 + uses: mikepenz/action-junit-report@v4 if: always() with: report_paths: 'samples/${{ matrix.sample }}/build/outputs/androidTest-results/connected/TEST-*.xml' diff --git a/CHANGELOG.md b/CHANGELOG.md index f0c65af1f..0606f537e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +### [3.6.6](https://github.com/customerio/customerio-android/compare/3.6.5...3.6.6) (2023-09-15) + + +### Bug Fixes + +* stack-overflow caused by BQ recursion ([#251](https://github.com/customerio/customerio-android/issues/251)) ([365a5b6](https://github.com/customerio/customerio-android/commit/365a5b690ed37667dfb6782629ad56743d97904d)) + ### [3.6.5](https://github.com/customerio/customerio-android/compare/3.6.4...3.6.5) (2023-08-23) diff --git a/app/.gitignore b/app/.gitignore deleted file mode 100644 index 42afabfd2..000000000 --- a/app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/app/README.md b/app/README.md deleted file mode 100644 index 18b51626b..000000000 --- a/app/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# SDK Android app - -This directory contains an Android application that uses the Customer.io Android SDK. - -# Goals of this app - -* [X] Use the SDK to make sure the SDK functions are public and works as they were designed. -* [X] Makes sure that Kotlin and Java support is fully supported in the SDK. - -The app does *not* intend to... - -* [ ] Be a real-world sample of an app that uses the Customer.io Android SDK. See real-world example code on how we suggest you use the SDK. -* [ ] Contain Android tests to make sure the SDK is friendly when writing tests. - -It's recommended you checkout our [Remote Habits](https://github.com/customerio/RemoteHabits-Android) app for all of these missing features. \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 03695520f..000000000 --- a/app/build.gradle +++ /dev/null @@ -1,58 +0,0 @@ -import io.customer.android.Dependencies - -plugins { - id 'com.android.application' - id 'kotlin-android' - id 'kotlin-kapt' -} - -apply from: "${rootDir}/scripts/android-config.gradle" - -android { - defaultConfig { - applicationId "io.customer.example" - versionCode 1 - versionName "1.0" - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = '1.8' - } - - testOptions { - animationsDisabled = true - - unitTests { - // From: http://robolectric.org/getting-started/ - includeAndroidResources = true - returnDefaultValues = true - } - } -} - -dependencies { - implementation project(":sdk") - implementation Dependencies.androidxAppCompat - implementation Dependencies.androidxCoreKtx - implementation Dependencies.materialComponents - - implementation Dependencies.coroutinesCore - implementation Dependencies.coroutinesAndroid - - androidTestImplementation Dependencies.junit4 - testImplementation Dependencies.junit4 - testImplementation Dependencies.androidxTestJunit - testImplementation Dependencies.robolectric -} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro deleted file mode 100644 index 481bb4348..000000000 --- a/app/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml deleted file mode 100644 index 1afdd5cc2..000000000 --- a/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/java/io/customer/example/MainActivity.kt b/app/src/main/java/io/customer/example/MainActivity.kt deleted file mode 100644 index 5366a3ac1..000000000 --- a/app/src/main/java/io/customer/example/MainActivity.kt +++ /dev/null @@ -1,83 +0,0 @@ -package io.customer.example - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import io.customer.sdk.CustomerIO -import java.util.* - -class MainActivity : AppCompatActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - - makeIdentifyRequest() - - // log events -// makeEventsRequests() - - // add custom attributes - makeAddCustomDeviceAttributesRequest() - - // register device - makeRegisterDeviceRequest() - } - - private fun makeAddCustomDeviceAttributesRequest() { - CustomerIO.instance().deviceAttributes = mapOf("bingo" to "heyaa") - } - - private fun makeRegisterDeviceRequest() { - CustomerIO.instance().registerDeviceToken("token") - } - - data class Fol(val a: String, val c: Int) - - private fun makeEventsRequests() { - CustomerIO.instance().track( - name = "string event", - attributes = mapOf( - "value" to "string test", - "target" to 1 - ) - ) - CustomerIO.instance().track( - name = "int event", - attributes = mapOf("value" to 1388377266772) - ) - CustomerIO.instance().track( - name = "long event", - attributes = mapOf("value" to 1653L) - ) - CustomerIO.instance().track( - name = "double event", - attributes = mapOf("value" to 133333.882) - ) - CustomerIO.instance().track( - name = "array event", - attributes = mapOf("value" to listOf("1", "2")) - ) - CustomerIO.instance().track( - name = "date event", - attributes = mapOf("value" to Date()) - ) - CustomerIO.instance().track( - name = "timestamp event", - attributes = mapOf("value" to Date().time) - ) - CustomerIO.instance().track( - name = "custom class event", - attributes = mapOf("value" to Fol(a = "aa", c = 1)) - ) - CustomerIO.instance().screen( - name = "MainActivity" - ) - } - - private fun makeIdentifyRequest() { - CustomerIO.instance().identify( - identifier = "support-ticket-test", - mapOf("created_at" to 1642659790) - ) - } -} diff --git a/app/src/main/java/io/customer/example/MainApplication.kt b/app/src/main/java/io/customer/example/MainApplication.kt deleted file mode 100644 index 7892ab2c9..000000000 --- a/app/src/main/java/io/customer/example/MainApplication.kt +++ /dev/null @@ -1,16 +0,0 @@ -package io.customer.example - -import android.app.Application -import io.customer.sdk.CustomerIO - -class MainApplication : Application() { - - override fun onCreate() { - super.onCreate() - CustomerIO.Builder( - siteId = "YOUR-SITE-ID", - apiKey = "YOUR-API-KEY", - appContext = this - ).build() - } -} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index 2b068d114..000000000 --- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 07d5da9cb..000000000 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index a1a0adb4e..000000000 --- a/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index eca70cfe5..000000000 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index eca70cfe5..000000000 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a571e6009..000000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 61da551c5..000000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c41dd2853..000000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index db5080a75..000000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 6dba46dab..000000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index da31a871c..000000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 15ac68172..000000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index b216f2d31..000000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index f25a41974..000000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index e96783ccc..000000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml deleted file mode 100644 index a3af6d433..000000000 --- a/app/src/main/res/values-night/themes.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml deleted file mode 100644 index f8c6127d3..000000000 --- a/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - #FFBB86FC - #FF6200EE - #FF3700B3 - #FF03DAC5 - #FF018786 - #FF000000 - #FFFFFFFF - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml deleted file mode 100644 index 426e91bf5..000000000 --- a/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Customer.io SDK Example - \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml deleted file mode 100644 index 6cdc8017f..000000000 --- a/app/src/main/res/values/themes.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/test/java/io/customer/example/ExampleRobolectricTest.kt b/app/src/test/java/io/customer/example/ExampleRobolectricTest.kt deleted file mode 100644 index 95b5a01b5..000000000 --- a/app/src/test/java/io/customer/example/ExampleRobolectricTest.kt +++ /dev/null @@ -1,15 +0,0 @@ -package io.customer.example - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.junit.Assert -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class ExampleRobolectricTest { - - @Test - fun canAccessR() { - Assert.assertEquals(1, 1) - } -} diff --git a/build.gradle b/build.gradle index e55e5036a..7340698b1 100644 --- a/build.gradle +++ b/build.gradle @@ -51,7 +51,6 @@ apiValidation { ignoredProjects += [ 'common-test', - 'app', 'base', 'kotlin_compose', 'java_layout', diff --git a/docs/dev-notes/KOTLIN-BINARY-VALIDATOR.md b/docs/dev-notes/KOTLIN-BINARY-VALIDATOR.md index 1aa3ec952..9d10c3db9 100644 --- a/docs/dev-notes/KOTLIN-BINARY-VALIDATOR.md +++ b/docs/dev-notes/KOTLIN-BINARY-VALIDATOR.md @@ -38,8 +38,7 @@ apiValidation { ] ignoredProjects += [ - 'common-test', - 'app', + 'common-test' ] ignoredClasses.add("io.customer.messagingpush.BuildConfig") diff --git a/scripts/deploy-code.sh b/scripts/deploy-code.sh index e1b9812af..85bb2dcf8 100755 --- a/scripts/deploy-code.sh +++ b/scripts/deploy-code.sh @@ -19,7 +19,7 @@ if [[ "$MODULE_VERSION" == "" ]]; then exit 1 fi -./gradlew assembleRelease -x :app:assembleRelease +./gradlew assembleRelease ./gradlew androidSourcesJar javadocJar MODULE_VERSION="$MODULE_VERSION" ./gradlew publishReleasePublicationToSonatypeRepository --max-workers 1 closeAndReleaseSonatypeStagingRepository diff --git a/sdk/src/main/java/io/customer/sdk/Version.kt b/sdk/src/main/java/io/customer/sdk/Version.kt index f2e018fc7..af7d599cb 100644 --- a/sdk/src/main/java/io/customer/sdk/Version.kt +++ b/sdk/src/main/java/io/customer/sdk/Version.kt @@ -7,5 +7,5 @@ package io.customer.sdk internal object Version { - const val version: String = "3.6.5" + const val version: String = "3.6.6" } diff --git a/sdk/src/main/java/io/customer/sdk/queue/QueueRunRequest.kt b/sdk/src/main/java/io/customer/sdk/queue/QueueRunRequest.kt index 61fe99b66..862e8e48d 100644 --- a/sdk/src/main/java/io/customer/sdk/queue/QueueRunRequest.kt +++ b/sdk/src/main/java/io/customer/sdk/queue/QueueRunRequest.kt @@ -1,7 +1,6 @@ package io.customer.sdk.queue import io.customer.sdk.error.CustomerIOError -import io.customer.sdk.queue.type.QueueInventory import io.customer.sdk.queue.type.QueueTaskMetadata import io.customer.sdk.util.Logger @@ -19,86 +18,72 @@ internal class QueueRunRequestImpl internal constructor( override suspend fun run() { logger.debug("queue starting to run tasks...") val inventory = queueStorage.getInventory() + val tasksToRun = inventory.toMutableList() - runTasks(inventory, inventory.count()) - } - - private suspend fun runTasks( - inventory: QueueInventory, - totalNumberOfTasksToRun: Int, - lastFailedTask: QueueTaskMetadata? = null - ) { - val nextTaskToRunInventoryItem = queryRunner.getNextTask(inventory, lastFailedTask) - if (nextTaskToRunInventoryItem == null) { - logger.debug("queue done running tasks") - - queryRunner.reset() - - return - } - - val nextTaskStorageId = nextTaskToRunInventoryItem.taskPersistedId - val nextTaskToRun = queueStorage.get(nextTaskStorageId) - if (nextTaskToRun == null) { - logger.error("tried to get queue task with storage id: $nextTaskStorageId but storage couldn't find it.") - - // The task failed to execute because it couldn't be found. Gracefully handle the scenario by - // behaving the same way a failed HTTP request does. Run next task with an updated `lastFailedTask`. - return goToNextTask(inventory, totalNumberOfTasksToRun, nextTaskToRunInventoryItem) - } + var lastFailedTask: QueueTaskMetadata? = null - logger.debug("queue tasks left to run: ${inventory.count()} out of $totalNumberOfTasksToRun") - logger.debug("queue next task to run: $nextTaskStorageId, ${nextTaskToRun.type}, ${nextTaskToRun.data}, ${nextTaskToRun.runResults}") + while (tasksToRun.isNotEmpty()) { + // get the next task to run using the query criteria + val currentTaskMetadata = queryRunner.getNextTask(tasksToRun, lastFailedTask) + if (currentTaskMetadata == null) { + logger.debug("queue out of tasks to run...") + break + } - val result = runner.runTask(nextTaskToRun) - when { - result.isSuccess -> { - logger.debug("queue task $nextTaskStorageId ran successfully") + tasksToRun.remove(currentTaskMetadata) - logger.debug("queue deleting task $nextTaskStorageId") - queueStorage.delete(nextTaskStorageId) + val taskStorageId = currentTaskMetadata.taskPersistedId + val taskToRun = queueStorage.get(taskStorageId) - return goToNextTask(inventory, totalNumberOfTasksToRun, null) + if (taskToRun == null) { + logger.error("Tried to get queue task with storage id: $taskStorageId but storage couldn't find it.") + // The task failed to execute because it couldn't be found. Gracefully handle the scenario by + // behaving the same way a failed HTTP request does. Run next task with an updated `lastFailedTask`. + lastFailedTask = currentTaskMetadata + continue } - result.isFailure -> { - val error = result.exceptionOrNull() - logger.debug("queue task $nextTaskStorageId run failed $error") - return when (error as? CustomerIOError) { - is CustomerIOError.HttpRequestsPaused -> { - // When HTTP requests are paused, don't increment metadata about the tasks to create inaccurate data - logger.info("queue is quitting early because all HTTP requests are paused.") + logger.debug("queue tasks left to run: ${tasksToRun.size}") + logger.debug("queue next task to run: $taskStorageId, ${taskToRun.type}, ${taskToRun.data}, ${taskToRun.runResults}") - goToNextTask(emptyList(), totalNumberOfTasksToRun, null) - } - is CustomerIOError.BadRequest400 -> { - logger.error("Received HTTP 400 response while trying to run ${nextTaskToRun.type}. 400 responses never succeed and therefore, the SDK is deleting this SDK request and not retry. Error message from API: ${error.message}, request data sent: ${nextTaskToRun.data}") + val result = runner.runTask(taskToRun) - queueStorage.delete(nextTaskStorageId) + when { + result.isSuccess -> { + logger.debug("queue task $taskStorageId ran successfully") + logger.debug("queue deleting task $taskStorageId") + queueStorage.delete(taskStorageId) + } - goToNextTask(inventory, totalNumberOfTasksToRun, lastFailedTask = nextTaskToRunInventoryItem) - } - else -> { - val previousRunResults = nextTaskToRun.runResults - val newRunResults = - nextTaskToRun.runResults.copy(totalRuns = previousRunResults.totalRuns + 1) - logger.debug("queue task $nextTaskStorageId, updating run history from: $previousRunResults to: $newRunResults") - queueStorage.update(nextTaskStorageId, newRunResults) - - goToNextTask(inventory, totalNumberOfTasksToRun, lastFailedTask = nextTaskToRunInventoryItem) + result.isFailure -> { + val error = result.exceptionOrNull() + logger.debug("queue task $taskStorageId run failed $error") + + when (error as? CustomerIOError) { + is CustomerIOError.HttpRequestsPaused, is CustomerIOError.NoHttpRequestMade -> { + logger.info("queue is quitting early because ${error.message})") + tasksToRun.clear() // clear the list to stop processing the next tasks + break + } + + is CustomerIOError.BadRequest400 -> { + logger.error("Received HTTP 400 response while trying to run ${taskToRun.type}. 400 responses never succeed and therefore, the SDK is deleting this SDK request and not retry. Error message from API: ${error.message}, request data sent: ${taskToRun.data}") + queueStorage.delete(taskStorageId) + } + + else -> { + val previousRunResults = taskToRun.runResults + val newRunResults = + taskToRun.runResults.copy(totalRuns = previousRunResults.totalRuns + 1) + logger.debug("queue task $taskStorageId, updating run history from: $previousRunResults to: $newRunResults") + queueStorage.update(taskStorageId, newRunResults) + } } + lastFailedTask = currentTaskMetadata } } } - } - - private suspend fun goToNextTask( - inventory: QueueInventory, - totalNumberOfTasksToRun: Int, - lastFailedTask: QueueTaskMetadata? - ) { - val newInventory = inventory.toMutableList() - newInventory.removeFirstOrNull() - runTasks(newInventory, totalNumberOfTasksToRun, lastFailedTask) + logger.debug("queue done running tasks") + queryRunner.reset() } } diff --git a/sdk/src/sharedTest/java/io/customer/sdk/queue/QueueRunRequestTest.kt b/sdk/src/sharedTest/java/io/customer/sdk/queue/QueueRunRequestTest.kt index 21dcc2019..df619a4e4 100644 --- a/sdk/src/sharedTest/java/io/customer/sdk/queue/QueueRunRequestTest.kt +++ b/sdk/src/sharedTest/java/io/customer/sdk/queue/QueueRunRequestTest.kt @@ -78,7 +78,13 @@ class QueueRunRequestTest : BaseTest() { val givenTaskId = String.random val givenQueueTask = QueueTask.random.copy(storageId = givenTaskId) - whenever(runnerMock.runTask(eq(givenQueueTask))).thenReturn(Result.failure(CustomerIOError.BadRequest400(""))) + whenever(runnerMock.runTask(eq(givenQueueTask))).thenReturn( + Result.failure( + CustomerIOError.BadRequest400( + "" + ) + ) + ) whenever(storageMock.getInventory()).thenReturn( listOf( QueueTaskMetadata.random.copy( @@ -153,4 +159,78 @@ class QueueRunRequestTest : BaseTest() { verify(runnerMock).runTask(givenQueueTask) verify(runnerMock).runTask(givenQueueTask2) } + + @Test + fun test_run_givenMissingTaskInStorage_expectToContinueNextTask(): Unit = runBlocking { + val givenTaskId = String.random + val givenTaskId2 = String.random + val givenQueueTask1 = QueueTask.random.copy(storageId = givenTaskId) + val givenQueueTask2 = QueueTask.random.copy(storageId = givenTaskId2) + + whenever(storageMock.getInventory()).thenReturn( + listOf( + QueueTaskMetadata.random.copy(taskPersistedId = givenTaskId), + QueueTaskMetadata.random.copy(taskPersistedId = givenTaskId2) + ) + ) + whenever(storageMock.get(eq(givenTaskId))).thenReturn(null) + whenever(storageMock.get(eq(givenTaskId2))).thenReturn(givenQueueTask2) + whenever(runnerMock.runTask(givenQueueTask2)).thenReturn(Result.success(Unit)) + whenever(storageMock.delete(any())).thenReturn( + QueueModifyResult( + true, + QueueStatus(siteId, 0) + ) + ) + + runRequest.run() + + verify(runnerMock, never()).runTask(givenQueueTask1) + verify(runnerMock).runTask(givenQueueTask2) + verify(storageMock).delete(givenTaskId2) + verify(storageMock).delete(givenTaskId2) + } + + @Test + fun test_run_givenNoTasksAvailable_expectGracefulTermination(): Unit = runBlocking { + whenever(storageMock.getInventory()).thenReturn(emptyList()) + + runRequest.run() + + verify(runnerMock, never()).runTask(any()) + verify(storageMock, never()).delete(anyOrNull()) + } + + @Test + fun test_run_givenTasksEmptiedInLoop_expectGracefulTermination(): Unit = runBlocking { + val queueQueryRunnerMock: QueueQueryRunner = mock() + runRequest = QueueRunRequestImpl(runnerMock, storageMock, di.logger, queueQueryRunnerMock) + + val givenTaskId = String.random + val givenQueueTask = QueueTask.random.copy(storageId = givenTaskId) + + val tasksToRun = mutableListOf( + QueueTaskMetadata.random.copy(taskPersistedId = givenTaskId) + ) + + // Ensure that inventory returns a list initially so that the loop is entered + whenever(storageMock.getInventory()).thenReturn(tasksToRun) + + // When queryRunner.getNextTask is called, clear tasksToRun list + whenever(queueQueryRunnerMock.getNextTask(any(), any())).thenAnswer { + tasksToRun.clear() // Clear the tasksToRun when queryRunner.getNextTask is called + QueueTaskMetadata.random.copy(taskPersistedId = givenTaskId) + } + + // Return our task for the given ID. + whenever(storageMock.get(eq(givenTaskId))).thenReturn(givenQueueTask) + + runRequest.run() + + // We expect that the loop should break right after our modification to the tasksToRun list, hence: + // - The runner should never execute runTask + // - The storage should never delete any task + verify(runnerMock, never()).runTask(any()) + verify(storageMock, never()).delete(anyOrNull()) + } } diff --git a/settings.gradle b/settings.gradle index 29be358e4..f9265aa7c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,9 +1,8 @@ include ':sdk' -include ':app' rootProject.name = "Customer.io SDK" include ':base' include ':messagingpush' include ':common-test' include ':messaginginapp' include ':samples:kotlin_compose' -include ':samples:java_layout' +include ':samples:java_layout' \ No newline at end of file