Skip to content

Commit 6c15da6

Browse files
authored
Add provider for KissMetrics (#20)
* Add provider for KissMetrics
1 parent b9de050 commit 6c15da6

File tree

14 files changed

+592
-8
lines changed

14 files changed

+592
-8
lines changed

.idea/runConfigurations/Unit_Tests___KissMetrics_Provider.xml

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ allprojects {
1515
In your module's build.gradle file, add the dependency:
1616
```groovy
1717
dependencies {
18-
compile 'com.github.busybusy.AnalyticsKit-Android:analyticskit:0.8.2'
18+
implementation "com.github.busybusy.AnalyticsKit-Android:analyticskit:0.8.2"
1919
...
2020
}
2121
```
@@ -24,7 +24,7 @@ You can include the implemented providers you want by adding them to the same de
2424
```groovy
2525
dependencies {
2626
...
27-
compile 'com.github.busybusy.AnalyticsKit-Android:mixpanel-provider:0.8.2'
27+
implementation "com.github.busybusy.AnalyticsKit-Android:mixpanel-provider:0.8.2"
2828
}
2929
```
3030

build.gradle

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,20 @@
1515
*/
1616

1717
buildscript {
18+
ext {
19+
kotlin_version = '1.3.72'
20+
}
1821
repositories {
1922
google()
2023
jcenter()
2124
maven { url "https://jitpack.io" }
2225
maven { url 'https://maven.fabric.io/public' }
2326
}
2427
dependencies {
25-
classpath 'com.android.tools.build:gradle:3.6.2'
28+
classpath 'com.android.tools.build:gradle:4.0.1'
2629
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
2730
classpath 'com.google.gms:google-services:4.3.3'
28-
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.71"
31+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72"
2932
}
3033
}
3134

@@ -43,13 +46,13 @@ ext {
4346
compileSdkVersion = 29
4447
minSdkVersion = 16
4548
targetSdkVersion = 29
46-
buildToolsVersion = "28.0.3"
49+
buildToolsVersion = "30.0.1"
4750
versionCode = 12
4851
versionName = "0.7.0"
4952

5053
// dependency versions
5154
assertjVersion = "3.14.0"
52-
kotlinVersion = "1.3.71"
55+
kotlinVersion = "1.3.72"
5356
mockitoVersion = "2.12.0"
5457
mockitoKotlinVersion = "2.2.0"
5558
supportAnnotationsVersion = "28.0.0"

gradle/wrapper/gradle-wrapper.jar

-7 Bytes
Binary file not shown.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-bin.zip
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists

gradlew.bat

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
2929
set APP_BASE_NAME=%~n0
3030
set APP_HOME=%DIRNAME%
3131

32+
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
33+
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34+
3235
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
3336
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
3437

kissmetrics-provider/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/build
2+
kissmetrics-provider.iml

kissmetrics-provider/build.gradle

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright 2020 busybusy, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
apply plugin: 'com.android.library'
18+
apply plugin: 'com.github.dcendents.android-maven'
19+
apply plugin: 'kotlin-android'
20+
apply plugin: 'kotlin-android-extensions'
21+
22+
android {
23+
compileSdkVersion rootProject.ext.compileSdkVersion
24+
buildToolsVersion rootProject.ext.buildToolsVersion
25+
26+
defaultConfig {
27+
minSdkVersion rootProject.ext.minSdkVersion
28+
targetSdkVersion rootProject.ext.targetSdkVersion
29+
versionCode 1
30+
versionName "0.8.0"
31+
}
32+
33+
buildTypes {
34+
release {
35+
debuggable false
36+
minifyEnabled false
37+
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
38+
}
39+
debug {
40+
debuggable true
41+
minifyEnabled false
42+
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
43+
}
44+
}
45+
46+
sourceSets {
47+
main.java.srcDirs += 'src/main/kotlin'
48+
test.java.srcDirs += 'src/test/kotlin'
49+
}
50+
51+
testOptions {
52+
unitTests.returnDefaultValues = true
53+
}
54+
}
55+
56+
dependencies {
57+
implementation 'androidx.core:core-ktx:1.2.0'
58+
59+
implementation project(path: rootProject.ext.analytics_kit)
60+
implementation "com.android.support:support-annotations:$rootProject.ext.supportAnnotationsVersion"
61+
compileOnly 'com.kissmetrics.sdk:KISSmetricsSDK:2.2.2'
62+
63+
testImplementation 'com.kissmetrics.sdk:KISSmetricsSDK:2.2.2'
64+
testImplementation "junit:junit:$rootProject.ext.junit_version"
65+
testImplementation "org.assertj:assertj-core:$rootProject.ext.assertjVersion"
66+
testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$rootProject.ext.kotlinVersion"
67+
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$rootProject.ext.kotlinVersion"
68+
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:$rootProject.ext.mockitoKotlinVersion"
69+
70+
testImplementation "org.powermock:powermock-api-mockito2:2.0.2"
71+
testImplementation "org.powermock:powermock-module-junit4:2.0.2"
72+
}
73+
74+
task sourcesJar(type: Jar) {
75+
archiveClassifier.set("sources")
76+
from android.sourceSets.main.java.srcDirs
77+
}
78+
79+
task javadoc(type: Javadoc) {
80+
failOnError false
81+
source = android.sourceSets.main.java.sourceFiles
82+
setClasspath(getClasspath() + project.files(android.getBootClasspath().join(File.pathSeparator)))
83+
}
84+
85+
task javadocJar(type: Jar, dependsOn: javadoc) {
86+
archiveClassifier.set("javadoc")
87+
from javadoc.destinationDir
88+
}
89+
90+
artifacts {
91+
archives sourcesJar
92+
archives javadocJar
93+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<!--
2+
~ Copyright 2020 busybusy, Inc.
3+
~
4+
~ Licensed under the Apache License, Version 2.0 (the "License");
5+
~ you may not use this file except in compliance with the License.
6+
~ You may obtain a copy of the License at
7+
~
8+
~ http://www.apache.org/licenses/LICENSE-2.0
9+
~
10+
~ Unless required by applicable law or agreed to in writing, software
11+
~ distributed under the License is distributed on an "AS IS" BASIS,
12+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
~ See the License for the specific language governing permissions and
14+
~ limitations under the License.
15+
-->
16+
17+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
18+
package="com.busybusy.analyticskit.kissmetrics_provider" />
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Copyright 2020 busybusy, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.busybusy.analyticskit.kissmetrics_provider
18+
19+
import com.busybusy.analyticskit_android.AnalyticsEvent
20+
import com.busybusy.analyticskit_android.AnalyticsKitProvider
21+
import com.busybusy.analyticskit_android.AnalyticsKitProvider.PriorityFilter
22+
import com.kissmetrics.sdk.KISSmetricsAPI
23+
import com.kissmetrics.sdk.KISSmetricsAPI.RecordCondition
24+
import java.text.DecimalFormat
25+
26+
/**
27+
* Provider that facilitates reporting events to KissMetrics.
28+
* *Note* The KissMetrics SDK supports sending a [RecordCondition]. To send a [RecordCondition] with this provider,
29+
* simply add it to the event's attribute map with "`record_condition`" as the key. From Kotlin code this can
30+
* also be accomplished by calling the `recordCondition(condition)` extension function on an [AnalyticsEvent] instance.
31+
* @see <a href="https://developers.kissmetrics.com/reference#android">KissMetrics Android SDK documentation</a>
32+
*
33+
* @constructor Initializes a new [KissMetricsProvider] object.
34+
*
35+
* @property kissMetrics your already-initialized [KISSmetricsAPI] instance. Please call
36+
* KISSmetricsAPI.sharedAPI(API_KEY, APPLICATION_CONTEXT) prior to setting up your [KissMetricsProvider].
37+
* @property priorityFilter the [PriorityFilter] to use when evaluating events
38+
*/
39+
class KissMetricsProvider(private val kissMetrics: KISSmetricsAPI,
40+
private val priorityFilter: PriorityFilter = PriorityFilter { true }
41+
) : AnalyticsKitProvider {
42+
private val timedEvents: MutableMap<String, AnalyticsEvent> = mutableMapOf()
43+
private val eventTimes: MutableMap<String, Long> = mutableMapOf()
44+
45+
override fun getPriorityFilter(): PriorityFilter = priorityFilter
46+
47+
override fun endTimedEvent(timedEvent: AnalyticsEvent) {
48+
val endTime: Long = System.currentTimeMillis()
49+
val startTime: Long? = this.eventTimes.remove(timedEvent.name())
50+
val finishedEvent: AnalyticsEvent? = timedEvents.remove(timedEvent.name())
51+
52+
if (startTime != null && finishedEvent != null) {
53+
val durationSeconds = (endTime - startTime) / 1000.0
54+
val df = DecimalFormat("#.###")
55+
finishedEvent.putAttribute(DURATION, df.format(durationSeconds))
56+
logKissMetricsEvent(finishedEvent)
57+
} else {
58+
error("Attempted ending an event that was never started (or was previously ended): " + timedEvent.name())
59+
}
60+
}
61+
62+
override fun sendEvent(event: AnalyticsEvent) {
63+
if (event.isTimed) { // Hang onto it until it is done
64+
eventTimes[event.name()] = System.currentTimeMillis()
65+
timedEvents[event.name()] = event
66+
} else { // Send the event through the Intercom SDK
67+
logKissMetricsEvent(event)
68+
}
69+
}
70+
71+
private fun logKissMetricsEvent(event: AnalyticsEvent) {
72+
if (event.attributes != null && event.attributes?.isNotEmpty() == true) {
73+
val attributes: MutableMap<String, Any> = event.attributes as MutableMap<String, Any>
74+
if (attributes.containsKey(RECORD_CONDITION)) {
75+
val condition: RecordCondition = attributes.remove(RECORD_CONDITION) as RecordCondition
76+
if (attributes.isNotEmpty()) { // other attributes there we need to track
77+
val stringProperties = attributes.stringifyAttributes()
78+
KISSmetricsAPI.sharedAPI().record(event.name(), stringProperties, condition)
79+
} else { // just the condition to send
80+
KISSmetricsAPI.sharedAPI().record(event.name(), condition)
81+
}
82+
} else { // no condition to record, just attributes
83+
val stringProperties = attributes.stringifyAttributes()
84+
KISSmetricsAPI.sharedAPI().record(event.name(), stringProperties)
85+
}
86+
} else { // record event by name only
87+
KISSmetricsAPI.sharedAPI().record(event.name())
88+
}
89+
}
90+
}
91+
92+
internal const val DURATION = "event_duration"
93+
const val RECORD_CONDITION = "record_condition"
94+
95+
/**
96+
* Converts an attributes Map to to Map<String, String> to appease the KissMetrics API.
97+
*/
98+
fun Map<String, Any>.stringifyAttributes(): Map<String, String> {
99+
return if (this.isNotEmpty()) {
100+
val attributeMap = mutableMapOf<String, String>()
101+
for (key in this.keys) {
102+
attributeMap[key] = this[key].toString()
103+
}
104+
attributeMap
105+
} else emptyMap()
106+
}
107+
108+
fun AnalyticsEvent.recordCondition(condition: RecordCondition): AnalyticsEvent {
109+
this.putAttribute(RECORD_CONDITION, condition)
110+
return this
111+
}

0 commit comments

Comments
 (0)