├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── feature_request.md
│ └── pull_request_template.md
└── workflows
│ ├── build.yml
│ ├── create_jira.yml
│ ├── release.yml
│ └── snapshot.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── JAVA_COMPAT.md
├── LICENSE
├── README.md
├── RELEASING.md
├── android
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── gradle.properties
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── segment
│ │ └── analytics
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── segment
│ │ └── analytics
│ │ └── kotlin
│ │ └── android
│ │ ├── AndroidAnalytics.kt
│ │ ├── Storage.kt
│ │ ├── plugins
│ │ ├── AndroidContextPlugin.kt
│ │ └── AndroidLifecyclePlugin.kt
│ │ └── utilities
│ │ ├── AndroidKVS.kt
│ │ ├── DeepLinkUtils.kt
│ │ └── JSON.kt
│ └── test
│ ├── java
│ └── com
│ │ └── segment
│ │ └── analytics
│ │ └── kotlin
│ │ └── android
│ │ ├── AndroidAnalyticsKtTest.kt
│ │ ├── AndroidContextCollectorTests.kt
│ │ ├── AndroidLifecyclePluginTests.kt
│ │ ├── EventsFileTests.kt
│ │ ├── StorageTests.kt
│ │ ├── utilities
│ │ ├── AndroidKVSTest.kt
│ │ └── DeepLinkUtilsTests.kt
│ │ └── utils
│ │ ├── MockPreferences.kt
│ │ ├── Mocks.kt
│ │ └── Plugins.kt
│ └── resources
│ └── robolectric.properties
├── build.gradle
├── core
├── .gitignore
├── build.gradle
├── gradle.properties
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── segment
│ │ └── analytics
│ │ └── kotlin
│ │ └── core
│ │ ├── Analytics.kt
│ │ ├── Annotations.kt
│ │ ├── Configuration.kt
│ │ ├── Constants.kt
│ │ ├── Errors.kt
│ │ ├── Events.kt
│ │ ├── HTTPClient.kt
│ │ ├── Settings.kt
│ │ ├── State.kt
│ │ ├── Storage.kt
│ │ ├── Telemetry.kt
│ │ ├── compat
│ │ ├── Builders.kt
│ │ ├── ConfigurationBuilder.kt
│ │ ├── JavaAnalytics.kt
│ │ └── JsonSerializable.kt
│ │ ├── platform
│ │ ├── EventPipeline.kt
│ │ ├── Mediator.kt
│ │ ├── Plugin.kt
│ │ ├── Timeline.kt
│ │ ├── plugins
│ │ │ ├── ContextPlugin.kt
│ │ │ ├── DestinationMetadataPlugin.kt
│ │ │ ├── DeviceToken.kt
│ │ │ ├── SegmentDestination.kt
│ │ │ ├── StartupQueue.kt
│ │ │ ├── UserInfoPlugin.kt
│ │ │ └── logger
│ │ │ │ ├── ConsoleLogger.kt
│ │ │ │ ├── Logger.kt
│ │ │ │ └── SegmentLog.kt
│ │ └── policies
│ │ │ ├── CountBasedFlushPolicy.kt
│ │ │ ├── FlushPolicy.kt
│ │ │ ├── FrequencyFlushPolicy.kt
│ │ │ └── StartupFlushPolicy.kt
│ │ └── utilities
│ │ ├── AnySerializer.kt
│ │ ├── Base64Utils.kt
│ │ ├── DateTimeUtils.kt
│ │ ├── EventManipulationFunctions.kt
│ │ ├── EventStream.kt
│ │ ├── EventsFileManager.kt
│ │ ├── FileUtils.kt
│ │ ├── JSON.kt
│ │ ├── KVS.kt
│ │ ├── PropertiesFile.kt
│ │ └── StorageImpl.kt
│ └── test
│ └── kotlin
│ └── com
│ └── segment
│ └── analytics
│ └── kotlin
│ └── core
│ ├── AnalyticsTests.kt
│ ├── ErrorsTest.kt
│ ├── EventTests.kt
│ ├── HTTPClientTests.kt
│ ├── JSONTests.kt
│ ├── PluginTests.kt
│ ├── SerializingTests.kt
│ ├── SettingsTests.kt
│ ├── StateTest.kt
│ ├── StorageTest.kt
│ ├── TelemetryTest.kt
│ ├── compat
│ ├── BuildersTest.kt
│ ├── ConfigurationBuilderTest.kt
│ └── JavaAnalyticsTest.kt
│ ├── platform
│ ├── EventPipelineTest.kt
│ ├── MediatorTest.kt
│ ├── plugins
│ │ ├── DestinationMetadataPluginTests.kt
│ │ ├── DestinationPluginTests.kt
│ │ ├── DeviceTokenPluginTests.kt
│ │ ├── LoggerTest.kt
│ │ ├── SegmentDestinationTests.kt
│ │ ├── SegmentLogTest.kt
│ │ ├── StartupQueueTest.kt
│ │ └── UserInfoPluginTests.kt
│ └── policies
│ │ ├── CountBasedFlushPolicyTests.kt
│ │ ├── FrequencyFlushPolicyTests.kt
│ │ └── StartupFlushPolicyTests.kt
│ ├── utilities
│ ├── Base64UtilsTest.kt
│ ├── DateTimeUtilsTest.kt
│ ├── EventManipulationFunctionsTest.kt
│ ├── EventStreamTest.kt
│ ├── InMemoryStorageTest.kt
│ ├── KVSTest.kt
│ ├── PropertiesFileTest.kt
│ └── StorageImplTest.kt
│ └── utils
│ ├── Mocks.kt
│ └── Plugins.kt
├── gradle.properties
├── gradle
├── artifacts-android.gradle
├── artifacts-core.gradle
├── codecov.gradle
├── mvn-publish.gradle
├── promote.gradle
├── versioning.gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── samples
├── README.md
├── java-android-app
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── segment
│ │ │ └── analytics
│ │ │ └── javaandroidapp
│ │ │ └── ExampleInstrumentedTest.java
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── segment
│ │ │ │ └── analytics
│ │ │ │ └── javaandroidapp
│ │ │ │ ├── MainActivity.java
│ │ │ │ └── MainApplication.java
│ │ └── res
│ │ │ ├── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ │ ├── drawable
│ │ │ └── ic_launcher_background.xml
│ │ │ ├── layout
│ │ │ └── activity_main.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── values-night
│ │ │ └── themes.xml
│ │ │ └── values
│ │ │ ├── colors.xml
│ │ │ ├── strings.xml
│ │ │ └── themes.xml
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── segment
│ │ └── analytics
│ │ └── javaandroidapp
│ │ └── ExampleUnitTest.java
├── kotlin-android-app-destinations
│ ├── .gitignore
│ ├── README.md
│ ├── build.gradle
│ ├── google-services.json
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── segment
│ │ │ └── analytics
│ │ │ └── next
│ │ │ └── ExampleInstrumentedTest.kt
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── com
│ │ │ └── segment
│ │ │ └── analytics
│ │ │ └── kotlin
│ │ │ └── destinations
│ │ │ ├── EventFragment.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── MainApplication.kt
│ │ │ └── plugins
│ │ │ └── WebhookPlugin.kt
│ │ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── border.xml
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── activity_sample.xml
│ │ ├── fragment_group.xml
│ │ ├── fragment_identify.xml
│ │ ├── fragment_screen.xml
│ │ ├── fragment_track.xml
│ │ ├── property.xml
│ │ └── trait.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── values-night
│ │ └── themes.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ ├── styles.xml
│ │ └── themes.xml
├── kotlin-android-app
│ ├── .gitignore
│ ├── README.md
│ ├── build.gradle
│ ├── google-services.json
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── segment
│ │ │ └── analytics
│ │ │ └── next
│ │ │ └── ExampleInstrumentedTest.kt
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── segment
│ │ │ │ └── analytics
│ │ │ │ └── next
│ │ │ │ ├── ConsentActivity.kt
│ │ │ │ ├── EncryptedEventStream.kt
│ │ │ │ ├── EventFragment.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── MainApplication.kt
│ │ │ │ ├── MyFirebaseService.kt
│ │ │ │ ├── UnmeteredFlushPolicy.kt
│ │ │ │ └── plugins
│ │ │ │ ├── AndroidRecordScreenPlugin.kt
│ │ │ │ ├── ConsentTracking.kt
│ │ │ │ ├── InjectTraitsPlugin.kt
│ │ │ │ ├── PassiveLocationPlugin.kt
│ │ │ │ └── PushNotificationTracking.kt
│ │ └── res
│ │ │ ├── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ │ ├── drawable
│ │ │ ├── border.xml
│ │ │ └── ic_launcher_background.xml
│ │ │ ├── layout
│ │ │ ├── activity_sample.xml
│ │ │ ├── fragment_group.xml
│ │ │ ├── fragment_identify.xml
│ │ │ ├── fragment_screen.xml
│ │ │ ├── fragment_track.xml
│ │ │ ├── property.xml
│ │ │ └── trait.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── values-night
│ │ │ └── themes.xml
│ │ │ └── values
│ │ │ ├── colors.xml
│ │ │ ├── strings.xml
│ │ │ ├── styles.xml
│ │ │ └── themes.xml
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── segment
│ │ └── analytics
│ │ └── next
│ │ └── ExampleUnitTest.kt
└── kotlin-jvm-app
│ ├── .gitignore
│ ├── build.gradle
│ └── src
│ └── main
│ └── kotlin
│ └── main.kt
└── settings.gradle
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: triage
6 | assignees: wenxi-zeng
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Do '...'
16 | 2. '....'
17 | 3. See error
18 |
19 | **Expected behavior**
20 | A clear and concise description of what you expected to happen.
21 |
22 | **Screenshots**
23 | If applicable, add screenshots to help explain your problem.
24 |
25 | **Platform (please complete the following information):**
26 | - Library Version in use: [e.g. 0.0.5]
27 | - Platform being tested: [e.g. Android]
28 | - Integrations in use: [e.g. Firebase, Amplitude]
29 |
30 | **Additional context**
31 | Add any other context about the problem here.
32 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: triage
6 | assignees: prayansh
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Purpose
2 | _Describe the problem or feature in addition to a link to the issues._
3 |
4 | ## Approach
5 | _How does this change address the problem?_
6 |
7 | #### Open Questions and Pre-Merge TODOs
8 | - [ ] Use github checklists. When solved, check the box and explain the answer.
9 |
10 | ## Learning
11 | _Describe the research stage_
12 |
13 | _Links to blog posts, patterns, libraries or addons used to solve this problem_
--------------------------------------------------------------------------------
/.github/workflows/create_jira.yml:
--------------------------------------------------------------------------------
1 | name: Create Jira Ticket
2 |
3 | on:
4 | issues:
5 | types:
6 | - opened
7 |
8 | jobs:
9 | create_jira:
10 | name: Create Jira Ticket
11 | runs-on: ubuntu-22.04
12 | environment: IssueTracker
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@master
16 | - name: Login
17 | uses: atlassian/gajira-login@master
18 | env:
19 | JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
20 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
21 | JIRA_API_TOKEN: ${{ secrets.JIRA_TOKEN }}
22 | JIRA_EPIC_KEY: ${{ secrets.JIRA_EPIC_KEY }}
23 | JIRA_PROJECT: ${{ secrets.JIRA_PROJECT }}
24 |
25 | - name: Create
26 | id: create
27 | uses: atlassian/gajira-create@master
28 | with:
29 | project: ${{ secrets.JIRA_PROJECT }}
30 | issuetype: Bug
31 | summary: |
32 | [${{ github.event.repository.name }}] (${{ github.event.issue.number }}): ${{ github.event.issue.title }}
33 | description: |
34 | Github Link: ${{ github.event.issue.html_url }}
35 | ${{ github.event.issue.body }}
36 | fields: '{"parent": {"key": "${{ secrets.JIRA_EPIC_KEY }}"}}'
37 |
38 | - name: Log created issue
39 | run: echo "Issue ${{ steps.create.outputs.issue }} was created"
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*.*.*'
7 |
8 | jobs:
9 | release:
10 | runs-on: ubuntu-22.04
11 | environment: deployment
12 | steps:
13 | - uses: actions/checkout@v2
14 | - name: Get tag
15 | id: vars
16 | run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
17 | - name: Verify tag
18 | run: |
19 | VERSION=$(grep VERSION_NAME gradle.properties | awk -F= '{ print $2 }' | sed "s/-SNAPSHOT//")
20 | if [ "${{ steps.vars.outputs.tag }}" != "$VERSION" ]; then {
21 | echo "Tag ${{ steps.vars.outputs.tag }} does not match the package version ($VERSION)"
22 | exit 1
23 | } fi
24 |
25 | - name: Grant execute permission for gradlew
26 | run: chmod +x gradlew
27 | - name: cache gradle dependencies
28 | uses: actions/cache@v3
29 | with:
30 | path: |
31 | ~/.gradle/caches
32 | ~/.gradle/wrapper
33 | key: ${{ runner.os }}-gradle-core-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
34 | restore-keys: |
35 | ${{ runner.os }}-gradle-core-
36 | - name: Publush release to sonatype
37 | run: ./gradlew clean build publish publishToSonatype -Prelease closeAndReleaseSonatypeStagingRepository
38 | env:
39 | SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
40 | SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
41 | ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.NEXUS_USERNAME }}
42 | ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.NEXUS_PASSWORD }}
43 | SIGNING_PRIVATE_KEY_BASE64: ${{ secrets.SIGNING_PRIVATE_KEY_BASE64 }}
44 |
45 | - name: create release
46 | run: |
47 | curl \
48 | -X POST \
49 | -H "Authorization: token $RELEASE_TOKEN" \
50 | https://api.github.com/repos/${{github.repository}}/releases \
51 | -d '{"tag_name": "${{ env.RELEASE_VERSION }}", "name": "${{ env.RELEASE_VERSION }}", "body": "Release of version ${{ env.RELEASE_VERSION }}", "draft": false, "prerelease": false, "generate_release_notes": true}'
52 | env:
53 | RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
54 | RELEASE_VERSION: ${{ steps.vars.outputs.tag }}
--------------------------------------------------------------------------------
/.github/workflows/snapshot.yml:
--------------------------------------------------------------------------------
1 | name: Snapshot
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 |
7 | jobs:
8 | snapshot:
9 | runs-on: ubuntu-22.04
10 | environment: deployment
11 | steps:
12 | - uses: actions/checkout@v2
13 | - name: Grant execute permission for gradlew
14 | run: chmod +x gradlew
15 | - name: cache gradle dependencies
16 | uses: actions/cache@v3
17 | with:
18 | path: |
19 | ~/.gradle/caches
20 | ~/.gradle/wrapper
21 | key: ${{ runner.os }}-gradle-core-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
22 | restore-keys: |
23 | ${{ runner.os }}-gradle-core-
24 | - name: Publush snapshot to sonatype
25 | run: ./gradlew clean build publish publishToSonatype
26 | env:
27 | SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
28 | SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
29 | ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.NEXUS_USERNAME }}
30 | ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.NEXUS_PASSWORD }}
31 | SIGNING_PRIVATE_KEY_BASE64: ${{ secrets.SIGNING_PRIVATE_KEY_BASE64 }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 | .idea
17 | /core/bin
18 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project.
4 |
5 | ## Development workflow
6 |
7 | We recommend using the Android Studio IDE to jumpstart your development workflow
8 |
9 | To get started with the project
10 | - clone this repository
11 | - import the project into Android Studio
12 |
13 | While developing, you can run the [example app](/example/) to test your changes.
14 | - You can do this via the IDE itself using the `Run app` button in the toolbar
15 | - You can install the debug version of the app using the gradle command:
16 | ```
17 | ./gradlew installDebug
18 | ```
19 |
20 | Remember to add tests for your change if possible. Run the unit tests:
21 | - Running the tests via the IDE
22 | - via the command line
23 | ```
24 | ./gradlew test
25 | ```
26 |
27 | ### Commit message convention
28 |
29 | We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages:
30 |
31 | - `fix`: bug fixes, e.g. fix crash due to deprecated method.
32 | - `feat`: new features, e.g. add new method to the module.
33 | - `refactor`: code refactor, e.g. migrate from class components to hooks.
34 | - `docs`: changes into documentation, e.g. add usage example for the module..
35 | - `test`: adding or updating tests, eg add integration tests using detox.
36 | - `chore`: tooling changes, e.g. change CI config.
37 |
38 | ### Linting and tests
39 |
40 | ./gradlew test
41 |
42 | Our pre-commit hooks verify that the linter and tests pass when committing.
43 |
44 | ### Scripts
45 |
46 | ### Sending a pull request
47 |
48 | > **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
49 |
50 | When you're sending a pull request:
51 |
52 | - Prefer small pull requests focused on one change.
53 | - Verify that linters and tests are passing.
54 | - Review the documentation to make sure it looks good.
55 | - Follow the pull request template when opening a pull request.
56 | - For pull requests that change the API or implementation, discuss with maintainers first by opening an issue.
57 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Segment
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/RELEASING.md:
--------------------------------------------------------------------------------
1 | Update Version
2 | =========
3 | 1. Update VERSION_CODE in `gradle.properties`
4 | 2. Update VERSION_NAME in `gradle.properties`
5 | 3. Update LIBRARY_VERSION in `core/Constants.kt`
6 |
7 | Releasing
8 | =========
9 |
10 | 1. Create a new branch called `release/X.Y.Z`
11 | 2. `git checkout -b release/X.Y.Z`
12 | 3. Change the version in `gradle.properties` to your desired release version (see `Update Version`)
13 | 4. `git commit -am "Create release X.Y.Z."` (where X.Y.Z is the new version)
14 | 5. `git tag -a X.Y.Z -m "Version X.Y.Z"` (where X.Y.Z is the new version)
15 | 6. Upgrade to next version by changing version in `gradle.properties`
16 | 7. `git commit -am "Prepare snapshot X.Y.Z-SNAPSHOT"`
17 | 8. `git push && git push --tags`
18 | 9. Create a PR to merge the new branch into `master`
19 | 10. The CI pipeline will recognize the tag and upload, close and promote the artifacts automatically, and generate changelog automatically
20 |
21 | Example (stable release)
22 | ========
23 | 1. Current VERSION_NAME in `gradle.properties` = 1.3.0
24 | 2. `git checkout -b release/1.3.1`
25 | 3. Change VERSION_NAME = 1.3.1 (next higher version)
26 | 4. Update CHANGELOG.md
27 | 5. `git commit -am "Create release 1.3.1`
28 | 6. `git tag -a 1.3.1 -m "Version 1.3.1"`
29 | 6. `git push && git push --tags`
30 | 7. Change VERSION_NAME = 1.3.2 (next higher version)
31 | 8. `git commit -am "Prepare snapshot 1.3.2-SNAPSHOT"`
32 | 9. `git push && git push --tags`
33 | 10. Merging PR master will create a snapshot release 1.3.2-SNAPSHOT and tag push will create stable release 1.3.1
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | id 'org.jetbrains.kotlin.plugin.serialization'
5 | id 'de.mannodermaus.android-junit5'
6 | }
7 |
8 | android {
9 | compileSdkVersion 33
10 |
11 | defaultConfig {
12 | // Required when setting minSdkVersion to 20 or lower
13 | multiDexEnabled true
14 |
15 | minSdkVersion 16
16 | targetSdkVersion 33
17 | versionCode VERSION_CODE.toInteger()
18 | versionName VERSION_NAME
19 |
20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
21 | consumerProguardFiles "consumer-rules.pro"
22 |
23 | buildConfigField("int", "SEGMENT_VERSION_CODE", "${versionCode}")
24 | buildConfigField("String", "SEGMENT_VERSION_NAME", "\"${versionName}\"")
25 | }
26 |
27 | buildTypes {
28 | release {
29 | minifyEnabled false
30 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
31 | }
32 | }
33 | compileOptions {
34 | sourceCompatibility JavaVersion.VERSION_1_8
35 | targetCompatibility JavaVersion.VERSION_1_8
36 | }
37 | kotlinOptions {
38 | jvmTarget = '1.8'
39 | }
40 | testOptions {
41 | unitTests {
42 | includeAndroidResources = true
43 | }
44 | }
45 |
46 | }
47 |
48 | dependencies {
49 | // MAIN DEPS
50 | api project(':core')
51 | api 'com.segment:sovran-kotlin:1.2.2'
52 | api "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1"
53 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1'
54 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1'
55 | implementation 'androidx.lifecycle:lifecycle-process:2.6.1'
56 | implementation 'androidx.lifecycle:lifecycle-common-java8:2.6.1'
57 |
58 |
59 | // TESTING
60 | testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2'
61 | testImplementation 'io.mockk:mockk:1.12.2'
62 | testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.1'
63 | androidTestImplementation 'androidx.test.ext:junit:1.1.5'
64 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
65 |
66 | // Add JUnit5 dependencies.
67 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
68 | testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2'
69 |
70 | // Add JUnit4 legacy dependencies.
71 | testImplementation 'junit:junit:4.13.2'
72 | testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.8.2'
73 |
74 | // Add Roboelectric dependencies.
75 | testImplementation 'org.robolectric:robolectric:4.7.3'
76 | testImplementation 'androidx.test:core:1.5.0'
77 | }
78 |
79 | apply from: rootProject.file('gradle/artifacts-android.gradle')
80 | apply from: rootProject.file('gradle/mvn-publish.gradle')
81 | apply from: rootProject.file('gradle/codecov.gradle')
--------------------------------------------------------------------------------
/android/consumer-rules.pro:
--------------------------------------------------------------------------------
1 | # kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
2 | -keepclassmembers class kotlinx.serialization.json.** {
3 | *** Companion;
4 | }
5 | -keepclasseswithmembers class kotlinx.serialization.json.** {
6 | kotlinx.serialization.KSerializer serializer(...);
7 | }
8 |
9 | # These rules will ensure that our generated serializers dont get obfuscated
10 | -keep,includedescriptorclasses class com.segment.analytics.kotlin.**$$serializer { *; }
11 | -keepclassmembers class com.segment.analytics.kotlin.** {
12 | *** Companion;
13 | }
14 | -keepclasseswithmembers class com.segment.analytics.kotlin.** {
15 | kotlinx.serialization.KSerializer serializer(...);
16 | }
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=Segment Kotlin Analytics
2 | POM_ARTIFACT_ID=android
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/android/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/android/src/androidTest/java/com/segment/analytics/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 | import com.segment.analytics.kotlin.core.Telemetry
6 |
7 | import org.junit.Test
8 | import org.junit.runner.RunWith
9 |
10 | import org.junit.Assert.*
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * See [testing documentation](http://d.android.com/tools/testing).
16 | */
17 | @RunWith(AndroidJUnit4::class)
18 | class ExampleInstrumentedTest {
19 | init {
20 | Telemetry.enable = false
21 | }
22 |
23 | @Test
24 | fun useAppContext() {
25 | // Context of the app under test.
26 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
27 | assertEquals("com.segment.analytics.test", appContext.packageName)
28 | }
29 | }
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/android/src/main/java/com/segment/analytics/kotlin/android/utilities/AndroidKVS.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.android.utilities
2 |
3 | import android.content.SharedPreferences
4 | import com.segment.analytics.kotlin.core.utilities.KVS
5 |
6 | /**
7 | * A key-value store wrapper for sharedPreferences on Android
8 | */
9 | class AndroidKVS(val sharedPreferences: SharedPreferences): KVS {
10 |
11 |
12 | override fun get(key: String, defaultVal: Int) =
13 | sharedPreferences.getInt(key, defaultVal)
14 |
15 | override fun get(key: String, defaultVal: String?) =
16 | sharedPreferences.getString(key, defaultVal) ?: defaultVal
17 |
18 | override fun put(key: String, value: Int) =
19 | sharedPreferences.edit().putInt(key, value).commit()
20 |
21 | override fun put(key: String, value: String) =
22 | sharedPreferences.edit().putString(key, value).commit()
23 |
24 | override fun remove(key: String): Boolean =
25 | sharedPreferences.edit().remove(key).commit()
26 |
27 | override fun contains(key: String) = sharedPreferences.contains(key)
28 | }
--------------------------------------------------------------------------------
/android/src/main/java/com/segment/analytics/kotlin/android/utilities/DeepLinkUtils.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.android.utilities
2 |
3 | import android.content.Intent
4 | import android.net.Uri
5 | import com.segment.analytics.kotlin.core.Analytics
6 | import kotlinx.serialization.json.JsonObject
7 | import kotlinx.serialization.json.buildJsonObject
8 | import kotlinx.serialization.json.put
9 |
10 | class DeepLinkUtils(val analytics: Analytics) {
11 |
12 | fun trackDeepLinkFrom(referrer: String?, intent: Intent?) {
13 |
14 | if (intent == null || intent.data == null) {
15 | return
16 | }
17 |
18 | val properties = extractLinkProperties(referrer, intent.data)
19 | analytics.track("Deep Link Opened", properties)
20 | }
21 |
22 | /**
23 | * Builds a JsonObject with the parameters of a given Uri.
24 | *
25 | * Note: The Uri must be hierarchical (myUri.isHierarchical == true) for parameters to be
26 | * extracted.
27 | *
28 | * Example hierarchical Uri: http://example.com/
29 | * Example non-hierarchical Uri: mailto:me@email.com
30 | *
31 | * Note: we return the given Uri as a property named: "url" since this is what is expected
32 | * upstream.
33 | */
34 | fun extractLinkProperties(
35 | referrer: String?,
36 | uri: Uri?
37 | ): JsonObject {
38 | val properties = buildJsonObject {
39 | referrer?.let {
40 | put("referrer", it)
41 | }
42 |
43 | uri?.let {
44 | if (uri.isHierarchical) {
45 | for (parameter in uri.queryParameterNames) {
46 | val value = uri.getQueryParameter(parameter)
47 | if (value != null && value.trim().isNotEmpty()) {
48 | put(parameter, value)
49 | }
50 | }
51 | }
52 | put("url", uri.toString())
53 | }
54 | }
55 |
56 | return properties
57 | }
58 | }
--------------------------------------------------------------------------------
/android/src/main/java/com/segment/analytics/kotlin/android/utilities/JSON.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.android.utilities
2 |
3 | import com.segment.analytics.kotlin.core.utilities.toContent
4 | import kotlinx.serialization.json.JsonArray
5 | import kotlinx.serialization.json.JsonElement
6 | import kotlinx.serialization.json.JsonObject
7 | import kotlinx.serialization.json.JsonPrimitive
8 | import org.json.JSONArray
9 | import org.json.JSONObject
10 |
11 | // Transform kotlinx.JsonArray to org.json.JSONArray
12 | fun List.toJSONArray(): JSONArray {
13 | val constructed = JSONArray()
14 | for (item in this) {
15 | when (item) {
16 | is JsonPrimitive -> {
17 | constructed.put(item.toContent())
18 | }
19 | is JsonObject -> {
20 | constructed.put(item.toJSONObject())
21 | }
22 | is JsonArray -> {
23 | constructed.put(item.toJSONArray())
24 | }
25 | }
26 | }
27 | return constructed
28 | }
29 |
30 | // Transform kotlinx.JsonArray to org.json.JSONArray
31 | fun Map.toJSONObject(): JSONObject {
32 | val constructed = JSONObject()
33 | for ((k, v) in entries) {
34 | when (v) {
35 | is JsonPrimitive -> {
36 | constructed.put(k, v.toContent())
37 | }
38 | is JsonObject -> {
39 | constructed.put(k, v.toJSONObject())
40 | }
41 | is JsonArray -> {
42 | constructed.put(k, v.toJSONArray())
43 | }
44 | }
45 | }
46 | return constructed
47 | }
--------------------------------------------------------------------------------
/android/src/test/java/com/segment/analytics/kotlin/android/AndroidAnalyticsKtTest.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.android
2 |
3 | import com.segment.analytics.kotlin.core.Telemetry
4 | import org.junit.jupiter.api.Assertions.*
5 |
6 | import org.junit.jupiter.api.Test
7 | import org.junit.jupiter.api.assertThrows
8 |
9 | internal class AndroidAnalyticsKtTest {
10 | init {
11 | Telemetry.enable = false
12 | }
13 |
14 | @Test
15 | fun `jvm initializer in android platform should failed`() {
16 | val exception = assertThrows {
17 | com.segment.analytics.kotlin.core.Analytics("123") {
18 | application = "Test"
19 | }
20 | }
21 |
22 | assertEquals(exception.message?.contains("Android"), true)
23 | }
24 | }
--------------------------------------------------------------------------------
/android/src/test/java/com/segment/analytics/kotlin/android/utilities/AndroidKVSTest.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.android.utilities
2 |
3 | import com.segment.analytics.kotlin.android.utils.MemorySharedPreferences
4 | import com.segment.analytics.kotlin.core.utilities.KVS
5 | import org.junit.jupiter.api.Assertions
6 | import org.junit.jupiter.api.BeforeEach
7 | import org.junit.jupiter.api.Test
8 |
9 | class AndroidKVSTest {
10 |
11 | private lateinit var prefs: KVS
12 |
13 | @BeforeEach
14 | fun setup(){
15 | val sharedPreferences = MemorySharedPreferences()
16 | prefs = AndroidKVS(sharedPreferences)
17 | prefs.put("int", 1)
18 | prefs.put("string", "string")
19 | }
20 |
21 | @Test
22 | fun getTest() {
23 | Assertions.assertEquals(1, prefs.get("int", 0))
24 | Assertions.assertEquals("string", prefs.get("string", null))
25 | Assertions.assertEquals(0, prefs.get("keyNotExists", 0))
26 | Assertions.assertEquals(null, prefs.get("keyNotExists", null))
27 | }
28 |
29 | @Test
30 | fun putTest() {
31 | prefs.put("int", 2)
32 | prefs.put("string", "stringstring")
33 |
34 | Assertions.assertEquals(2, prefs.get("int", 0))
35 | Assertions.assertEquals("stringstring", prefs.get("string", null))
36 | }
37 |
38 | @Test
39 | fun containsAndRemoveTest() {
40 | Assertions.assertTrue(prefs.contains("int"))
41 | prefs.remove("int")
42 | Assertions.assertFalse(prefs.contains("int"))
43 | }
44 | }
--------------------------------------------------------------------------------
/android/src/test/java/com/segment/analytics/kotlin/android/utils/Plugins.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.android.utils
2 |
3 | import com.segment.analytics.kotlin.core.*
4 | import com.segment.analytics.kotlin.core.platform.*
5 |
6 | class TestRunPlugin(var closure: (BaseEvent?) -> Unit): EventPlugin {
7 | override val type: Plugin.Type = Plugin.Type.Before
8 | override lateinit var analytics: Analytics
9 | var ran = false
10 |
11 | override fun reset() {
12 | ran = false
13 | }
14 |
15 | fun updateState(ran: Boolean) {
16 | this.ran = ran
17 | }
18 |
19 | override fun execute(event: BaseEvent): BaseEvent {
20 | super.execute(event)
21 | updateState(true)
22 | return event
23 | }
24 |
25 | override fun track(payload: TrackEvent): BaseEvent {
26 | closure(payload)
27 | updateState(true)
28 | return payload
29 | }
30 |
31 | override fun identify(payload: IdentifyEvent): BaseEvent {
32 | closure(payload)
33 | updateState(true)
34 | return payload
35 | }
36 |
37 | override fun screen(payload: ScreenEvent): BaseEvent {
38 | closure(payload)
39 | updateState(true)
40 | return payload
41 | }
42 |
43 | override fun group(payload: GroupEvent): BaseEvent {
44 | closure(payload)
45 | updateState(true)
46 | return payload
47 | }
48 |
49 | override fun alias(payload: AliasEvent): BaseEvent {
50 | closure(payload)
51 | updateState(true)
52 | return payload
53 | }
54 | }
55 |
56 | class StubPlugin : EventPlugin {
57 | override val type: Plugin.Type = Plugin.Type.Before
58 | override lateinit var analytics: Analytics
59 | }
--------------------------------------------------------------------------------
/android/src/test/resources/robolectric.properties:
--------------------------------------------------------------------------------
1 | sdk=31
2 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | ext.kotlin_version = "1.8.0"
4 | ext.writeKey = "hAh3QxoRCsQsnyabTLMpWbpxoIN4O2mU"
5 | repositories {
6 | google()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:7.4.2'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
12 |
13 | // needed for android unit tests
14 | classpath("de.mannodermaus.gradle.plugins:android-junit5:1.9.3.0")
15 | // NOTE: Do not place your application dependencies here; they belong
16 | // in the individual module build.gradle files
17 | classpath 'com.google.gms:google-services:4.3.15'
18 | }
19 | }
20 |
21 | plugins {
22 | id "io.snyk.gradle.plugin.snykplugin" version "0.4"
23 | id "io.github.gradle-nexus.publish-plugin" version "1.1.0"
24 | }
25 |
26 | allprojects {
27 | repositories {
28 | google()
29 | mavenCentral()
30 | maven { url "https://kotlin.bintray.com/kotlinx" }
31 | maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
32 | }
33 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
34 | kotlinOptions {
35 | freeCompilerArgs = ['-Xjvm-default=enable']
36 | jvmTarget = "1.8"
37 | }
38 | }
39 | group GROUP
40 | version getVersionName()
41 | }
42 |
43 | snyk {
44 | // severity = 'high'
45 | autoDownload = true
46 | autoUpdate = true
47 | arguments = '--all-sub-projects --fail-on=upgradable'
48 | }
49 |
50 | task clean(type: Delete) {
51 | delete rootProject.buildDir
52 | }
53 |
54 | def getVersionName() { // If not release build add SNAPSHOT suffix
55 | return hasProperty('release') ? VERSION_NAME : VERSION_NAME+"-SNAPSHOT"
56 | }
57 |
58 | apply from: rootProject.file('gradle/promote.gradle')
59 | apply from: rootProject.file('gradle/codecov.gradle')
--------------------------------------------------------------------------------
/core/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java-library'
3 | id 'kotlin'
4 | id 'org.jetbrains.kotlin.plugin.serialization'
5 | }
6 |
7 | java {
8 | sourceCompatibility = JavaVersion.VERSION_1_8
9 | targetCompatibility = JavaVersion.VERSION_1_8
10 | }
11 |
12 | compileKotlin {
13 | kotlinOptions {
14 | freeCompilerArgs += [
15 | "-Xopt-in=kotlin.RequiresOptIn"
16 | ]
17 | }
18 | }
19 |
20 | test {
21 | useJUnitPlatform()
22 | }
23 |
24 | dependencies {
25 | // MAIN DEPS
26 | api 'com.segment:sovran-kotlin:1.2.2'
27 | api "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1"
28 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1'
29 |
30 | // TESTING
31 | repositories { mavenCentral() }
32 | testImplementation 'io.mockk:mockk:1.10.6'
33 | testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.1'
34 |
35 | testImplementation platform("org.junit:junit-bom:5.7.2")
36 | testImplementation "org.junit.jupiter:junit-jupiter"
37 | }
38 |
39 | apply from: rootProject.file('gradle/artifacts-core.gradle')
40 | apply from: rootProject.file('gradle/mvn-publish.gradle')
41 | apply from: rootProject.file('gradle/codecov.gradle')
--------------------------------------------------------------------------------
/core/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=Segment Kotlin Analytics
2 | POM_ARTIFACT_ID=core
3 | POM_PACKAGING=jar
4 |
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/Annotations.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core
2 |
3 | @RequiresOptIn(
4 | level = RequiresOptIn.Level.WARNING,
5 | message = "This method invokes `runBlocking` internal, it's not recommended to be used in coroutines."
6 | )
7 | annotation class BlockingApi
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/Configuration.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core
2 |
3 | import com.segment.analytics.kotlin.core.Constants.DEFAULT_API_HOST
4 | import com.segment.analytics.kotlin.core.Constants.DEFAULT_CDN_HOST
5 | import com.segment.analytics.kotlin.core.platform.policies.FlushPolicy
6 | import com.segment.analytics.kotlin.core.utilities.ConcreteStorageProvider
7 | import kotlinx.coroutines.*
8 | import sovran.kotlin.Store
9 |
10 | /**
11 | * Configuration that analytics can use
12 | * @property writeKey the Segment writeKey
13 | * @property application defaults to `null`
14 | * @property storageProvider Provider for storage class, defaults to `ConcreteStorageProvider`
15 | * @property collectDeviceId collect deviceId, defaults to `false`
16 | * @property trackApplicationLifecycleEvents automatically send track for Lifecycle events (eg: Application Opened, Application Backgrounded, etc.), defaults to `false`
17 | * @property useLifecycleObserver enables the use of LifecycleObserver to track Application lifecycle events. Defaults to `false`.
18 | * @property trackDeepLinks automatically track [Deep link][https://developer.android.com/training/app-links/deep-linking] opened based on intents, defaults to `false`
19 | * @property flushAt count of events at which we flush events, defaults to `20`
20 | * @property flushInterval interval in seconds at which we flush events, defaults to `30 seconds`
21 | * @property defaultSettings Settings object that will be used as fallback in case of network failure, defaults to empty
22 | * @property autoAddSegmentDestination automatically add SegmentDestination plugin, defaults to `true`
23 | * @property apiHost set a default apiHost to which Segment sends events, defaults to `api.segment.io/v1`
24 | */
25 | data class Configuration(
26 | val writeKey: String,
27 | var application: Any? = null,
28 | var storageProvider: StorageProvider = ConcreteStorageProvider,
29 | var collectDeviceId: Boolean = false,
30 | var trackApplicationLifecycleEvents: Boolean = false,
31 | var useLifecycleObserver: Boolean = false,
32 | var trackDeepLinks: Boolean = false,
33 | var flushAt: Int = 20,
34 | var flushInterval: Int = 30,
35 | var flushPolicies: List = emptyList(),
36 | var defaultSettings: Settings? = null,
37 | var autoAddSegmentDestination: Boolean = true,
38 | var apiHost: String = DEFAULT_API_HOST,
39 | var cdnHost: String = DEFAULT_CDN_HOST,
40 | var requestFactory: RequestFactory = RequestFactory(),
41 | var errorHandler: ErrorHandler? = null
42 | ) {
43 | fun isValid(): Boolean {
44 | return writeKey.isNotBlank() && application != null
45 | }
46 | }
47 |
48 | interface CoroutineConfiguration {
49 | val store: Store
50 |
51 | val analyticsScope: CoroutineScope
52 |
53 | val analyticsDispatcher: CoroutineDispatcher
54 |
55 | val networkIODispatcher: CoroutineDispatcher
56 |
57 | val fileIODispatcher: CoroutineDispatcher
58 | }
59 |
60 | typealias ErrorHandler = (Throwable) -> Unit
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core
2 |
3 | object Constants {
4 | const val LIBRARY_VERSION = "1.19.2"
5 | const val DEFAULT_API_HOST = "api.segment.io/v1"
6 | const val DEFAULT_CDN_HOST = "cdn-settings.segment.com/v1"
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/Errors.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core
2 |
3 | import com.segment.analytics.kotlin.core.platform.plugins.logger.segmentLog
4 | import java.net.URL
5 |
6 | sealed class AnalyticsError(): Throwable() {
7 | data class StorageUnableToCreate(override val message: String?): AnalyticsError()
8 | data class StorageUnableToWrite(override val message: String?): AnalyticsError()
9 | data class StorageUnableToRename(override val message: String?): AnalyticsError()
10 | data class StorageUnableToOpen(override val message: String?): AnalyticsError()
11 | data class StorageUnableToClose(override val message: String?): AnalyticsError()
12 | data class StorageInvalid(override val message: String?): AnalyticsError()
13 | data class StorageUnknown(override val cause: Throwable?): AnalyticsError()
14 |
15 | data class NetworkUnexpectedHTTPCode(val uri: URL?, val code: Int): AnalyticsError()
16 | data class NetworkServerLimited(val uri: URL?, val code: Int): AnalyticsError()
17 | data class NetworkServerRejected(val uri: URL?, val code: Int): AnalyticsError()
18 | data class NetworkUnknown(val uri: URL?, override val cause: Throwable?): AnalyticsError()
19 | data class NetworkInvalidData(override val message: String?): AnalyticsError()
20 |
21 | data class JsonUnableToSerialize(override val cause: Throwable?): AnalyticsError()
22 | data class JsonUnableToDeserialize(override val cause: Throwable?): AnalyticsError()
23 | data class JsonUnknown(override val cause: Throwable?): AnalyticsError()
24 |
25 | data class PluginError(override val cause: Throwable?): AnalyticsError()
26 |
27 | data class EnrichmentError(override val message: String): AnalyticsError()
28 |
29 | data class SettingsFail(override val cause: AnalyticsError): AnalyticsError()
30 | data class BatchUploadFail(override val cause: AnalyticsError): AnalyticsError()
31 | }
32 |
33 | /**
34 | * Reports an internal error to the user-defined error handler.
35 | */
36 | fun Analytics.reportInternalError(error: Throwable) {
37 | configuration.errorHandler?.invoke(error)
38 | Analytics.reportInternalError(error)
39 | }
40 |
41 | fun reportErrorWithMetrics(analytics: Analytics?, error: Throwable,
42 | message: String, metric:String,
43 | log: String, buildTags: (MutableMap) -> Unit) {
44 | analytics?.configuration?.errorHandler?.invoke(error)
45 | var fullMessage = message
46 | error.message?.let { fullMessage += ": $it"}
47 | Analytics.segmentLog(fullMessage)
48 | Telemetry.error(metric, log, buildTags)
49 | }
50 |
51 | fun Analytics.Companion.reportInternalError(error: Throwable) {
52 | error.message?.let {
53 | Analytics.segmentLog(it)
54 | }
55 | }
56 |
57 | fun Analytics.Companion.reportInternalError(error: String) {
58 | Analytics.segmentLog(error)
59 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/compat/ConfigurationBuilder.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.compat
2 |
3 | import com.segment.analytics.kotlin.core.Configuration
4 | import com.segment.analytics.kotlin.core.RequestFactory
5 |
6 | /**
7 | * This class serves as a helper class for Java compatibility, which makes the
8 | * @see Configuration buildable through a builder pattern.
9 | * It's strongly discouraged to use this builder in a Kotlin based project, since
10 | * the optional parameters is the way to go in Kotlin.
11 | */
12 | class ConfigurationBuilder (writeKey: String) {
13 |
14 | private val configuration: Configuration = Configuration(writeKey)
15 |
16 | fun setApplication(application: Any?) = apply { configuration.application = application }
17 |
18 | fun setCollectDeviceId(collectDeviceId: Boolean) = apply { configuration.collectDeviceId = collectDeviceId }
19 |
20 | fun setTrackApplicationLifecycleEvents(trackApplicationLifecycleEvents: Boolean) = apply { configuration.trackApplicationLifecycleEvents = trackApplicationLifecycleEvents }
21 |
22 | fun setUseLifecycleObserver(useLifecycleObserver: Boolean) = apply { configuration.useLifecycleObserver = useLifecycleObserver }
23 |
24 | fun setTrackDeepLinks(trackDeepLinks: Boolean) = apply { configuration.trackDeepLinks = trackDeepLinks }
25 |
26 | fun setFlushAt(flushAt: Int) = apply { configuration.flushAt = flushAt }
27 |
28 | fun setFlushInterval(flushInterval: Int) = apply { configuration.flushInterval = flushInterval }
29 |
30 | fun setAutoAddSegmentDestination(autoAddSegmentDestination: Boolean) = apply { configuration.autoAddSegmentDestination = autoAddSegmentDestination}
31 |
32 | fun setApiHost(apiHost: String) = apply { configuration.apiHost = apiHost}
33 |
34 | fun setCdnHost(cdnHost: String) = apply { configuration.cdnHost = cdnHost}
35 |
36 | fun setRequestFactory(requestFactory: RequestFactory) = apply { configuration.requestFactory = requestFactory }
37 |
38 | fun build() = configuration
39 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/compat/JsonSerializable.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.compat
2 |
3 | import kotlinx.serialization.json.JsonObject
4 |
5 | interface JsonSerializable {
6 | fun serialize() : JsonObject
7 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/platform/plugins/ContextPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.platform.plugins
2 |
3 | import com.segment.analytics.kotlin.core.Analytics
4 | import com.segment.analytics.kotlin.core.BaseEvent
5 | import com.segment.analytics.kotlin.core.Constants.LIBRARY_VERSION
6 | import com.segment.analytics.kotlin.core.platform.Plugin
7 | import com.segment.analytics.kotlin.core.utilities.putAll
8 | import kotlinx.serialization.json.JsonObject
9 | import kotlinx.serialization.json.buildJsonObject
10 | import kotlinx.serialization.json.put
11 | import java.util.*
12 |
13 | /**
14 | * Analytics plugin used to populate events with basic context data.
15 | * Auto-added to analytics client on construction
16 | */
17 | class ContextPlugin : Plugin {
18 | override val type: Plugin.Type = Plugin.Type.Before
19 | override lateinit var analytics: Analytics
20 |
21 | private lateinit var library: JsonObject
22 | private val instanceId = UUID.randomUUID().toString()
23 |
24 | companion object {
25 | // Library
26 | const val LIBRARY_KEY = "library"
27 | const val LIBRARY_NAME_KEY = "name"
28 | const val LIBRARY_VERSION_KEY = "version"
29 | const val INSTANCE_ID_KEY = "instanceId"
30 | }
31 |
32 | override fun setup(analytics: Analytics) {
33 | super.setup(analytics)
34 | library = buildJsonObject {
35 | put(LIBRARY_NAME_KEY, "analytics-kotlin")
36 | put(LIBRARY_VERSION_KEY, LIBRARY_VERSION)
37 | }
38 | }
39 |
40 | private fun applyContextData(event: BaseEvent) {
41 | val newContext = buildJsonObject {
42 | // copy existing context
43 | putAll(event.context)
44 |
45 | // putLibrary
46 | put(LIBRARY_KEY, library)
47 | put(INSTANCE_ID_KEY, instanceId)
48 | }
49 | event.context = newContext
50 | }
51 |
52 | override fun execute(event: BaseEvent): BaseEvent {
53 | applyContextData(event)
54 | return event
55 | }
56 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/platform/plugins/DestinationMetadataPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.platform.plugins
2 |
3 | import com.segment.analytics.kotlin.core.Analytics
4 | import com.segment.analytics.kotlin.core.BaseEvent
5 | import com.segment.analytics.kotlin.core.DestinationMetadata
6 | import com.segment.analytics.kotlin.core.Settings
7 | import com.segment.analytics.kotlin.core.platform.DestinationPlugin
8 | import com.segment.analytics.kotlin.core.platform.Plugin
9 | import com.segment.analytics.kotlin.core.utilities.safeJsonArray
10 | import com.segment.analytics.kotlin.core.utilities.safeJsonObject
11 | import kotlinx.serialization.json.JsonPrimitive
12 |
13 | /**
14 | * DestinationMetadataPlugin adds `_metadata` information to payloads that Segment uses to
15 | * determine delivery of events to cloud/device-mode destinations
16 | */
17 | class DestinationMetadataPlugin : Plugin {
18 | override val type: Plugin.Type = Plugin.Type.Enrichment
19 | override lateinit var analytics: Analytics
20 | private var analyticsSettings: Settings = Settings()
21 |
22 | override fun update(settings: Settings, type: Plugin.UpdateType) {
23 | super.update(settings, type)
24 | analyticsSettings = settings
25 | }
26 |
27 | override fun execute(event: BaseEvent): BaseEvent {
28 | // Using this over `findAll` for that teensy performance benefit
29 | val enabledDestinations = analytics.timeline.plugins[Plugin.Type.Destination]?.plugins
30 | ?.map { it as DestinationPlugin }
31 | ?.filter { it.enabled && it !is SegmentDestination }
32 | val metadata = DestinationMetadata().apply {
33 | // Mark all loaded destinations as bundled
34 | val bundled = buildSet { enabledDestinations?.forEach { add(it.key) } }
35 |
36 | // All active integrations, not in `bundled` are put in `unbundled`
37 | // All unbundledIntegrations not in `bundled` are put in `unbundled`
38 | val unbundled = buildSet {
39 | analyticsSettings.integrations.keys.forEach {
40 | if (it != "Segment.io" && !bundled.contains(it)) {
41 | add(it)
42 | }
43 | }
44 |
45 | analyticsSettings.integrations["Segment.io"]?.safeJsonObject
46 | ?.get("unbundledIntegrations")?.safeJsonArray
47 | ?.forEach {
48 | val content = (it as JsonPrimitive).content
49 | if (!bundled.contains(content)) {
50 | add(content)
51 | }
52 | }
53 | }
54 |
55 | // `bundledIds` for mobile is empty
56 | this.bundledIds = emptyList()
57 | this.bundled = bundled.toList()
58 | this.unbundled = unbundled.toList()
59 | }
60 |
61 | val payload = event.copy().apply {
62 | this._metadata = metadata
63 | }
64 |
65 | return payload
66 | }
67 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/platform/plugins/DeviceToken.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.platform.plugins
2 |
3 | import com.segment.analytics.kotlin.core.Analytics
4 | import com.segment.analytics.kotlin.core.BaseEvent
5 | import com.segment.analytics.kotlin.core.platform.Plugin
6 | import com.segment.analytics.kotlin.core.utilities.putInContextUnderKey
7 |
8 | /**
9 | * Analytics plugin to add device token to events
10 | */
11 | class DeviceToken(var token: String) : Plugin {
12 | override var type = Plugin.Type.Before
13 | override lateinit var analytics: Analytics
14 |
15 | override fun execute(event: BaseEvent): BaseEvent {
16 | event.putInContextUnderKey("device", "token", token)
17 | return event
18 | }
19 | }
20 |
21 | /**
22 | * Set a device token in your payload's context
23 | * @param token [String] Device Token to add to payloads
24 | */
25 | fun Analytics.setDeviceToken(token: String) {
26 | var tokenPlugin = find(DeviceToken::class)
27 | if (tokenPlugin != null) {
28 | tokenPlugin.token = token
29 | } else {
30 | tokenPlugin = DeviceToken(token)
31 | add(tokenPlugin)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/platform/plugins/StartupQueue.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.platform.plugins
2 |
3 | import com.segment.analytics.kotlin.core.Analytics
4 | import com.segment.analytics.kotlin.core.BaseEvent
5 | import com.segment.analytics.kotlin.core.System
6 | import com.segment.analytics.kotlin.core.platform.Plugin
7 | import kotlinx.coroutines.launch
8 | import com.segment.analytics.kotlin.core.platform.plugins.logger.*
9 | import sovran.kotlin.Subscriber
10 | import java.util.Queue
11 | import java.util.concurrent.ConcurrentLinkedQueue
12 | import java.util.concurrent.atomic.AtomicBoolean
13 |
14 | /**
15 | * Analytics plugin to manage started state of analytics client
16 | * All events will be held in an in-memory queue until started state is enabled, and once enabled
17 | * events will be replayed into the analytics timeline
18 | */
19 | class StartupQueue : Plugin, Subscriber {
20 | override val type: Plugin.Type = Plugin.Type.Before
21 | override lateinit var analytics: Analytics
22 |
23 | private val maxSize = 1000
24 | private val started: AtomicBoolean = AtomicBoolean(false)
25 | private val queuedEvents: Queue = ConcurrentLinkedQueue()
26 |
27 | override fun setup(analytics: Analytics) {
28 | super.setup(analytics)
29 | with(analytics) {
30 | analyticsScope.launch(analyticsDispatcher) {
31 | store.subscribe(
32 | subscriber = this@StartupQueue,
33 | stateClazz = System::class,
34 | initialState = true,
35 | handler = this@StartupQueue::runningUpdate
36 | )
37 | }
38 | }
39 | }
40 |
41 | override fun execute(event: BaseEvent): BaseEvent? {
42 | if (!started.get()) {
43 | analytics.log("SegmentStartupQueue queueing event")
44 | // timeline hasn't started, so queue it up.
45 | if (queuedEvents.size >= maxSize) {
46 | // if we've exceeded the max queue size start dropping events
47 | queuedEvents.remove()
48 | }
49 | queuedEvents.offer(event)
50 | return null
51 | }
52 | // the timeline has started, so let the event pass.
53 | return event
54 | }
55 |
56 | // Handler to manage system update
57 | private fun runningUpdate(state: System) {
58 | analytics.log("Analytics starting = ${state.running}")
59 | started.set(state.running)
60 | if (started.get()) {
61 | replayEvents()
62 | }
63 | }
64 |
65 | private fun replayEvents() {
66 | // replay the queued events to the instance of Analytics we're working with.
67 | while (!queuedEvents.isEmpty()) {
68 |
69 | val event = queuedEvents.poll()
70 |
71 | // It is possible that event might actually be null due to time-slicing
72 | // after checking if the queue is empty so we only process if the event
73 | // if it is indeed not NULL.
74 | event?.let {
75 | analytics.process(it, it.enrichment)
76 | }
77 | }
78 | }
79 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/platform/plugins/UserInfoPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.platform.plugins
2 |
3 | import com.segment.analytics.kotlin.core.*
4 | import com.segment.analytics.kotlin.core.platform.Plugin
5 |
6 | /**
7 | * Analytics plugin used to populate events with basic UserInfo data.
8 | * Auto-added to analytics client on construction
9 | */
10 | class UserInfoPlugin : Plugin {
11 |
12 | override val type: Plugin.Type = Plugin.Type.Before
13 | override lateinit var analytics: Analytics
14 |
15 | override fun execute(event: BaseEvent): BaseEvent {
16 |
17 | if (event.type == EventType.Identify) {
18 |
19 | analytics.userInfo.userId = event.userId
20 | analytics.userInfo.anonymousId = event.anonymousId
21 | analytics.userInfo.traits = (event as IdentifyEvent).traits
22 |
23 | } else if (event.type === EventType.Alias) {
24 |
25 | analytics.userInfo.anonymousId = event.anonymousId
26 | } else {
27 |
28 | analytics.userInfo.userId?.let {
29 | event.userId = analytics.userInfo.userId.toString()
30 | }
31 | analytics.userInfo.anonymousId?.let {
32 | event.anonymousId = analytics.userInfo.anonymousId.toString()
33 | }
34 | }
35 |
36 | return event
37 | }
38 |
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/platform/plugins/logger/ConsoleLogger.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.platform.plugins.logger
2 |
3 | class ConsoleLogger: Logger {
4 | override fun parseLog(log: LogMessage) {
5 | println("[Segment ${log.kind.toString()} ${log.message}")
6 | }
7 |
8 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/platform/plugins/logger/Logger.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.platform.plugins.logger
2 |
3 | import com.segment.analytics.kotlin.core.Analytics
4 | import java.util.*
5 |
6 | /**
7 | * The foundation for building out a special logger. If logs need to be directed to a certain area, this is the
8 | * interface to start off with. For instance a console logger, a networking logger or offline storage logger
9 | * would all start off with LogTarget.
10 | */
11 | interface Logger {
12 |
13 | /**
14 | * Implement this method to process logging messages. This is where the logic for the target will be
15 | * added. Feel free to add your own data queueing and offline storage.
16 | * - important: Use the Segment Network stack for Segment library compatibility and simplicity.
17 | */
18 | fun parseLog(log: LogMessage)
19 | }
20 |
21 | /**
22 | * Used for analytics.log() types. This lets the system know what to filter on and how to set priorities.
23 | */
24 | enum class LogKind {
25 | ERROR,
26 | WARNING,
27 | DEBUG;
28 |
29 | override fun toString(): String {
30 | return when(this) {
31 | ERROR -> "ERROR" // Not Verbose (fail cases | non-recoverable errors)
32 | WARNING -> "Warning" // Semi-verbose (deprecations | potential issues)
33 | DEBUG -> "Debug" // Verbose (everything of interest)
34 | }
35 | }
36 | }
37 |
38 | /**
39 | * The interface to the message being returned to `LogTarget` -> `parseLog()`.
40 | */
41 | data class LogMessage(
42 | val kind: LogKind,
43 | val message: String,
44 | val dateTime: Date = Date())
45 |
46 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
47 | /**
48 | * The public logging method for capturing all general types of log messages related to Segment.
49 | *
50 | * @property message The main message of the log to be captured.
51 | * @property kind Usually .error, .warning or .debug, in order of severity. This helps filter logs based on
52 | * this added metadata.
53 | * @property function The name of the function the log came from. This will be captured automatically.
54 | * @property line The line number in the function the log came from. This will be captured automatically.
55 | */
56 | @JvmOverloads
57 | fun Analytics.log(message: String, kind: LogKind = LogKind.DEBUG) {
58 | Analytics.segmentLog(message, kind)
59 | }
60 |
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/platform/plugins/logger/SegmentLog.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.platform.plugins.logger
2 |
3 | import com.segment.analytics.kotlin.core.Analytics
4 |
5 | // Internal log usage
6 | fun Analytics.Companion.segmentLog(message: String, kind: LogKind = LogKind.ERROR) {
7 | val logger = logger
8 | val logMessage = LogMessage(kind, message=message)
9 | when (kind){
10 | LogKind.DEBUG -> {
11 | if (debugLogsEnabled) {
12 | logger.parseLog(logMessage)
13 | }
14 | }
15 | else -> logger.parseLog(logMessage)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/platform/policies/CountBasedFlushPolicy.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.platform.policies
2 |
3 | import com.segment.analytics.kotlin.core.BaseEvent
4 |
5 | /**
6 | * A Count based Flush Policy that instructs the EventPipeline to flush at the
7 | * given @param[flushAt]. The default value is 20. @param[flushAt] values should
8 | * be >= 1 or they'll get the default value.
9 | */
10 | class CountBasedFlushPolicy(var flushAt: Int = 20): FlushPolicy {
11 |
12 |
13 | init {
14 | // Make sure to only take valid counts or fallback to our default.
15 | flushAt = when {
16 | flushAt >= 1 -> flushAt
17 | else -> 20
18 | }
19 | }
20 |
21 | private var count: Int = 0
22 |
23 | override fun shouldFlush(): Boolean {
24 | return count >= flushAt
25 | }
26 |
27 | override fun updateState(event: BaseEvent) {
28 | count++
29 | }
30 |
31 | override fun reset() {
32 | count = 0
33 | }
34 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/platform/policies/FlushPolicy.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.platform.policies
2 |
3 | import com.segment.analytics.kotlin.core.Analytics
4 | import com.segment.analytics.kotlin.core.BaseEvent
5 | import kotlinx.coroutines.CoroutineScope
6 |
7 | interface FlushPolicy {
8 |
9 | /**
10 | * Called when the policy becomes active. We should start any periodic flushing
11 | * we want here.
12 | */
13 | fun schedule(analytics: Analytics) = Unit
14 |
15 | /**
16 | * Called when policy should stop running any scheduled flushes
17 | */
18 | fun unschedule() = Unit
19 |
20 | /**
21 | * Called to check whether or not the events should be flushed.
22 | */
23 | fun shouldFlush(): Boolean
24 |
25 | /**
26 | * Called as events are added to the timeline and JSON Stringified.
27 | */
28 | fun updateState(event: BaseEvent) = Unit
29 |
30 | /**
31 | * Called after the events are flushed.
32 | */
33 | fun reset() = Unit
34 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/platform/policies/FrequencyFlushPolicy.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.platform.policies
2 |
3 | import com.segment.analytics.kotlin.core.Analytics
4 | import kotlinx.coroutines.Job
5 | import kotlinx.coroutines.delay
6 | import kotlinx.coroutines.isActive
7 | import kotlinx.coroutines.launch
8 |
9 | class FrequencyFlushPolicy(var flushIntervalInMillis: Long = 30_000): FlushPolicy {
10 |
11 | var flushJob: Job? = null
12 | var jobStarted: Boolean = false
13 |
14 | override fun schedule(analytics: Analytics) {
15 |
16 | if (!jobStarted) {
17 | jobStarted = true
18 |
19 | flushJob = analytics.analyticsScope.launch(analytics.fileIODispatcher) {
20 |
21 | if (flushIntervalInMillis > 0) {
22 |
23 | while (isActive) {
24 | analytics.flush()
25 |
26 | // use delay to do periodical task
27 | // this is doable in coroutine, since delay only suspends, allowing thread to
28 | // do other work and then come back. see:
29 | // https://github.com/Kotlin/kotlinx.coroutines/issues/1632#issuecomment-545693827
30 | delay(flushIntervalInMillis)
31 | }
32 | }
33 | }
34 | }
35 | }
36 |
37 | override fun unschedule() {
38 | if (jobStarted) {
39 | jobStarted = false
40 | flushJob?.cancel()
41 | }
42 | }
43 |
44 | override fun shouldFlush(): Boolean = false // Always return false; Scheduler will call flush.
45 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/platform/policies/StartupFlushPolicy.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.platform.policies
2 |
3 | /**
4 | * Flush policy that dictates flushing events at app startup.
5 | */
6 | class StartupFlushPolicy: FlushPolicy {
7 |
8 | private var flushedAtStartup = false
9 |
10 | override fun shouldFlush(): Boolean {
11 | return when {
12 | flushedAtStartup -> false
13 | else -> {
14 | // Set to 'true' so we never flush again
15 | flushedAtStartup = true
16 | true
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/utilities/AnySerializer.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.utilities
2 |
3 | import kotlinx.serialization.*
4 | import kotlinx.serialization.descriptors.SerialDescriptor
5 | import kotlinx.serialization.encoding.Decoder
6 | import kotlinx.serialization.encoding.Encoder
7 | import kotlinx.serialization.json.Json
8 | import kotlinx.serialization.modules.SerializersModule
9 | import kotlinx.serialization.serializer
10 |
11 | object AnySerializer: KSerializer {
12 | override fun deserialize(decoder: Decoder): Any {
13 | // Stub function; should not be called.
14 | return "not-implemented";
15 | }
16 |
17 | @OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
18 | override val descriptor: SerialDescriptor = ContextualSerializer(Any::class, null, emptyArray()).descriptor
19 |
20 |
21 | override fun serialize(encoder: Encoder, value: Any) {
22 | val toJsonElement = value.toJsonElement()
23 | encoder.encodeSerializableValue(Json.serializersModule.serializer(), toJsonElement)
24 | }
25 | }
26 |
27 | /**
28 | * A pre-configured Json Implementation with an Any type serializer.
29 | */
30 | val JsonAnySerializer = Json { serializersModule = SerializersModule {
31 | contextual(Any::class) { AnySerializer }
32 | } }
33 |
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/utilities/Base64Utils.kt:
--------------------------------------------------------------------------------
1 | @file:JvmName("Base64Utils")
2 | package com.segment.analytics.kotlin.core.utilities
3 |
4 | // Encode string to base64
5 | fun encodeToBase64(str: String) = encodeToBase64(str.toByteArray())
6 |
7 | // Encode byte-array to base64, this implementation is not url-safe
8 | fun encodeToBase64(bytes: ByteArray) = buildString {
9 | val wData = ByteArray(3) // working data
10 | var i = 0
11 | while (i < bytes.size) {
12 | val leftover = bytes.size - i
13 | val available = if (leftover >= 3) {
14 | 3
15 | } else {
16 | leftover
17 | }
18 | for (j in 0 until available) {
19 | wData[j] = bytes[i++]
20 | }
21 | for (j in 2 downTo available) {
22 | wData[j] = 0 // clear out
23 | }
24 | // Given a 3 byte block (24 bits), encode it to 4 base64 characters
25 | val chunk = ((wData[0].toInt() and 0xFF) shl 16) or
26 | ((wData[1].toInt() and 0xFF) shl 8) or
27 | (wData[2].toInt() and 0xFF)
28 |
29 | // if we have too little characters in this block, we add padding
30 | val padCount = (wData.size - available) * 8 / 6
31 |
32 | // encode to base64
33 | for (index in 3 downTo padCount) { // 4 base64 characters
34 | val char = (chunk shr (6 * index)) and 0x3f // 0b00111111
35 | append(char.base64Val())
36 | }
37 |
38 | // add padding if needed
39 | repeat(padCount) { append("=") }
40 | }
41 | }
42 |
43 | private const val ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
44 | private fun Int.base64Val(): Char = ALPHABET[this]
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/utilities/DateTimeUtils.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.utilities
2 |
3 | import java.text.SimpleDateFormat
4 | import java.util.*
5 |
6 | class SegmentInstant {
7 | companion object {
8 | private val formatters = object : ThreadLocal() {
9 | override fun initialValue(): SimpleDateFormat {
10 | return SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'.'SSSzzz", Locale.ROOT).apply {
11 | timeZone = TimeZone.getTimeZone("UTC")
12 | }
13 | }
14 | }
15 |
16 | /**
17 | * This function is a replacement for Instant.now().toString(). It produces strings in a
18 | * compatible format.
19 | *
20 | * Ex:
21 | * Instant.now(): 2023-04-19T04:03:46.880969Z
22 | * dateTimeNowString(): 2023-04-19T04:03:46.880Z
23 | */
24 | fun now(): String {
25 | return from(Date())
26 | }
27 |
28 | internal fun from(date: Date): String {
29 | return formatters.get().format(date).replace("UTC", "Z")
30 | }
31 | }
32 | }
33 |
34 | @Deprecated("Please use SegmentInstant.now() instead", ReplaceWith("SegmentInstant.now()"))
35 | fun dateTimeNowString(): String {
36 | return SegmentInstant.now()
37 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/utilities/FileUtils.kt:
--------------------------------------------------------------------------------
1 | @file:JvmName("FileUtils")
2 | package com.segment.analytics.kotlin.core.utilities
3 |
4 | import java.io.File
5 | import java.io.IOException
6 |
7 | /**
8 | * Ensures that a directory is created in the given location, throws an IOException otherwise.
9 | */
10 | @Throws(IOException::class)
11 | fun createDirectory(location: File) {
12 | if (!(location.exists() || location.mkdirs() || location.isDirectory)) {
13 | throw IOException("Could not create directory at $location")
14 | }
15 | }
16 |
17 | fun removeFileExtension(fileName: String): String {
18 | val lastDotIndex = fileName.lastIndexOf('.')
19 | return if (lastDotIndex != -1 && lastDotIndex > 0) {
20 | fileName.substring(0, lastDotIndex)
21 | } else {
22 | fileName
23 | }
24 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/utilities/KVS.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.utilities
2 |
3 | import java.util.concurrent.ConcurrentHashMap
4 |
5 |
6 | /**
7 | * Key-value store interface used by eventsFile
8 | */
9 | interface KVS {
10 | @Deprecated("Deprecated in favor of `get`", ReplaceWith("get(key, defaultVal)"))
11 | fun getInt(key: String, defaultVal: Int): Int = get(key, defaultVal)
12 | @Deprecated("Deprecated in favor of `put`", ReplaceWith("put(key, value)"))
13 | fun putInt(key: String, value: Int): Boolean = put(key, value)
14 |
15 | /**
16 | * Read the value of a given key as integer
17 | * @param key Key
18 | * @param defaultVal Fallback value to use
19 | * @return Value
20 | */
21 | fun get(key: String, defaultVal: Int): Int
22 |
23 | /**
24 | * Store the key value pair
25 | * @param key Key
26 | * @param value Fallback value to use
27 | * @return Status of the operation
28 | */
29 | fun put(key: String, value: Int): Boolean
30 |
31 | /**
32 | * Read the value of a given key as integer
33 | * @param key Key
34 | * @param defaultVal Fallback value to use
35 | * @return Value
36 | */
37 | fun get(key: String, defaultVal: String?): String?
38 |
39 | /**
40 | * Store the key value pair
41 | * @param key Key
42 | * @param value Fallback value to use
43 | * @return Status of the operation
44 | */
45 | fun put(key: String, value: String): Boolean
46 |
47 | /**
48 | * Remove a key/value pair by key
49 | *
50 | * @param key Key
51 | * @return Status of the operation
52 | */
53 | fun remove(key: String): Boolean
54 |
55 | /**
56 | * checks if a given key exists
57 | *
58 | * @param Key
59 | * @return Status of the operation
60 | */
61 | fun contains(key: String): Boolean
62 | }
63 |
64 | class InMemoryPrefs: KVS {
65 |
66 | private val cache = ConcurrentHashMap()
67 | override fun get(key: String, defaultVal: Int): Int {
68 | return if (cache[key] is Int) cache[key] as Int else defaultVal
69 | }
70 |
71 | override fun get(key: String, defaultVal: String?): String? {
72 | return if (cache[key] is String) cache[key] as String else defaultVal
73 | }
74 |
75 | override fun put(key: String, value: Int): Boolean {
76 | cache[key] = value
77 | return true
78 | }
79 |
80 | override fun put(key: String, value: String): Boolean {
81 | cache[key] = value
82 | return true
83 | }
84 |
85 | override fun remove(key: String): Boolean {
86 | cache.remove(key)
87 | return true
88 | }
89 |
90 | override fun contains(key: String) = cache.containsKey(key)
91 |
92 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/segment/analytics/kotlin/core/utilities/PropertiesFile.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.utilities
2 |
3 | import java.io.File
4 | import java.io.FileInputStream
5 | import java.io.FileOutputStream
6 | import java.util.Properties
7 |
8 | /**
9 | * A key-value storage built on top of {@link java.util.Properties}
10 | * conforming to {@link com.segment.analytics.kotlin.core.utilities.KVS} interface.
11 | * Ideal for use on JVM systems to store k-v pairs on a file.
12 | */
13 | class PropertiesFile(val file: File) : KVS {
14 | private val properties: Properties = Properties()
15 |
16 | init {
17 | load()
18 | }
19 |
20 | /**
21 | * Check if underlying file exists, and load properties if true
22 | */
23 | fun load() {
24 | if (file.exists()) {
25 | FileInputStream(file).use {
26 | properties.load(it)
27 | }
28 | }
29 | else {
30 | file.parentFile.mkdirs()
31 | file.createNewFile()
32 | }
33 | }
34 |
35 | fun save() {
36 | FileOutputStream(file).use {
37 | properties.store(it, null)
38 | }
39 | }
40 |
41 | override fun get(key: String, defaultVal: Int): Int {
42 | return properties.getProperty(key, "").toIntOrNull() ?: defaultVal
43 | }
44 |
45 | override fun get(key: String, defaultVal: String?): String? {
46 | return properties.getProperty(key, defaultVal)
47 | }
48 |
49 | override fun put(key: String, value: Int): Boolean {
50 | properties.setProperty(key, value.toString())
51 | save()
52 | return true
53 | }
54 |
55 | override fun put(key: String, value: String): Boolean {
56 | properties.setProperty(key, value)
57 | save()
58 | return true
59 | }
60 |
61 | fun putString(key: String, value: String): Boolean {
62 | properties.setProperty(key, value)
63 | save()
64 | return true
65 | }
66 |
67 | fun getString(key: String, defaultVal: String?): String? =
68 | properties.getProperty(key, defaultVal)
69 |
70 | override fun remove(key: String): Boolean {
71 | properties.remove(key)
72 | save()
73 | return true
74 | }
75 |
76 | override fun contains(key: String) = properties.containsKey(key)
77 | }
78 |
--------------------------------------------------------------------------------
/core/src/test/kotlin/com/segment/analytics/kotlin/core/ErrorsTest.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core
2 |
3 | import com.segment.analytics.kotlin.core.utils.clearPersistentStorage
4 | import com.segment.analytics.kotlin.core.utils.mockHTTPClient
5 | import com.segment.analytics.kotlin.core.utils.testAnalytics
6 | import io.mockk.every
7 | import io.mockk.mockk
8 | import io.mockk.spyk
9 | import io.mockk.verify
10 | import kotlinx.coroutines.test.TestScope
11 | import kotlinx.coroutines.test.UnconfinedTestDispatcher
12 | import org.junit.jupiter.api.BeforeEach
13 | import org.junit.jupiter.api.Test
14 | import org.junit.jupiter.api.TestInstance
15 |
16 | @TestInstance(TestInstance.Lifecycle.PER_CLASS)
17 | class ErrorsTest {
18 | private lateinit var analytics: Analytics
19 | private val testDispatcher = UnconfinedTestDispatcher()
20 | private val testScope = TestScope(testDispatcher)
21 | private val errorHandler = spyk()
22 |
23 | init {
24 | Telemetry.enable = false
25 | }
26 |
27 | @BeforeEach
28 | fun setup() {
29 | clearPersistentStorage()
30 | mockHTTPClient()
31 | val config = Configuration(
32 | writeKey = "123",
33 | application = "Test",
34 | errorHandler = errorHandler,
35 | )
36 |
37 | analytics = testAnalytics(config, testScope, testDispatcher)
38 | analytics.configuration.autoAddSegmentDestination = false
39 | }
40 |
41 | @Test
42 | fun `custom errorHandler handles error`() {
43 | val error = Exception()
44 | every { anyConstructed().upload(any()) } throws error
45 |
46 | analytics.track("test")
47 | analytics.flush()
48 |
49 | verify { errorHandler.invoke(error) }
50 | }
51 | }
--------------------------------------------------------------------------------
/core/src/test/kotlin/com/segment/analytics/kotlin/core/PluginTests.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core
2 |
3 | import com.segment.analytics.kotlin.core.platform.Plugin
4 | import com.segment.analytics.kotlin.core.platform.Timeline
5 | import com.segment.analytics.kotlin.core.utils.mockAnalytics
6 | import io.mockk.spyk
7 | import io.mockk.verify
8 | import kotlinx.coroutines.test.UnconfinedTestDispatcher
9 | import kotlinx.coroutines.test.TestScope
10 | import kotlinx.serialization.json.buildJsonObject
11 | import kotlinx.serialization.json.put
12 | import org.junit.jupiter.api.Assertions
13 | import org.junit.jupiter.api.Test
14 | import org.junit.jupiter.api.TestInstance
15 | import org.junit.jupiter.api.assertDoesNotThrow
16 | import java.util.*
17 |
18 | @TestInstance(TestInstance.Lifecycle.PER_CLASS)
19 | class PluginTests {
20 |
21 | private val testDispatcher = UnconfinedTestDispatcher()
22 | private val testScope = TestScope(testDispatcher)
23 | private val mockAnalytics = mockAnalytics(testScope, testDispatcher)
24 | private val timeline: Timeline
25 |
26 | init {
27 | Telemetry.enable = false
28 | timeline = Timeline().also { it.analytics = mockAnalytics }
29 | }
30 |
31 | @Test
32 | fun `setup is called`() {
33 | val plugin = object : Plugin {
34 | override val type: Plugin.Type = Plugin.Type.Before
35 | override lateinit var analytics: Analytics
36 | }
37 | val spy = spyk(plugin)
38 | timeline.add(spy)
39 | verify(exactly = 1) { spy.setup(mockAnalytics) }
40 | Assertions.assertTrue(spy.analytics === mockAnalytics)
41 | }
42 |
43 | @Test
44 | fun `plugin processes the event correctly`() {
45 | val plugin = spyk(object : Plugin {
46 | override val type: Plugin.Type = Plugin.Type.Before
47 | override lateinit var analytics: Analytics
48 | })
49 | timeline.add(plugin)
50 | val trackEvent = TrackEvent(
51 | event = "clicked",
52 | properties = buildJsonObject { put("behaviour", "good") })
53 | .apply {
54 | messageId = "qwerty-1234"
55 | anonymousId = "anonId"
56 | integrations = emptyJsonObject
57 | context = emptyJsonObject
58 | timestamp = Date(0).toInstant().toString()
59 | }
60 | val result = timeline.process(trackEvent)
61 | Assertions.assertEquals(trackEvent, result)
62 | verify(exactly = 1) { plugin.execute(trackEvent) }
63 | }
64 |
65 | @Test
66 | fun `throwing exception in setup() is handled in timeline`() {
67 | val plugin = object : Plugin {
68 | override val type: Plugin.Type = Plugin.Type.Before
69 | override lateinit var analytics: Analytics
70 | override fun setup(analytics: Analytics) {
71 | super.setup(analytics)
72 | throw Exception("Boom!")
73 | }
74 | }
75 | val spy = spyk(plugin)
76 | assertDoesNotThrow {
77 | timeline.add(spy)
78 | }
79 | verify(exactly = 1) { spy.setup(mockAnalytics) }
80 | Assertions.assertTrue(spy.analytics === mockAnalytics)
81 | }
82 | }
--------------------------------------------------------------------------------
/core/src/test/kotlin/com/segment/analytics/kotlin/core/platform/plugins/SegmentLogTest.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.platform.plugins
2 |
3 | import com.segment.analytics.kotlin.core.Analytics
4 | import com.segment.analytics.kotlin.core.platform.plugins.logger.*
5 | import org.junit.jupiter.api.Assertions.*
6 |
7 | import org.junit.jupiter.api.Test
8 | import java.util.concurrent.atomic.AtomicBoolean
9 |
10 | internal class SegmentLogTest {
11 |
12 | @Test
13 | fun `can call segmentLog() `() {
14 | val parseLogCalled = AtomicBoolean(false)
15 | val testLogger = object : Logger {
16 | override fun parseLog(log: LogMessage) {
17 | if (log.message.contains("test") && log.kind == LogKind.ERROR) {
18 | parseLogCalled.set(true)
19 | }
20 | }
21 | }
22 | Analytics.logger = testLogger
23 |
24 | Analytics.segmentLog("test")
25 |
26 | assertTrue(parseLogCalled.get())
27 | }
28 |
29 | @Test
30 | fun `can call segmentLog() with different log filter kind`() {
31 | val parseLogErrorCalled = AtomicBoolean(false)
32 | val parseLogWarnCalled = AtomicBoolean(false)
33 | val parseLogDebugCalled = AtomicBoolean(false)
34 |
35 | val testLogger = object: Logger {
36 | override fun parseLog(log: LogMessage) {
37 |
38 | if (log.message.contains("test")) {
39 | when (log.kind) {
40 | LogKind.ERROR -> {
41 | parseLogErrorCalled.set(true)
42 | }
43 | LogKind.WARNING -> {
44 | parseLogWarnCalled.set(true)
45 | }
46 | LogKind.DEBUG -> {
47 | parseLogDebugCalled.set(true)
48 | }
49 | }
50 | }
51 | }
52 | }
53 |
54 | Analytics.logger = testLogger
55 | Analytics.debugLogsEnabled = true
56 |
57 | Analytics.segmentLog("test") // Default LogFilterKind is ERROR
58 | Analytics.segmentLog("test", kind = LogKind.WARNING)
59 | Analytics.segmentLog("test", kind = LogKind.DEBUG)
60 |
61 | assertTrue(parseLogErrorCalled.get())
62 | assertTrue(parseLogWarnCalled.get())
63 | assertTrue(parseLogDebugCalled.get())
64 | }
65 |
66 | @Test
67 | fun `debug logging respects debugLogsEnabled flag`() {
68 |
69 | var logSent = AtomicBoolean(false)
70 |
71 | val testLogger = object : Logger {
72 | override fun parseLog(log: LogMessage) {
73 | logSent.set(true)
74 | }
75 | }
76 |
77 | Analytics.logger = testLogger
78 |
79 | // Turn ON debug logs
80 | Analytics.debugLogsEnabled = true
81 | Analytics.segmentLog("test", kind = LogKind.DEBUG)
82 |
83 | assertTrue(logSent.get())
84 |
85 | // Turn OFF debug logs
86 | Analytics.debugLogsEnabled = false
87 | logSent.set(false)
88 |
89 | Analytics.segmentLog("test", kind = LogKind.DEBUG)
90 | assertFalse(logSent.get())
91 | }
92 | }
93 |
94 |
--------------------------------------------------------------------------------
/core/src/test/kotlin/com/segment/analytics/kotlin/core/platform/plugins/StartupQueueTest.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.platform.plugins
2 |
3 | import com.segment.analytics.kotlin.core.Analytics
4 | import com.segment.analytics.kotlin.core.Configuration
5 | import com.segment.analytics.kotlin.core.TrackEvent
6 | import com.segment.analytics.kotlin.core.utils.testAnalytics
7 | import io.mockk.every
8 | import io.mockk.spyk
9 | import kotlinx.coroutines.test.UnconfinedTestDispatcher
10 | import kotlinx.coroutines.test.TestScope
11 | import kotlinx.serialization.json.buildJsonObject
12 | import kotlinx.serialization.json.put
13 | import org.junit.jupiter.api.Assertions.*
14 | import org.junit.jupiter.api.BeforeEach
15 |
16 | import org.junit.jupiter.api.Test
17 |
18 | internal class StartupQueueTest {
19 |
20 | private lateinit var analytics: Analytics
21 |
22 | private val testDispatcher = UnconfinedTestDispatcher()
23 |
24 | private val testScope = TestScope(testDispatcher)
25 |
26 | @BeforeEach
27 | internal fun setUp() {
28 | val config = Configuration(
29 | writeKey = "123",
30 | application = "Test",
31 | autoAddSegmentDestination = false
32 | )
33 | analytics = testAnalytics(config, testScope, testDispatcher)
34 | }
35 |
36 | @Test
37 | fun `execute when startup queue not ready`() {
38 | val startupQueue = spyk(StartupQueue())
39 | every { startupQueue.setup(any()) } answers { startupQueue.analytics = analytics }
40 |
41 | analytics.add(startupQueue)
42 | val trackEvent = TrackEvent(
43 | event = "clicked",
44 | properties = buildJsonObject { put("behaviour", "good") })
45 | assertNull(startupQueue.execute(trackEvent))
46 | }
47 | }
--------------------------------------------------------------------------------
/core/src/test/kotlin/com/segment/analytics/kotlin/core/platform/policies/CountBasedFlushPolicyTests.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.platform.policies
2 |
3 | import com.segment.analytics.kotlin.core.ScreenEvent
4 | import com.segment.analytics.kotlin.core.emptyJsonObject
5 | import org.junit.jupiter.api.Assertions.*
6 | import org.junit.jupiter.api.Test
7 | import org.junit.jupiter.api.TestInstance
8 |
9 | @TestInstance(TestInstance.Lifecycle.PER_CLASS)
10 | class CountBasedFlushPolicyTests {
11 |
12 | @Test
13 | fun `Policy defaults to 20 events`() {
14 | assertEquals(20, CountBasedFlushPolicy().flushAt)
15 | }
16 |
17 | @Test
18 | fun `Policy respects flushAt constructor arg`() {
19 | assertEquals(20, CountBasedFlushPolicy().flushAt) // default
20 | assertEquals(30, CountBasedFlushPolicy(30).flushAt)
21 | assertEquals(1, CountBasedFlushPolicy(1).flushAt)
22 | assertEquals(100, CountBasedFlushPolicy(100).flushAt)
23 | }
24 |
25 | @Test
26 | fun `Policy flushes at appropriate time`() {
27 | val flushAt = 10
28 | val defaultPolicy = CountBasedFlushPolicy(flushAt) // default count based policy with a 20 event flushAt value.
29 | assertFalse(defaultPolicy.shouldFlush()) // Should NOT flush before any events
30 |
31 | // all the first 19 events should not cause the policy to be flushed
32 | for( i in 1 until flushAt) {
33 | defaultPolicy.updateState(ScreenEvent("event 1", "", emptyJsonObject))
34 | assertFalse(defaultPolicy.shouldFlush())
35 | }
36 |
37 | // next event should trigger the flush
38 | defaultPolicy.updateState(ScreenEvent("event 1", "", emptyJsonObject))
39 | assertTrue(defaultPolicy.shouldFlush())
40 |
41 | // Even if we somehow go over the flushAt event limit, the policy should still want to flush
42 | // events
43 | defaultPolicy.updateState(ScreenEvent("event 1", "", emptyJsonObject))
44 | assertTrue(defaultPolicy.shouldFlush())
45 |
46 | // Only when we reset the policy will it not want to flush
47 | defaultPolicy.reset()
48 | assertFalse(defaultPolicy.shouldFlush())
49 |
50 | // The policy will then be ready to count another N events
51 | for( i in 1 until flushAt) {
52 | defaultPolicy.updateState(ScreenEvent("event 1", "", emptyJsonObject))
53 | assertFalse(defaultPolicy.shouldFlush())
54 | }
55 |
56 | // but once again the next event will trigger a flush request
57 | defaultPolicy.updateState(ScreenEvent("event 1", "", emptyJsonObject))
58 | assertTrue(defaultPolicy.shouldFlush())
59 | }
60 |
61 | @Test
62 | fun `Policy constructor param flushAt ignored for values less than 1`() {
63 | assertEquals(1, CountBasedFlushPolicy(1).flushAt) // Lowest flushAt that is allowed
64 | assertEquals(20, CountBasedFlushPolicy(0).flushAt)
65 | assertEquals(20, CountBasedFlushPolicy(-1).flushAt)
66 | assertEquals(20, CountBasedFlushPolicy(-20).flushAt)
67 | assertEquals(20, CountBasedFlushPolicy(-1000).flushAt)
68 | assertEquals(20, CountBasedFlushPolicy(-2439872).flushAt)
69 | }
70 | }
--------------------------------------------------------------------------------
/core/src/test/kotlin/com/segment/analytics/kotlin/core/platform/policies/FrequencyFlushPolicyTests.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.platform.policies
2 |
3 | import com.segment.analytics.kotlin.core.utils.mockAnalytics
4 | import io.mockk.coVerify
5 | import io.mockk.verify
6 | import kotlinx.coroutines.delay
7 | import kotlinx.coroutines.launch
8 | import kotlinx.coroutines.runBlocking
9 | import kotlinx.coroutines.test.TestScope
10 | import kotlinx.coroutines.test.UnconfinedTestDispatcher
11 | import kotlinx.coroutines.test.advanceTimeBy
12 | import kotlinx.coroutines.test.runTest
13 | import org.junit.jupiter.api.Assertions.assertFalse
14 | import org.junit.jupiter.api.Test
15 | import org.junit.jupiter.api.TestInstance
16 |
17 | @TestInstance(TestInstance.Lifecycle.PER_CLASS)
18 | class FrequencyFlushPolicyTests {
19 |
20 | private val testDispatcher = UnconfinedTestDispatcher()
21 | private val testScope = TestScope(testDispatcher)
22 | private val mockAnalytics = mockAnalytics(testScope, testDispatcher)
23 |
24 | @Test
25 | fun `Policy should not ever request flush`() {
26 | val frequencyFlushPolicy = FrequencyFlushPolicy()
27 |
28 | // Should not flush at first
29 | assertFalse(frequencyFlushPolicy.shouldFlush())
30 |
31 | // Start the policy's scheduler
32 | frequencyFlushPolicy.schedule(analytics = mockAnalytics)
33 |
34 | // Should still not flush
35 | assertFalse(frequencyFlushPolicy.shouldFlush())
36 |
37 | // Stop the scheduler
38 | frequencyFlushPolicy.unschedule()
39 |
40 | // Should still not flush
41 | assertFalse(frequencyFlushPolicy.shouldFlush())
42 |
43 | }
44 |
45 |
46 | @Test
47 | fun `Policy should flush when first scheduled`() = runTest {
48 |
49 | val frequencyFlushPolicy = FrequencyFlushPolicy()
50 |
51 | // Start the scheduler
52 | frequencyFlushPolicy.schedule(mockAnalytics)
53 |
54 | // Make sure flush is called just once
55 | coVerify(exactly = 1) {
56 | mockAnalytics.flush()
57 | }
58 | }
59 |
60 |
61 | @Test
62 | fun `Policy should flush after each flush interval`() = runTest {
63 |
64 | val frequencyFlushPolicy = FrequencyFlushPolicy(1_000)
65 |
66 | frequencyFlushPolicy.schedule(mockAnalytics)
67 |
68 | delay(2_500)
69 |
70 | // Make sure flush is called just once
71 | coVerify(atLeast = 1, atMost = 2) {
72 | mockAnalytics.flush()
73 | }
74 |
75 | }
76 | }
--------------------------------------------------------------------------------
/core/src/test/kotlin/com/segment/analytics/kotlin/core/platform/policies/StartupFlushPolicyTests.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.platform.policies
2 |
3 | import com.segment.analytics.kotlin.core.ScreenEvent
4 | import com.segment.analytics.kotlin.core.emptyJsonObject
5 | import org.junit.jupiter.api.Assertions.*
6 | import org.junit.jupiter.api.Test
7 | import org.junit.jupiter.api.TestInstance
8 |
9 | @TestInstance(TestInstance.Lifecycle.PER_CLASS)
10 | class StartupFlushPolicyTests {
11 |
12 | @Test
13 | fun `Policy flushes only first call to shouldFlush`() {
14 |
15 | val startupFlushPolicy = StartupFlushPolicy()
16 |
17 | // Should only flush the first time requested!
18 | assertTrue(startupFlushPolicy.shouldFlush())
19 |
20 | // Should now not flush any more!
21 | assertFalse(startupFlushPolicy.shouldFlush())
22 |
23 | // No matter how many times you call shouldFlush()
24 | for (i in 1..10) {
25 | assertFalse(startupFlushPolicy.shouldFlush())
26 | }
27 |
28 | // even you call reset; the policy will not want to flush.
29 | startupFlushPolicy.reset()
30 | assertFalse(startupFlushPolicy.shouldFlush())
31 |
32 | // Adding events has no effect and does not cause the policy to flush
33 | startupFlushPolicy.updateState(ScreenEvent("event 1", "", emptyJsonObject))
34 | assertFalse(startupFlushPolicy.shouldFlush())
35 | }
36 | }
--------------------------------------------------------------------------------
/core/src/test/kotlin/com/segment/analytics/kotlin/core/utilities/Base64UtilsTest.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.utilities
2 |
3 | import org.junit.jupiter.api.Assertions.assertEquals
4 | import org.junit.jupiter.api.Test
5 | import org.junit.jupiter.api.TestInstance
6 |
7 | @TestInstance(TestInstance.Lifecycle.PER_CLASS)
8 | class Base64UtilsTest {
9 |
10 | @Test
11 | fun testBase64Encoding() {
12 | assertEquals(encodeToBase64(""), "")
13 | assertEquals(encodeToBase64("f"), "Zg==")
14 | assertEquals(encodeToBase64("fo"), "Zm8=")
15 | assertEquals(encodeToBase64("foo"), "Zm9v")
16 | assertEquals(encodeToBase64("foob"), "Zm9vYg==")
17 | assertEquals(encodeToBase64("fooba"), "Zm9vYmE=")
18 | assertEquals(encodeToBase64("foobar"), "Zm9vYmFy")
19 | }
20 | }
--------------------------------------------------------------------------------
/core/src/test/kotlin/com/segment/analytics/kotlin/core/utilities/DateTimeUtilsTest.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.utilities
2 |
3 | import io.mockk.every
4 | import io.mockk.mockkConstructor
5 | import io.mockk.mockkObject
6 | import org.junit.jupiter.api.Assertions.assertEquals
7 | import org.junit.jupiter.api.Assertions.assertNotNull
8 | import org.junit.jupiter.api.Test
9 | import java.time.format.DateTimeFormatter.ISO_DATE_TIME
10 | import java.util.*
11 |
12 | class DateTimeUtilsTest {
13 |
14 | @Test
15 | fun `dateTimeNowString() produces a string in the correct ISO8601 format`() {
16 | val dateTimeNowString = SegmentInstant.now()
17 | val date = ISO_DATE_TIME.parse(dateTimeNowString)
18 | assertNotNull(date)
19 | }
20 |
21 | @Test
22 | fun `dateTimeNowString() returns three digit seconds`() {
23 | val date = Date(1700617928023L)
24 | val dateTimeNowString = SegmentInstant.from(date)
25 | assertEquals("2023-11-22T01:52:08.023Z", dateTimeNowString)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/core/src/test/kotlin/com/segment/analytics/kotlin/core/utilities/KVSTest.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.utilities
2 |
3 | import org.junit.jupiter.api.Assertions.*
4 | import org.junit.jupiter.api.BeforeEach
5 | import org.junit.jupiter.api.Nested
6 | import org.junit.jupiter.api.Test
7 |
8 | class KVSTest {
9 | @Nested
10 | inner class InMemoryPrefsTest {
11 | private lateinit var prefs: KVS
12 |
13 | @BeforeEach
14 | fun setup(){
15 | prefs = InMemoryPrefs()
16 | prefs.put("int", 1)
17 | prefs.put("string", "string")
18 | }
19 |
20 | @Test
21 | fun getTest() {
22 | assertEquals(1, prefs.get("int", 0))
23 | assertEquals("string", prefs.get("string", null))
24 | assertEquals(0, prefs.get("keyNotExists", 0))
25 | assertEquals(null, prefs.get("keyNotExists", null))
26 | }
27 |
28 | @Test
29 | fun putTest() {
30 | prefs.put("int", 2)
31 | prefs.put("string", "stringstring")
32 |
33 | assertEquals(2, prefs.get("int", 0))
34 | assertEquals("stringstring", prefs.get("string", null))
35 | }
36 |
37 | @Test
38 | fun containsAndRemoveTest() {
39 | assertTrue(prefs.contains("int"))
40 | prefs.remove("int")
41 | assertFalse(prefs.contains("int"))
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/core/src/test/kotlin/com/segment/analytics/kotlin/core/utilities/PropertiesFileTest.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.utilities
2 |
3 |
4 | import org.junit.jupiter.api.Assertions.assertEquals
5 | import org.junit.jupiter.api.BeforeEach
6 | import org.junit.jupiter.api.Test
7 | import java.io.File
8 |
9 | internal class PropertiesFileTest {
10 |
11 | private val directory = File("/tmp/analytics-test/123")
12 | private val kvStore = PropertiesFile(File(directory, "123"))
13 |
14 | @BeforeEach
15 | internal fun setUp() {
16 | directory.deleteRecursively()
17 | kvStore.load()
18 | }
19 |
20 | @Test
21 | fun `test properties file operations`() {
22 | kvStore.putString("string", "test")
23 | kvStore.putInt("int", 1)
24 |
25 | assertEquals(kvStore.get("string", ""), "test")
26 | assertEquals(kvStore.get("int", 0), 1)
27 |
28 | kvStore.remove("int")
29 | assertEquals(kvStore.get("int", 0), 0)
30 | }
31 | }
--------------------------------------------------------------------------------
/core/src/test/kotlin/com/segment/analytics/kotlin/core/utils/Mocks.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.utils
2 |
3 | import com.segment.analytics.kotlin.core.*
4 | import io.mockk.*
5 | import kotlinx.coroutines.CoroutineDispatcher
6 | import kotlinx.coroutines.CoroutineScope
7 | import kotlinx.coroutines.test.*
8 | import sovran.kotlin.Store
9 | import java.io.ByteArrayInputStream
10 | import java.io.File
11 | import java.net.HttpURLConnection
12 | import kotlin.coroutines.CoroutineContext
13 |
14 | /**
15 | * Retrieve a relaxed mock of analytics, that can be used while testing plugins
16 | * Current capabilities:
17 | * - In-memory sovran.store
18 | */
19 | fun mockAnalytics(testScope: TestScope, testDispatcher: TestDispatcher): Analytics {
20 | val mock = mockk(relaxed = true)
21 | val mockStore = spyStore(testScope, testDispatcher)
22 | every { mock.store } returns mockStore
23 | every { mock.analyticsScope } returns testScope
24 | every { mock.fileIODispatcher } returns testDispatcher
25 | every { mock.networkIODispatcher } returns testDispatcher
26 | every { mock.analyticsDispatcher } returns testDispatcher
27 | return mock
28 | }
29 |
30 | fun testAnalytics(configuration: Configuration, testScope: TestScope, testDispatcher: TestDispatcher): Analytics {
31 | return object : Analytics(configuration, TestCoroutineConfiguration(testScope, testDispatcher)) {}
32 | }
33 |
34 | fun clearPersistentStorage(writeKey: String = "123") {
35 | File("/tmp/analytics-kotlin/$writeKey").deleteRecursively()
36 | }
37 |
38 | fun spyStore(scope: TestScope, dispatcher: TestDispatcher): Store {
39 | val store = spyk(Store())
40 | every { store getProperty "sovranScope" } propertyType CoroutineScope::class returns scope
41 | every { store getProperty "syncQueue" } propertyType CoroutineContext::class returns dispatcher
42 | every { store getProperty "updateQueue" } propertyType CoroutineContext::class returns dispatcher
43 | return store
44 | }
45 | val settingsDefault = """
46 | {"integrations":{"Segment.io":{"apiKey":"1vNgUqwJeCHmqgI9S1sOm9UHCyfYqbaQ"}},"plan":{},"edgeFunction":{}}
47 | """.trimIndent()
48 |
49 | fun mockHTTPClient(settings: String = settingsDefault) {
50 | mockkConstructor(HTTPClient::class)
51 | val settingsStream = ByteArrayInputStream(
52 | settings.toByteArray()
53 | )
54 | val httpConnection: HttpURLConnection = mockk()
55 | val connection = object : Connection(httpConnection, settingsStream, null) {}
56 | every { anyConstructed().settings("cdn-settings.segment.com/v1") } returns connection
57 | }
58 |
59 | class TestCoroutineConfiguration(
60 | val testScope: TestScope,
61 | val testDispatcher: TestDispatcher
62 | ) : CoroutineConfiguration {
63 |
64 | override val store: Store = spyStore(testScope, testDispatcher)
65 |
66 | override val analyticsScope: CoroutineScope
67 | get() = testScope
68 |
69 | override val analyticsDispatcher: CoroutineDispatcher
70 | get() = testDispatcher
71 |
72 | override val networkIODispatcher: CoroutineDispatcher
73 | get() = testDispatcher
74 |
75 | override val fileIODispatcher: CoroutineDispatcher
76 | get() = testDispatcher
77 | }
--------------------------------------------------------------------------------
/core/src/test/kotlin/com/segment/analytics/kotlin/core/utils/Plugins.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.core.utils
2 |
3 | import com.segment.analytics.kotlin.core.*
4 | import com.segment.analytics.kotlin.core.platform.*
5 |
6 | /**
7 | * An analytics plugin that can be used to test features
8 | * Current capabilities
9 | * - is a `Before` plugin so is guaranteed to be run
10 | * - can add a closure that will be run if any hook is executed
11 | * - has a boolean state `ran` that can be used to verify if a particular hook was executed
12 | * - has a `reset()` function to reset state between tests
13 | */
14 | class TestRunPlugin(var closure: (BaseEvent?) -> Unit): EventPlugin {
15 | override val type: Plugin.Type = Plugin.Type.Before
16 | override lateinit var analytics: Analytics
17 | var ran = false
18 |
19 | override fun reset() {
20 | ran = false
21 | }
22 |
23 | fun updateState(ran: Boolean) {
24 | this.ran = ran
25 | }
26 |
27 | override fun execute(event: BaseEvent): BaseEvent {
28 | super.execute(event)
29 | updateState(true)
30 | return event
31 | }
32 |
33 | override fun track(payload: TrackEvent): BaseEvent {
34 | closure(payload)
35 | updateState(true)
36 | return payload
37 | }
38 |
39 | override fun identify(payload: IdentifyEvent): BaseEvent {
40 | closure(payload)
41 | updateState(true)
42 | return payload
43 | }
44 |
45 | override fun screen(payload: ScreenEvent): BaseEvent {
46 | closure(payload)
47 | updateState(true)
48 | return payload
49 | }
50 |
51 | override fun group(payload: GroupEvent): BaseEvent {
52 | closure(payload)
53 | updateState(true)
54 | return payload
55 | }
56 |
57 | override fun alias(payload: AliasEvent): BaseEvent {
58 | closure(payload)
59 | updateState(true)
60 | return payload
61 | }
62 | }
63 |
64 | /**
65 | * An analytics plugin that is a simple pass-through plugin. Ideally to be used to verify
66 | * if particular hooks are run via mockk's `verify`
67 | */
68 | open class StubPlugin : EventPlugin {
69 | override val type: Plugin.Type = Plugin.Type.Before
70 | override lateinit var analytics: Analytics
71 | }
72 |
73 | open class StubAfterPlugin : EventPlugin {
74 | override val type: Plugin.Type = Plugin.Type.After
75 | override lateinit var analytics: Analytics
76 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 |
23 | # Deployment variables
24 | GROUP=com.segment.analytics.kotlin
25 |
26 | VERSION_CODE=1192
27 | VERSION_NAME=1.19.2
28 |
29 | POM_NAME=Segment for Kotlin
30 | POM_DESCRIPTION=The hassle-free way to add analytics to your Kotlin app.
31 |
32 | POM_URL=http://github.com/segmentio/analytics-kotlin
33 | POM_SCM_URL=http://github.com/segmentio/analytics-kotlin
34 | POM_SCM_CONNECTION=scm:git:git://github.com/segmentio/analytics-kotlin.git
35 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/segmentio/analytics-kotlin.git
36 |
37 | POM_LICENCE_NAME=The MIT License (MIT)
38 | POM_LICENCE_URL=http://opensource.org/licenses/MIT
39 | POM_LICENCE_DIST=repo
40 |
41 | POM_DEVELOPER_ID=segmentio
42 | POM_DEVELOPER_NAME=Segment, Inc.
43 |
--------------------------------------------------------------------------------
/gradle/artifacts-android.gradle:
--------------------------------------------------------------------------------
1 | task sourcesJar(type: Jar) {
2 | archiveClassifier.set('sources')
3 | from android.sourceSets.main.java.srcDirs
4 | }
5 |
6 | task javadoc(type: Javadoc) {
7 | configurations.implementation.setCanBeResolved(true)
8 |
9 | failOnError false
10 | source = android.sourceSets.main.java.sourceFiles
11 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
12 | classpath += configurations.implementation
13 | }
14 |
15 | // build a jar with javadoc
16 | task javadocJar(type: Jar, dependsOn: javadoc) {
17 | archiveClassifier.set('javadoc')
18 | from javadoc.destinationDir
19 | }
20 |
21 | // Attach Javadocs and Sources jar
22 | artifacts {
23 | archives sourcesJar
24 | archives javadocJar
25 | }
--------------------------------------------------------------------------------
/gradle/artifacts-core.gradle:
--------------------------------------------------------------------------------
1 | task sourcesJar(type: Jar, dependsOn: classes) {
2 | archiveClassifier.set('sources')
3 | from sourceSets.main.allSource
4 | }
5 |
6 | task javadocJar(type: Jar, dependsOn: javadoc) {
7 | archiveClassifier.set('javadoc')
8 | from javadoc.destinationDir
9 | }
10 |
11 | // Attach Javadocs and Sources jar
12 | artifacts {
13 | archives sourcesJar
14 | archives javadocJar
15 | }
--------------------------------------------------------------------------------
/gradle/codecov.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'jacoco'
2 |
3 | jacoco {
4 | toolVersion = "0.8.7"
5 | }
6 |
7 | task codeCoverageReport(type: JacocoReport) {
8 | def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', "**/**/*serializer*.*", "**/**/*Companion*.*" ]
9 | def mainSrc = []
10 | def debugTrees = []
11 | def execData = []
12 | if(project.name == rootProject.name) {
13 | subprojects.each {
14 | mainSrc.add("${it.projectDir}/src/main/java")
15 | debugTrees.add(fileTree(dir: "${it.buildDir}/classes", excludes: fileFilter))
16 | debugTrees.add(fileTree(dir: "${it.buildDir}/tmp/kotlin-classes/debugUnitTest", excludes: fileFilter))
17 | execData.add(fileTree(dir: "${it.buildDir}/jacoco", includes: ["*.exec"]))
18 | }
19 | }
20 | else {
21 | mainSrc.add("${project.projectDir}/src/main/java")
22 | debugTrees.add(fileTree(dir: "${project.buildDir}/classes", excludes: fileFilter))
23 | debugTrees.add(fileTree(dir: "${project.buildDir}/tmp/kotlin-classes/debugUnitTest", excludes: fileFilter))
24 | execData.add(fileTree(dir: "${project.buildDir}/jacoco", includes: ["*.exec"]))
25 | }
26 |
27 | sourceDirectories.setFrom(files(mainSrc))
28 | classDirectories.setFrom(files(debugTrees))
29 | executionData.setFrom(execData)
30 |
31 | reports {
32 | xml.enabled true
33 | xml.destination file("${buildDir}/reports/jacoco/report.xml")
34 | html.enabled true
35 | csv.enabled false
36 | }
37 | }
--------------------------------------------------------------------------------
/gradle/promote.gradle:
--------------------------------------------------------------------------------
1 | apply from: rootProject.file('gradle/versioning.gradle')
2 |
3 | group GROUP
4 | version getVersionName()
5 |
6 | if (!hasProperty("signing.keyId")) {
7 | ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID')
8 | ext["signing.password"] = System.getenv('SIGNING_KEY_PASSWORD')
9 |
10 | def pgpKeyContent = System.getenv('SIGNING_PRIVATE_KEY_BASE64')
11 | if (pgpKeyContent != null) {
12 | def tmpDir = new File("$rootProject.rootDir/tmp")
13 | mkdir tmpDir
14 | def keyFile = new File("$tmpDir/key.pgp")
15 | keyFile.createNewFile()
16 | def os = keyFile.newDataOutputStream()
17 | os.write(pgpKeyContent.decodeBase64())
18 | os.close()
19 |
20 | ext['signing.secretKeyRingFile'] = keyFile.absolutePath
21 | }
22 | }
23 |
24 | nexusPublishing {
25 | repositories {
26 | /*
27 | nexus publish plugin by default looking for the following as credential:
28 | * sonatypeUsername and sonatypePassword in global gradle property or
29 | * ORG_GRADLE_PROJECT_sonatypeUsername and ORG_GRADLE_PROJECT_sonatypePassword
30 | in system environments
31 | be sure to set the variable names exactly as above.
32 | */
33 | sonatype()
34 | }
35 | }
--------------------------------------------------------------------------------
/gradle/versioning.gradle:
--------------------------------------------------------------------------------
1 | def isReleaseBuild() {
2 | return hasProperty('release')
3 | }
4 |
5 | def getVersionName() { // If not release build add SNAPSHOT suffix
6 | return isReleaseBuild() ? VERSION_NAME : VERSION_NAME+"-SNAPSHOT"
7 | }
8 |
9 | ext {
10 | isReleaseBuild = this.&isReleaseBuild
11 | getVersionName = this.&getVersionName
12 | }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/segmentio/analytics-kotlin/c2ac4eab1fffec304deeec8cbfe1693cd1a2ec94/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jan 22 15:05:55 PST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/samples/README.md:
--------------------------------------------------------------------------------
1 | # Sample Projects using analytics-kotlin
2 |
3 | - [Java Android Application](java-android-app)
4 | A sample project showcasing the usage of `analytics-kotlin` in a pure java codebase Android application
5 |
6 | - [Kotlin Android Application](kotlin-android-app)
7 | A sample project showcasing the usage of `analytics-kotlin` in a pure kotlin codebase Android application. Also contains sample plugins that are commonly used.
8 |
9 | - [Kotlin Android Application w/ Sample Destination Plugins](kotlin-android-app-destinations)
10 | A sample project showcasing the usage of `analytics-kotlin` in a pure kotlin codebase Android application. This project also hosts sample Destination plugins, and instructions on how to include them in your own projects
11 |
12 | - [Kotlin JVM Application](kotlin-jvm-app)
13 | A sample project showcasing the usage of `analytics-kotlin` in a pure kotlin codebase JVM application.
--------------------------------------------------------------------------------
/samples/java-android-app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/samples/java-android-app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | }
4 |
5 | android {
6 | compileSdkVersion 33
7 |
8 | defaultConfig {
9 | applicationId "com.segment.analytics.javaandroidapp"
10 | minSdkVersion 16
11 | targetSdkVersion 33
12 | versionCode 1
13 | versionName "1.0"
14 |
15 | buildConfigField "String", "SEGMENT_WRITE_KEY", "\"${writeKey}\""
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | }
19 |
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 | compileOptions {
27 | sourceCompatibility JavaVersion.VERSION_1_8
28 | targetCompatibility JavaVersion.VERSION_1_8
29 | }
30 | }
31 |
32 | dependencies {
33 | implementation project(':android')
34 |
35 | implementation 'androidx.appcompat:appcompat:1.4.0'
36 | implementation 'com.google.android.material:material:1.4.0'
37 | implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
38 | testImplementation 'junit:junit:4.13.2'
39 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
40 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
41 | }
--------------------------------------------------------------------------------
/samples/java-android-app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/samples/java-android-app/src/androidTest/java/com/segment/analytics/javaandroidapp/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.javaandroidapp;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 | assertEquals("com.segment.analytics.javaandroidapp", appContext.getPackageName());
25 | }
26 | }
--------------------------------------------------------------------------------
/samples/java-android-app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/samples/java-android-app/src/main/java/com/segment/analytics/javaandroidapp/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.javaandroidapp;
2 |
3 | import androidx.appcompat.app.AppCompatActivity;
4 |
5 | import android.os.Bundle;
6 |
7 | public class MainActivity extends AppCompatActivity {
8 |
9 | @Override
10 | protected void onCreate(Bundle savedInstanceState) {
11 | super.onCreate(savedInstanceState);
12 | setContentView(R.layout.activity_main);
13 | }
14 | }
--------------------------------------------------------------------------------
/samples/java-android-app/src/main/java/com/segment/analytics/javaandroidapp/MainApplication.java:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.javaandroidapp;
2 |
3 | import android.app.Application;
4 |
5 | import com.segment.analytics.kotlin.android.AndroidAnalyticsKt;
6 | import com.segment.analytics.kotlin.core.Analytics;
7 |
8 | import kotlin.Unit;
9 |
10 | public class MainApplication extends Application {
11 | private Analytics mainAnalytics;
12 |
13 | @Override
14 | public void onCreate() {
15 | super.onCreate();
16 | mainAnalytics = AndroidAnalyticsKt.Analytics(BuildConfig.SEGMENT_WRITE_KEY, getApplicationContext(), configuration -> {
17 | configuration.setFlushAt(1);
18 | return Unit.INSTANCE;
19 | });
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/samples/java-android-app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/samples/java-android-app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
--------------------------------------------------------------------------------
/samples/java-android-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/samples/java-android-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/samples/java-android-app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/segmentio/analytics-kotlin/c2ac4eab1fffec304deeec8cbfe1693cd1a2ec94/samples/java-android-app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/java-android-app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/segmentio/analytics-kotlin/c2ac4eab1fffec304deeec8cbfe1693cd1a2ec94/samples/java-android-app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/java-android-app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/segmentio/analytics-kotlin/c2ac4eab1fffec304deeec8cbfe1693cd1a2ec94/samples/java-android-app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/java-android-app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/segmentio/analytics-kotlin/c2ac4eab1fffec304deeec8cbfe1693cd1a2ec94/samples/java-android-app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/java-android-app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/segmentio/analytics-kotlin/c2ac4eab1fffec304deeec8cbfe1693cd1a2ec94/samples/java-android-app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/java-android-app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/segmentio/analytics-kotlin/c2ac4eab1fffec304deeec8cbfe1693cd1a2ec94/samples/java-android-app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/java-android-app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/segmentio/analytics-kotlin/c2ac4eab1fffec304deeec8cbfe1693cd1a2ec94/samples/java-android-app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/java-android-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/segmentio/analytics-kotlin/c2ac4eab1fffec304deeec8cbfe1693cd1a2ec94/samples/java-android-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/java-android-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/segmentio/analytics-kotlin/c2ac4eab1fffec304deeec8cbfe1693cd1a2ec94/samples/java-android-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/java-android-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/segmentio/analytics-kotlin/c2ac4eab1fffec304deeec8cbfe1693cd1a2ec94/samples/java-android-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/java-android-app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/samples/java-android-app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/samples/java-android-app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Java Android App
3 |
--------------------------------------------------------------------------------
/samples/java-android-app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/samples/java-android-app/src/test/java/com/segment/analytics/javaandroidapp/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.javaandroidapp;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/samples/kotlin-android-app-destinations/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/samples/kotlin-android-app-destinations/README.md:
--------------------------------------------------------------------------------
1 | # Sample App
2 | This is a sample android app that uses the `analytics-kotlin` library and the new `Plugins` concepts. It is meant to be simplistic, and easy to understand all the while showcasing the power of the analytics-kotlin library
3 |
4 | ## Plugins
5 | - [Amplitude Session](https://github.com/segment-integrations/analytics-kotlin-amplitude)
6 | Plugin to enable users to enrich data for the Amplitude Actions destination
7 |
8 | - [Appcues](https://github.com/appcues/segment-appcues-android)
9 | Plugin to enable users to send data to the device-mode Appcues destination
10 |
11 | - [AppsFlyer](https://github.com/segment-integrations/analytics-kotlin-appsflyer)
12 | Plugin to enable users to send data to the device-mode AppsFlyer destination
13 |
14 | - [Comscore](https://github.com/segment-integrations/analytics-kotlin-comscore)
15 | Plugin to enable users to send data to the device-mode Comscore destination
16 |
17 | - [Firebase](https://github.com/segment-integrations/analytics-kotlin-firebase)
18 | Plugin to enable users to send data to the device-mode Firebase destination
19 |
20 | - [Intercom](https://github.com/segment-integrations/analytics-kotlin-intercom)
21 | Plugin to enable users to send data to the device-mode Intercom destination
22 |
23 | - [Mixpanel](https://github.com/segment-integrations/analytics-kotlin-mixpanel)
24 | Plugin to enable users to send data to the device-mode Mixpanel destination
25 |
26 | - [Webhook Plugin](src/main/java/com/segment/analytics/kotlin/destinations/plugins/WebhookPlugin.kt)
27 | An after plugin that allows you to send the event from the analytics timeline to a webhook of your choice. Ideal for debugging payloads in an internal network.
28 |
--------------------------------------------------------------------------------
/samples/kotlin-android-app-destinations/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | id 'org.jetbrains.kotlin.plugin.serialization'
5 | }
6 |
7 | android {
8 | compileSdkVersion 33
9 |
10 | defaultConfig {
11 | multiDexEnabled true
12 | applicationId "com.segment.analytics.kotlin.destinations"
13 | minSdkVersion 21
14 | targetSdkVersion 33
15 | versionCode 3
16 | versionName "2.0"
17 |
18 | buildConfigField "String", "SEGMENT_WRITE_KEY", "\"${writeKey}\""
19 |
20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
21 | }
22 |
23 | buildTypes {
24 | release {
25 | minifyEnabled false
26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
27 | }
28 | }
29 | compileOptions {
30 | coreLibraryDesugaringEnabled true
31 | sourceCompatibility JavaVersion.VERSION_1_8
32 | targetCompatibility JavaVersion.VERSION_1_8
33 | }
34 | kotlinOptions {
35 | jvmTarget = '1.8'
36 | }
37 | }
38 |
39 | dependencies {
40 | coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
41 |
42 | implementation 'com.segment.analytics.kotlin:android:1.6.2'
43 | implementation 'androidx.multidex:multidex:2.0.1'
44 |
45 | implementation 'androidx.core:core-ktx:1.7.0'
46 | implementation 'androidx.appcompat:appcompat:1.4.0'
47 | implementation 'com.google.android.material:material:1.4.0'
48 | implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
49 |
50 | implementation 'androidx.lifecycle:lifecycle-process:2.4.0'
51 | implementation 'androidx.lifecycle:lifecycle-common-java8:2.4.0'
52 | }
53 |
54 | // Supported destinations
55 | dependencies {
56 | implementation 'com.segment.analytics.kotlin.destinations:amplitude:1.5.1'
57 | implementation 'com.segment.analytics.kotlin.destinations:appsflyer:1.5.1'
58 | implementation 'com.segment.analytics.kotlin.destinations:firebase:1.5.2'
59 | implementation 'com.segment.analytics.kotlin.destinations:mixpanel:1.5.2'
60 | implementation 'com.segment.analytics.kotlin.destinations:intercom:1.5.0'
61 | implementation 'com.segment.analytics.kotlin.destinations:comscore:1.5.0'
62 | }
63 |
64 | // Test Dependencies
65 | dependencies {
66 | testImplementation 'junit:junit:4.13.2'
67 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
68 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
69 | testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0'
70 |
71 | testImplementation 'io.mockk:mockk:1.10.6'
72 | testImplementation(platform("org.junit:junit-bom:5.7.2"))
73 | testImplementation("org.junit.jupiter:junit-jupiter")
74 |
75 | // Add Roboelectric dependencies.
76 | testImplementation 'org.robolectric:robolectric:4.7.3'
77 | testImplementation 'androidx.test:core:1.4.0'
78 |
79 | // Add JUnit4 legacy dependencies.
80 | testImplementation 'junit:junit:4.13.2'
81 | testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.7.2'
82 |
83 | // For JSON Object testing
84 | testImplementation 'org.json:json:20200518'
85 | testImplementation 'org.skyscreamer:jsonassert:1.5.0'
86 | }
87 |
88 | apply from: rootProject.file('gradle/codecov.gradle')
--------------------------------------------------------------------------------
/samples/kotlin-android-app-destinations/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "896388530950",
4 | "project_id": "codyfire-aec37",
5 | "storage_bucket": "codyfire-aec37.appspot.com"
6 | },
7 | "client": [
8 | {
9 | "client_info": {
10 | "mobilesdk_app_id": "1:896388530950:android:05388b0468bcc4fb9b1487",
11 | "android_client_info": {
12 | "package_name": "com.segment.analytics.destinations"
13 | }
14 | },
15 | "oauth_client": [
16 | {
17 | "client_id": "896388530950-athmfbk281ot242c49jg8l50phar0g09.apps.googleusercontent.com",
18 | "client_type": 3
19 | }
20 | ],
21 | "api_key": [
22 | {
23 | "current_key": "AIzaSyDrjBK2AfgX1ZL63ypw5vGUSfp1vBOx970"
24 | }
25 | ],
26 | "services": {
27 | "appinvite_service": {
28 | "other_platform_oauth_client": [
29 | {
30 | "client_id": "896388530950-athmfbk281ot242c49jg8l50phar0g09.apps.googleusercontent.com",
31 | "client_type": 3
32 | },
33 | {
34 | "client_id": "896388530950-bevk6oulirofbnc87iahub4vth7salm2.apps.googleusercontent.com",
35 | "client_type": 2,
36 | "ios_info": {
37 | "bundle_id": "com.segment.DestinationsExample"
38 | }
39 | }
40 | ]
41 | }
42 | }
43 | }
44 | ],
45 | "configuration_version": "1"
46 | }
--------------------------------------------------------------------------------
/samples/kotlin-android-app-destinations/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/samples/kotlin-android-app-destinations/src/androidTest/java/com/segment/analytics/next/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.next
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.segment.analytics.next", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/samples/kotlin-android-app-destinations/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
26 |
29 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/samples/kotlin-android-app-destinations/src/main/java/com/segment/analytics/kotlin/destinations/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.segment.analytics.kotlin.destinations
2 |
3 | import android.os.Bundle
4 | import android.widget.Button
5 | import androidx.appcompat.app.AppCompatActivity
6 | import androidx.fragment.app.Fragment
7 | import androidx.fragment.app.FragmentManager
8 | import androidx.fragment.app.FragmentTransaction
9 | import com.segment.analytics.kotlin.core.EventType
10 |
11 |
12 | class MainActivity : AppCompatActivity() {
13 |
14 | val analytics = MainApplication.analytics
15 | val trackFragment = EventFragment(EventType.Track, analytics)
16 | val identifyFragment = EventFragment(EventType.Identify, analytics)
17 | val screenFragment = EventFragment(EventType.Screen, analytics)
18 | val groupFragment = EventFragment(EventType.Group, analytics)
19 |
20 | override fun onCreate(savedInstanceState: Bundle?) {
21 | super.onCreate(savedInstanceState)
22 | setContentView(R.layout.activity_sample)
23 |
24 | findViewById