Android SDK
Native Android SDK written in Kotlin
Android SDK
The com.entrolytics:android-sdk package provides a native Android SDK written in Kotlin for analytics tracking with automatic lifecycle management, crash reporting, and battery optimization.
Installation
// build.gradle (Module: app)
dependencies {
implementation 'com.entrolytics:android-sdk:1.0.0'
}Permissions
Add to AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />Quick Start
Initialize SDK
// Application.kt
import com.entrolytics.sdk.Entrolytics
import com.entrolytics.sdk.Config
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize Entrolytics
Entrolytics.initialize(
context = this,
config = Config.Builder()
.websiteId("your-website-id")
.apiKey("your-api-key")
.debug(BuildConfig.DEBUG)
.autoTrackScreens(true)
.trackCrashes(true)
.build()
)
}
}Track Events
// MainActivity.kt
import com.entrolytics.sdk.Entrolytics
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Track custom events
trackUserActions()
}
private fun trackUserActions() {
// Track button click
binding.signupButton.setOnClickListener {
Entrolytics.track("signup_button_click", mapOf(
"screen" to "MainActivity",
"button_text" to "Sign Up"
))
}
// Track user interaction
binding.featureCard.setOnClickListener {
Entrolytics.track("feature_discovery", mapOf(
"feature_name" to "premium_analytics",
"source" to "main_card"
))
}
}
}Track Screens
// BaseFragment.kt
import com.entrolytics.sdk.Entrolytics
abstract class BaseFragment : Fragment() {
override fun onResume() {
super.onResume()
// Track screen view automatically
Entrolytics.screen(
name = getScreenName(),
properties = getScreenProperties()
)
}
protected open fun getScreenName(): String {
return this::class.java.simpleName
}
protected open fun getScreenProperties(): Map<String, Any> {
return emptyMap()
}
}
// Usage
class ProfileFragment : BaseFragment() {
override fun getScreenName(): String {
return "ProfileScreen"
}
override fun getScreenProperties(): Map<String, Any> {
return mapOf(
"user_authenticated" to (currentUser != null),
"has_premium" to (currentUser?.isPremium ?: false)
)
}
}Configuration
Configuration Builder
import com.entrolytics.sdk.Config
val config = Config.Builder()
.websiteId("your-website-id") // required
.apiKey("your-api-key") // optional
.host("https://entrolytics.dev") // custom host
.debug(BuildConfig.DEBUG) // debug mode
.autoTrackScreens(true) // auto-track screens
.trackCrashes(true) // track crashes
.trackAppLifecycle(true) // track app lifecycle
.batchSize(50) // batch size
.flushInterval(30_000L) // flush interval (ms)
.timeout(10_000L) // request timeout (ms)
.maxRetries(3) // max retry attempts
.batteryOptimization(true) // battery optimization
.offlineSupport(true) // offline support
.build()Configuration via Resources
<!-- res/xml/entrolytics_config.xml -->
<?xml version="1.0" encoding="utf-8"?>
<config>
<website-id>your-website-id</website-id>
<api-key>your-api-key</api-key>
<host>https://entrolytics.dev</host>
<debug>false</debug>
<auto-track-screens>true</auto-track-screens>
<track-crashes>true</track-crashes>
<batch-size>50</batch-size>
<flush-interval>30000</flush-interval>
</config>// Load from resources
val config = Config.fromResources(this, R.xml.entrolytics_config)
Entrolytics.initialize(this, config)API Reference
Core Class
object Entrolytics {
// Initialization
fun initialize(context: Context, config: Config)
fun isInitialized(): Boolean
// Event tracking
fun track(event: String, properties: Map<String, Any>? = null)
fun trackAsync(event: String, properties: Map<String, Any>? = null)
// Screen tracking
fun screen(name: String, properties: Map<String, Any>? = null)
fun screenAsync(name: String, properties: Map<String, Any>? = null)
// User identification
fun identify(userId: String, traits: Map<String, Any>? = null)
fun identifyAsync(userId: String, traits: Map<String, Any>? = null)
// User properties
fun setUserProperties(properties: Map<String, Any>)
fun setUserPropertiesAsync(properties: Map<String, Any>)
// Batch operations
fun trackBatch(events: List<Event>)
fun trackBatchAsync(events: List<Event>)
// Control
fun flush()
fun flushAsync()
fun reset()
fun setEnabled(enabled: Boolean)
fun isEnabled(): Boolean
// Configuration
fun getConfig(): Config
fun updateConfig(config: Config)
// User information
fun getUserId(): String?
fun getSessionId(): String
}Data Classes
data class Config(
val websiteId: String,
val apiKey: String? = null,
val host: String = "https://entrolytics.dev",
val debug: Boolean = false,
val autoTrackScreens: Boolean = true,
val trackCrashes: Boolean = true,
val trackAppLifecycle: Boolean = true,
val batchSize: Int = 50,
val flushInterval: Long = 30_000L,
val timeout: Long = 10_000L,
val maxRetries: Int = 3,
val batteryOptimization: Boolean = true,
val offlineSupport: Boolean = true
)
data class Event(
val event: String,
val properties: Map<String, Any>? = null,
val timestamp: Long = System.currentTimeMillis(),
val userId: String? = null,
val sessionId: String? = null
)Advanced Usage
Activity Lifecycle Integration
// BaseActivity.kt
import com.entrolytics.sdk.Entrolytics
abstract class BaseActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
// Track screen automatically
Entrolytics.screen(
name = getScreenName(),
properties = getScreenProperties()
)
}
override fun onPause() {
super.onPause()
// Track screen exit
Entrolytics.track("screen_exit", mapOf(
"screen_name" to getScreenName(),
"duration_ms" to getScreenDuration()
))
}
private var screenStartTime: Long = 0
override fun onStart() {
super.onStart()
screenStartTime = System.currentTimeMillis()
}
protected open fun getScreenName(): String {
return this::class.java.simpleName
}
protected open fun getScreenProperties(): Map<String, Any> {
return emptyMap()
}
protected open fun getScreenDuration(): Long {
return if (screenStartTime > 0) {
System.currentTimeMillis() - screenStartTime
} else 0L
}
}Fragment Integration
// BaseFragment.kt
import com.entrolytics.sdk.Entrolytics
abstract class BaseFragment : Fragment() {
override fun onResume() {
super.onResume()
// Track fragment screen
Entrolytics.screen(
name = getScreenName(),
properties = getScreenProperties().plus(
"parent_activity" to requireActivity()::class.java.simpleName
)
)
}
protected open fun getScreenName(): String {
return this::class.java.simpleName
}
protected open fun getScreenProperties(): Map<String, Any> {
return emptyMap()
}
}User Authentication Integration
// AuthenticationManager.kt
import com.entrolytics.sdk.Entrolytics
class AuthenticationManager {
suspend fun login(email: String, password: String): Result<User> {
return try {
// Track login attempt
Entrolytics.track("login_attempt", mapOf(
"method" to "email",
"email_hash" to email.hashCode().toString()
))
val user = apiService.login(email, password)
// Identify user in analytics
Entrolytics.identify(user.id, mapOf(
"email" to user.email,
"name" to user.name,
"role" to user.role,
"plan" to user.subscriptionPlan
))
// Track successful login
Entrolytics.track("login_success", mapOf(
"user_id" to user.id,
"method" to "email"
))
// Update user properties
Entrolytics.setUserProperties(mapOf(
"login_count" to (user.loginCount + 1),
"last_login" to System.currentTimeMillis(),
"is_premium" to user.isPremium
))
Result.success(user)
} catch (e: Exception) {
// Track login failure
Entrolytics.track("login_failed", mapOf(
"method" to "email",
"error_type" to e::class.java.simpleName,
"error_message" to e.message
))
Result.failure(e)
}
}
fun logout() {
// Track logout
Entrolytics.track("logout", mapOf(
"method" to "explicit"
))
// Reset user session
Entrolytics.reset()
// Clear local data
clearUserData()
}
}E-commerce Tracking
// ProductActivity.kt
import com.entrolytics.sdk.Entrolytics
class ProductActivity : BaseActivity() {
private lateinit var product: Product
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_product)
product = intent.getParcelableExtra(EXTRA_PRODUCT)!!
// Track product view
Entrolytics.track("product_view", mapOf(
"product_id" to product.id,
"product_name" to product.name,
"category" to product.category,
"price" to product.price,
"currency" to product.currency,
"in_stock" to product.inStock,
"source" to intent.getStringExtra(EXTRA_SOURCE) ?: "direct"
))
setupUI()
}
private fun setupUI() {
// Track add to cart
binding.addToCartButton.setOnClickListener {
Entrolytics.track("add_to_cart", mapOf(
"product_id" to product.id,
"product_name" to product.name,
"price" to product.price,
"quantity" to binding.quantityPicker.value,
"currency" to product.currency
))
addToCart(product, binding.quantityPicker.value)
}
// Track wishlist
binding.addToWishlistButton.setOnClickListener {
Entrolytics.track("add_to_wishlist", mapOf(
"product_id" to product.id,
"product_name" to product.name,
"category" to product.category
))
addToWishlist(product)
}
// Track share
binding.shareButton.setOnClickListener {
Entrolytics.track("product_share", mapOf(
"product_id" to product.id,
"product_name" to product.name,
"method" to "native_share"
))
shareProduct(product)
}
}
}
// CheckoutActivity.kt
class CheckoutActivity : BaseActivity() {
private fun trackPurchase(order: Order) {
Entrolytics.track("purchase", mapOf(
"order_id" to order.id,
"total" to order.total,
"currency" to order.currency,
"payment_method" to order.paymentMethod,
"items" to order.items.map { item ->
mapOf(
"product_id" to item.productId,
"product_name" to item.productName,
"quantity" to item.quantity,
"price" to item.price
)
}
))
// Update user properties
Entrolytics.setUserProperties(mapOf(
"total_orders" to (getCurrentUser().orderCount + 1),
"total_spent" to (getCurrentUser().totalSpent + order.total),
"last_purchase" to System.currentTimeMillis()
))
}
}Performance Monitoring
// PerformanceTracker.kt
import com.entrolytics.sdk.Entrolytics
import kotlin.system.measureTimeMillis
class PerformanceTracker {
inline fun <T> trackOperation(
operationName: String,
properties: Map<String, Any> = emptyMap(),
operation: () -> T
): T {
val duration = measureTimeMillis {
return operation()
}
Entrolytics.track("performance_operation", mapOf(
"operation" to operationName,
"duration_ms" to duration,
"success" to true
).plus(properties))
return operation()
}
inline fun <T> trackOperationAsync(
operationName: String,
properties: Map<String, Any> = emptyMap(),
crossinline operation: suspend () -> T
): suspend T {
val startTime = System.currentTimeMillis()
return try {
val result = operation()
val duration = System.currentTimeMillis() - startTime
Entrolytics.trackAsync("performance_operation", mapOf(
"operation" to operationName,
"duration_ms" to duration,
"success" to true
).plus(properties))
result
} catch (e: Exception) {
val duration = System.currentTimeMillis() - startTime
Entrolytics.trackAsync("performance_operation", mapOf(
"operation" to operationName,
"duration_ms" to duration,
"success" to false,
"error_type" to e::class.java.simpleName
).plus(properties))
throw e
}
}
}
// Usage
class DataManager {
private val performanceTracker = PerformanceTracker()
suspend fun loadUserData(userId: String): User {
return performanceTracker.trackOperationAsync("load_user_data", mapOf(
"user_id" to userId
)) {
apiService.getUser(userId)
}
}
}Error and Crash Tracking
// ErrorTracker.kt
import com.entrolytics.sdk.Entrolytics
class ErrorTracker : Thread.UncaughtExceptionHandler {
private val defaultHandler: Thread.UncaughtExceptionHandler? =
Thread.getDefaultUncaughtExceptionHandler()
override fun uncaughtException(thread: Thread, throwable: Throwable) {
// Track crash
Entrolytics.track("app_crash", mapOf(
"error_type" to throwable::class.java.simpleName,
"error_message" to throwable.message,
"stack_trace" to Log.getStackTraceString(throwable),
"thread_name" to thread.name,
"device_info" to getDeviceInfo(),
"app_version" to getAppVersion(),
"build_type" to BuildConfig.BUILD_TYPE
))
// Call default handler
defaultHandler?.uncaughtException(thread, throwable)
}
fun trackHandledError(error: Throwable, context: String = "") {
Entrolytics.track("handled_error", mapOf(
"error_type" to error::class.java.simpleName,
"error_message" to error.message,
"context" to context,
"stack_trace" to Log.getStackTraceString(error)
))
}
private fun getDeviceInfo(): Map<String, String> {
return mapOf(
"manufacturer" to Build.MANUFACTURER,
"model" to Build.MODEL,
"os_version" to Build.VERSION.RELEASE,
"api_level" to Build.VERSION.SDK_INT.toString()
)
}
private fun getAppVersion(): String {
return "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})"
}
}
// Application.kt
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Set up error tracking
val errorTracker = ErrorTracker()
Thread.setDefaultUncaughtExceptionHandler(errorTracker)
// Initialize Entrolytics
Entrolytics.initialize(this, config)
}
}Testing
Unit Tests
// EntrolyticsTest.kt
import com.entrolytics.sdk.Entrolytics
import io.mockk.every
import io.mockk.mockkStatic
import io.mockk.verify
import org.junit.Before
import org.junit.Test
class EntrolyticsTest {
@Before
fun setup() {
// Mock the SDK
mockkStatic(Entrolytics::class)
}
@Test
fun `track event with properties`() {
// Arrange
val eventName = "test_event"
val properties = mapOf("property" to "value")
every { Entrolytics.track(eventName, properties) } returns Unit
// Act
Entrolytics.track(eventName, properties)
// Assert
verify { Entrolytics.track(eventName, properties) }
}
@Test
fun `identify user with traits`() {
// Arrange
val userId = "user-123"
val traits = mapOf("email" to "user@example.com")
every { Entrolytics.identify(userId, traits) } returns Unit
// Act
Entrolytics.identify(userId, traits)
// Assert
verify { Entrolytics.identify(userId, traits) }
}
}Instrumentation Tests
// AnalyticsInstrumentationTest.kt
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.entrolytics.sdk.Entrolytics
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class AnalyticsInstrumentationTest {
@Test
fun testInitialization() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
// Test initialization
val config = Config.Builder()
.websiteId("test-website-id")
.debug(true)
.build()
Entrolytics.initialize(context, config)
// Verify initialization
assert(Entrolytics.isInitialized())
}
@Test
fun testEventTracking() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
// Initialize
Entrolytics.initialize(context, testConfig)
// Track event
Entrolytics.track("test_event", mapOf("test" to true))
// In real tests, you would verify the event was sent
// This might require mocking the network layer
}
}Performance Optimization
Battery Optimization
// BatteryAwareTracker.kt
import android.content.Context
import android.os.BatteryManager
import com.entrolytics.sdk.Entrolytics
class BatteryAwareTracker(private val context: Context) {
fun trackWithBatteryAwareness(event: String, properties: Map<String, Any> = emptyMap()) {
val batteryLevel = getBatteryLevel()
if (batteryLevel < 0.2) {
// Low battery mode - reduce tracking frequency
if (Math.random() < 0.1) { // Only track 10% of events
Entrolytics.track(event, properties.plus("battery_saver" to true))
}
} else {
// Normal tracking
Entrolytics.track(event, properties.plus("battery_level" to batteryLevel))
}
}
private fun getBatteryLevel(): Float {
val batteryManager = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) / 100f
}
}Memory Management
// MemoryEfficientTracker.kt
import com.entrolytics.sdk.Entrolytics
class MemoryEfficientTracker {
private val eventQueue = mutableListOf<Event>()
private val maxQueueSize = 100
fun addEvent(event: Event) {
synchronized(eventQueue) {
if (eventQueue.size >= maxQueueSize) {
// Remove oldest events to prevent memory issues
eventQueue.removeAt(0)
}
eventQueue.add(event)
// Flush if queue is getting full
if (eventQueue.size >= maxQueueSize * 0.8) {
flushEvents()
}
}
}
private fun flushEvents() {
synchronized(eventQueue) {
if (eventQueue.isNotEmpty()) {
Entrolytics.trackBatch(eventQueue.toList())
eventQueue.clear()
}
}
}
fun flush() {
flushEvents()
}
}Troubleshooting
Best Practices
Migration Guide
From Other Analytics SDKs
// Before - Other analytics
import com.other.analytics.Analytics
Analytics.track("event_name", mapOf("property" to "value"))
// After - Entrolytics
import com.entrolytics.sdk.Entrolytics
Entrolytics.track("event_name", mapOf("property" to "value"))From Version 1.x
// Before - v1.x
Entrolytics.getInstance().track("event", properties)
// After - v2.x
Entrolytics.track("event", properties)Android SDK for Entrolytics - First-party growth analytics for the edge