androidengineers.Book a session

Monitoring & Analytics

Analytics Events & Funnels

article20 minMedium

Analytics turns user behavior into product decisions. Well-designed events and funnels answer "why are users dropping off?" — but only if you define them with precision before you build.

Event Design Principles

Naming Convention

Use noun_verb or object_action format:

article_view
article_read_complete
search_query_submitted
cart_item_added
checkout_started
checkout_completed
payment_failed

Avoid generic names (click, tap, button_pressed) — they tell you nothing.

Parameter Taxonomy

Every event should have:

  • What: the object (article_id, product_id)
  • Context: where it happened (screen_name, section)
  • User state: relevant user properties (subscription_tier, is_onboarded)
sealed class AnalyticsEvent {
    abstract val name: String
    abstract val params: Map<String, Any>

    data class ArticleViewed(
        val articleId: String,
        val title: String,
        val source: String,      // "feed", "search", "deeplink"
        val readingTime: Int     // estimated reading time in seconds
    ) : AnalyticsEvent() {
        override val name = "article_view"
        override val params = mapOf(
            "article_id" to articleId,
            "article_title" to title,
            "source" to source,
            "estimated_reading_time" to readingTime
        )
    }

    data class SearchPerformed(
        val query: String,
        val resultCount: Int,
        val hasFilters: Boolean
    ) : AnalyticsEvent() {
        override val name = "search_query_submitted"
        override val params = mapOf(
            "query_length" to query.length,
            "result_count" to resultCount,
            "has_filters" to hasFilters
        )
    }
}

Analytics Abstraction Layer

Never call Firebase Analytics directly from UI code — use an abstraction so you can swap providers and unit test:

interface AnalyticsTracker {
    fun track(event: AnalyticsEvent)
    fun setUserProperty(name: String, value: String)
    fun setUserId(id: String)
}

class FirebaseAnalyticsTracker(
    private val analytics: FirebaseAnalytics
) : AnalyticsTracker {

    override fun track(event: AnalyticsEvent) {
        analytics.logEvent(event.name) {
            event.params.forEach { (key, value) ->
                when (value) {
                    is String -> param(key, value)
                    is Long -> param(key, value)
                    is Double -> param(key, value)
                    is Int -> param(key, value.toLong())
                    is Boolean -> param(key, if (value) 1L else 0L)
                }
            }
        }
    }

    override fun setUserProperty(name: String, value: String) {
        analytics.setUserProperty(name, value)
    }

    override fun setUserId(id: String) {
        analytics.setUserId(id)
    }
}

// Debug/test implementation
class LoggingAnalyticsTracker : AnalyticsTracker {
    override fun track(event: AnalyticsEvent) {
        Log.d("Analytics", "[${event.name}] ${event.params}")
    }
    override fun setUserProperty(name: String, value: String) {}
    override fun setUserId(id: String) {}
}

Tracking in ViewModels

@HiltViewModel
class ArticleViewModel @Inject constructor(
    private val repository: ArticleRepository,
    private val analytics: AnalyticsTracker
) : ViewModel() {

    fun onArticleOpened(articleId: String) = viewModelScope.launch {
        val article = repository.getArticle(articleId)
        analytics.track(AnalyticsEvent.ArticleViewed(
            articleId = article.id,
            title = article.title,
            source = "feed",
            readingTime = article.estimatedReadingTime
        ))
    }
}

Funnel Definition

A funnel tracks a multi-step user journey:

Checkout Funnel:
1. cart_view              (users who saw the cart)
2. checkout_started       (tapped "Checkout")
3. payment_info_entered   (filled payment form)
4. checkout_completed     (order placed)

Drop-off rate:
cart_view → checkout_started: 40% conversion (60% abandon)
checkout_started → payment_entered: 80%
payment_entered → completed: 85%

Product decision: fix the 60% cart-to-checkout drop-off
// Instrument each funnel step
fun onCartViewed() = analytics.track(AnalyticsEvent.CartViewed(itemCount = items.size))
fun onCheckoutTapped() = analytics.track(AnalyticsEvent.CheckoutStarted(cartValue = cart.total))
fun onPaymentEntered() = analytics.track(AnalyticsEvent.PaymentInfoEntered)
fun onOrderPlaced(orderId: String) = analytics.track(AnalyticsEvent.CheckoutCompleted(orderId))

User Properties for Segmentation

// Set once at login; used to segment all events
analytics.setUserId(user.id)
analytics.setUserProperty("subscription_tier", user.tier.name)  // FREE, PRO, ENTERPRISE
analytics.setUserProperty("days_since_signup", user.daysSinceSignup.toString())
analytics.setUserProperty("content_language", user.preferredLanguage)

Key Takeaways

PracticeRule
noun_verb namingConsistent, searchable, queryable in BigQuery
Abstraction layerNever call Firebase directly from UI; allows mocking in tests
User propertiesSet at login; enable cohort analysis in analytics dashboard
Funnel instrumentationEvery step of a critical journey must be an event
Avoid PII in eventsNo names, emails, or exact locations in event params
Debug viewEnable Firebase DebugView on device to validate events in real time

YOUR LEARNING JOURNEY

0 of 177 available lessons completed

Progress saved in this browser. No account needed.
Analytics Events & Funnels | Android System Design | Android Engineers