Skip to content

Commit

Permalink
Android firebase integration (#12)
Browse files Browse the repository at this point in the history
* Using in-house fork of Firebase Integration for Android

* Converted integration to be kotlin first

* Supressed lint

* Linting issues resolved

* Bump kotlin version to fix cinterop error

* Opt in to experimental api. Required for build

---------

Co-authored-by: Dave Blake <[email protected]>
  • Loading branch information
Reedyuk and dblake78 authored Sep 10, 2024
1 parent c803e2f commit 87e3806
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 6 deletions.
2 changes: 1 addition & 1 deletion library/Segmenkt.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'Segmenkt'
spec.version = '0.3.2'
spec.version = '0.3.3'
spec.homepage = ''
spec.source = { :http=> ''}
spec.authors = ''
Expand Down
6 changes: 5 additions & 1 deletion library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ kotlin {
val jsTest by getting
val androidMain by getting {
dependencies {
implementation("com.google.firebase:firebase-analytics:21.0.0")
api("com.segment.analytics.android:analytics:4.10.4")
api("com.appsflyer:segment-android-integration:6.5.2")
api("com.segment.analytics.android.integrations:firebase:1.1.0")
}
}
val androidUnitTest by getting {
Expand All @@ -108,6 +108,10 @@ kotlin {
val iosTest by getting
val iosSimulatorArm64Test by getting
iosSimulatorArm64Test.dependsOn(iosTest)

all {
languageSettings.optIn("kotlinx.cinterop.ExperimentalForeignApi")
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion library/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ signing.keyId=""
signing.password=""

MODULE_PACKAGE_NAME=com.myunidays
MODULE_VERSION_NUMBER=0.3.2
MODULE_VERSION_NUMBER=0.3.3
MODULE_NAME=segmenkt

OPEN_SOURCE_REPO=https://oss.sonatype.org/service/local/staging/deploy/maven2/
Expand Down
3 changes: 3 additions & 0 deletions library/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
</manifest>
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
package com.myunidays.segmenkt

import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import com.google.firebase.analytics.FirebaseAnalytics
import com.myunidays.segmenkt.integrations.AliasPayload
import com.myunidays.segmenkt.integrations.GroupPayload
import com.myunidays.segmenkt.integrations.IdentifyPayload
import com.myunidays.segmenkt.integrations.Integration
import com.myunidays.segmenkt.integrations.IntegrationFactory
import com.myunidays.segmenkt.integrations.ScreenPayload
import com.myunidays.segmenkt.integrations.TrackPayload
import com.segment.analytics.Analytics
import com.segment.analytics.Properties
import com.segment.analytics.ValueMap
import com.segment.analytics.integrations.Logger
import com.segment.analytics.internal.Utils
import kotlin.math.min

@Suppress("TooManyFunctions")
actual class FirebaseIntegration internal constructor(
private val android: com.segment.analytics.integrations.Integration<*>
private val android: AndroidFirebaseIntegration
) : Integration<FirebaseIntegration> {
fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) =
android.onActivityCreated(activity, savedInstanceState)
Expand All @@ -38,6 +48,243 @@ actual class FirebaseIntegration internal constructor(
actual fun factory(
delegate: Any?,
deeplinkHandler: Any?
): IntegrationFactory = com.segment.analytics.android.integrations.firebase.FirebaseIntegration.FACTORY
): IntegrationFactory = AndroidFirebaseIntegration.FACTORY
}
}

/**
* A kotlin converted implementation of FirebaseIntegration https://github.com/segment-integrations/analytics-android-integration-firebase
*/
internal class AndroidFirebaseIntegration(context: Context?, private val logger: Logger) :
com.segment.analytics.integrations.Integration<FirebaseAnalytics?>() {
private val firebaseAnalytics = FirebaseAnalytics.getInstance(context!!)
private var currentActivity: Activity? = null

override fun onActivityResumed(activity: Activity?) {
super.onActivityResumed(activity)

val packageManager = activity?.packageManager
try {
val info =
packageManager?.getActivityInfo(activity.componentName, PackageManager.GET_META_DATA)
val activityLabel = info?.loadLabel(packageManager).toString()
firebaseAnalytics.setCurrentScreen(activity!!, activityLabel, null)
logger.verbose("firebaseAnalytics.setCurrentScreen(activity, %s, null);", activityLabel)
} catch (e: PackageManager.NameNotFoundException) {
throw AssertionError("Activity Not Found: $e")
}
}

override fun onActivityStarted(activity: Activity?) {
super.onActivityStarted(activity)

this.currentActivity = activity
}

override fun onActivityStopped(activity: Activity?) {
super.onActivityStopped(activity)

this.currentActivity = null
}

override fun identify(identify: com.segment.analytics.integrations.IdentifyPayload) {
super.identify(identify)

if (!Utils.isNullOrEmpty(identify.userId())) {
firebaseAnalytics.setUserId(identify.userId())
}
val traits: Map<String?, Any> = identify.traits()
for (entry in traits.entries) {
var trait = entry.key
val value = entry.value.toString()
trait = makeKey(trait)
firebaseAnalytics.setUserProperty(trait, value)
logger.verbose("firebaseAnalytics.setUserProperty(%s, %s);", trait, value)
}
}

override fun track(track: com.segment.analytics.integrations.TrackPayload) {
super.track(track)

val event = track.event()
val eventName = if (EVENT_MAPPER.containsKey(event)) {
EVENT_MAPPER[event]
} else {
makeKey(event)
}
val properties = track.properties()
val formattedProperties = formatProperties(properties)
firebaseAnalytics.logEvent(eventName!!, formattedProperties)
logger.verbose("firebaseAnalytics.logEvent(%s, %s);", eventName, formattedProperties)
}

override fun screen(screen: com.segment.analytics.integrations.ScreenPayload) {
super.screen(screen)

val propertiesBundle = Bundle()
for ((key, value) in screen) {
propertiesBundle.putString(key, value.toString())
}
propertiesBundle.putString(FirebaseAnalytics.Param.SCREEN_NAME, screen.name())
firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW, propertiesBundle)
logger.verbose("firebaseAnalytics.screen(activity, %s, null);", screen.name())
}

companion object {
val FACTORY: Factory = object : Factory {
@Suppress("ReturnCount")
override fun create(
settings: ValueMap,
analytics: Analytics
): com.segment.analytics.integrations.Integration<*>? {
val logger = analytics.logger(FIREBASE_ANALYTICS_KEY)
if (!Utils.hasPermission(
analytics.application, Manifest.permission.ACCESS_NETWORK_STATE
)
) {
logger.debug("ACCESS_NETWORK_STATE is required for Firebase Analytics.")
return null
}
if (!Utils.hasPermission(analytics.application, Manifest.permission.WAKE_LOCK)) {
logger.debug("WAKE_LOCK is required for Firebase Analytics.")
return null
}

val context: Context = analytics.application

return AndroidFirebaseIntegration(context, logger)
}

override fun key(): String {
return FIREBASE_ANALYTICS_KEY
}
}

private const val FIREBASE_ANALYTICS_KEY = "Firebase"
private val EVENT_MAPPER = createEventMap()
private fun createEventMap(): Map<String, String> {
val EVENT_MAPPER: MutableMap<String, String> = HashMap()
EVENT_MAPPER["Product Added"] = FirebaseAnalytics.Event.ADD_TO_CART
EVENT_MAPPER["Checkout Started"] = FirebaseAnalytics.Event.BEGIN_CHECKOUT
EVENT_MAPPER["Order Completed"] = FirebaseAnalytics.Event.PURCHASE
EVENT_MAPPER["Order Refunded"] = FirebaseAnalytics.Event.REFUND
EVENT_MAPPER["Product Viewed"] = FirebaseAnalytics.Event.VIEW_ITEM
EVENT_MAPPER["Product List Viewed"] = FirebaseAnalytics.Event.VIEW_ITEM_LIST
EVENT_MAPPER["Payment Info Entered"] = FirebaseAnalytics.Event.ADD_PAYMENT_INFO
EVENT_MAPPER["Promotion Viewed"] = FirebaseAnalytics.Event.VIEW_PROMOTION
EVENT_MAPPER["Product Added to Wishlist"] = FirebaseAnalytics.Event.ADD_TO_WISHLIST
EVENT_MAPPER["Product Shared"] = FirebaseAnalytics.Event.SHARE
EVENT_MAPPER["Product Clicked"] = FirebaseAnalytics.Event.SELECT_CONTENT
EVENT_MAPPER["Products Searched"] = FirebaseAnalytics.Event.SEARCH
return EVENT_MAPPER
}

private val PROPERTY_MAPPER = createPropertyMap()

private fun createPropertyMap(): Map<String?, String> {
val PROPERTY_MAPPER: MutableMap<String?, String> = HashMap()
PROPERTY_MAPPER["category"] = FirebaseAnalytics.Param.ITEM_CATEGORY
PROPERTY_MAPPER["product_id"] = FirebaseAnalytics.Param.ITEM_ID
PROPERTY_MAPPER["name"] = FirebaseAnalytics.Param.ITEM_NAME
PROPERTY_MAPPER["price"] = FirebaseAnalytics.Param.PRICE
PROPERTY_MAPPER["quantity"] = FirebaseAnalytics.Param.QUANTITY
PROPERTY_MAPPER["query"] = FirebaseAnalytics.Param.SEARCH_TERM
PROPERTY_MAPPER["shipping"] = FirebaseAnalytics.Param.SHIPPING
PROPERTY_MAPPER["tax"] = FirebaseAnalytics.Param.TAX
PROPERTY_MAPPER["total"] = FirebaseAnalytics.Param.VALUE
PROPERTY_MAPPER["revenue"] = FirebaseAnalytics.Param.VALUE
PROPERTY_MAPPER["order_id"] = FirebaseAnalytics.Param.TRANSACTION_ID
PROPERTY_MAPPER["currency"] = FirebaseAnalytics.Param.CURRENCY
PROPERTY_MAPPER["products"] = FirebaseAnalytics.Param.ITEMS
return PROPERTY_MAPPER
}

private val PRODUCT_MAPPER = createProductMap()

private fun createProductMap(): Map<String?, String> {
val MAPPER: MutableMap<String?, String> = HashMap()
MAPPER["category"] = FirebaseAnalytics.Param.ITEM_CATEGORY
MAPPER["product_id"] = FirebaseAnalytics.Param.ITEM_ID
MAPPER["id"] = FirebaseAnalytics.Param.ITEM_ID
MAPPER["name"] = FirebaseAnalytics.Param.ITEM_NAME
MAPPER["price"] = FirebaseAnalytics.Param.PRICE
MAPPER["quantity"] = FirebaseAnalytics.Param.QUANTITY
return MAPPER
}

private fun formatProperties(properties: Properties): Bundle {
val bundle = Bundle()
if ((properties.revenue() != 0.0 || properties.total() != 0.0) && Utils.isNullOrEmpty(properties.currency())
) {
bundle.putString(FirebaseAnalytics.Param.CURRENCY, "USD")
}
for (entry in properties.entries) {
val value = entry.value
var property = entry.key
property = if (PROPERTY_MAPPER.containsKey(property)) {
PROPERTY_MAPPER[property]
} else {
makeKey(property)
}
if (property == FirebaseAnalytics.Param.ITEMS && value != null) {
val products = properties.getList(
"products",
ValueMap::class.java
)
val mappedProducts = formatProducts(products)
bundle.putParcelableArrayList(property, mappedProducts)
} else {
putValue(bundle, property, value)
}
}
return bundle
}

private fun formatProducts(products: List<ValueMap>?): ArrayList<Bundle> {
val mappedProducts = ArrayList<Bundle>()
if (products == null) return mappedProducts

for (product in products) {
val mappedProduct = Bundle()
for (innerEntry in product.entries) {
var key = innerEntry.key
val value = innerEntry.value
key = if (PRODUCT_MAPPER.containsKey(key)) {
PRODUCT_MAPPER[key]
} else {
makeKey(key)
}
putValue(mappedProduct, key, value)
}
mappedProducts.add(mappedProduct)
}
return mappedProducts
}

private fun putValue(bundle: Bundle, key: String?, value: Any?) {
if (value is Int) {
bundle.putInt(key, value)
} else if (value is Double) {
bundle.putDouble(key, value)
} else if (value is Long) {
bundle.putLong(key, value)
} else {
val stringValue = value.toString()
bundle.putString(key, stringValue)
}
}

@Suppress("MagicNumber")
fun makeKey(key: String?): String {
var key = key
val forbiddenChars = arrayOf(".", "-", " ", ":")
for (forbidden in forbiddenChars) {
if (key!!.contains(forbidden)) {
key = key.trim { it <= ' ' }.replace(forbidden, "_")
}
}

return key!!.substring(0, min(key.length.toDouble(), 40.0).toInt())
}
}
}
2 changes: 1 addition & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import java.util.Properties

dependencyResolutionManagement {
versionCatalogs {
val kotlinVersion = "1.9.10"
val kotlinVersion = "1.9.23"
create("libs") {
version("kotlin", kotlinVersion)

Expand Down

0 comments on commit 87e3806

Please sign in to comment.