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