Analytics in Android apps: practical tips and trade-offs
Most Android teams collect too much data and act on too little of it. We've inherited a culture of "capture everything and figure it out later," but that approach costs you in performance, battery drain, and user trust. In this article, we'll walk through how to build analytics that actually inform your product decisions without turning your app into a surveillance tool.
Why analytics matter (and when they don't)
Analytics exist to answer specific questions: Did our payment flow conversion improve after we redesigned it? Are users hitting an unexpected error in production? Is feature X actually being used? If you can't articulate what question you're trying to answer with a metric, you probably shouldn't be tracking it.
We've seen teams spend weeks configuring event tracking for every screen transition, button tap, and form field, only to never open the dashboard. Start by defining your core business metrics—the ones tied to retention, revenue, or user satisfaction. Everything else is noise.
Track key events, not everything
The temptation to log every user action is strong, especially when your analytics SDK makes it trivial. Resist it. Each event you send:
- Consumes network bandwidth (even on WiFi, it adds up)
- Drains battery on mobile devices
- Increases app size and startup time
- Creates noise in your dashboards
- Raises privacy and compliance concerns
Instead, identify the events that tell your core story. For an e-commerce app, that might be: product viewed, added to cart, checkout started, payment completed, and order confirmed. For a ride-hailing app: ride requested, driver accepted, pickup arrived, ride completed. For a social app: signup, profile created, content posted, content engaged.
Here's a realistic approach using Firebase Analytics, which ships with most Android projects:
// Good: track high-value business events
private fun logCheckoutStarted(cartValue: Double, itemCount: Int) {
val bundle = Bundle().apply {
putDouble(FirebaseAnalytics.Param.VALUE, cartValue)
putInt("item_count", itemCount)
}
analytics.logEvent("checkout_started", bundle)
}
// Avoid: logging every screen view as a custom event
// (Firebase does this automatically via setCurrentScreen)
// Don't do this:
// analytics.logEvent("screen_viewed", Bundle().apply {
// putString("screen_name", "ProductListScreen")
// })
// Good: track important errors that affect user experience
private fun logPaymentFailed(errorCode: String, errorMessage: String) {
val bundle = Bundle().apply {
putString("error_code", errorCode)
putString("error_message", errorMessage)
}
analytics.logEvent("payment_failed", bundle)
}Notice we're not logging every button press or every network request. We're logging outcomes that matter. A payment failure matters because it directly affects your revenue. A user navigating between screens is expected behavior and doesn't need individual tracking.
A good heuristic: if you wouldn't write a dashboard to visualize it, don't track it. If you have more than 20-30 custom events, you're probably over-tracking.
Respect privacy and consent
Analytics touch user privacy. Depending on where your users are located, you may need explicit consent under GDPR, CCPA, or other regulations. Even where not legally required, respecting user choice builds trust.
The standard approach is to wrap analytics calls in a consent check:
object Analytics {
private var consentGranted = false
fun setConsentGranted(granted: Boolean) {
consentGranted = granted
}
fun logEvent(eventName: String, params: Bundle? = null) {
if (!consentGranted) {
return
}
// Only log if consent is given
FirebaseAnalytics.getInstance(context).logEvent(eventName, params)
}
}
// In your consent screen or settings
Analytics.setConsentGranted(userHasOptedIn)Beyond consent, be thoughtful about what personal data you attach to events. Avoid logging:
- Full email addresses or phone numbers
- User IDs (unless hashed and pseudonymized)
- Credit card or payment details
- Passwords or authentication tokens
- Location data (unless specifically relevant to your feature)
Many analytics platforms allow you to define data retention policies. Set a reasonable retention window—30 days for detailed events, 90 days for aggregated metrics. Older data often has limited value for decision-making anyway.
If you're building a fintech, healthcare, or other sensitive-data app, consider whether you need a privacy-focused analytics provider instead of a mainstream one. Some teams use on-device analytics aggregation, sending only summary statistics rather than individual events.
Use consistent event names
Event naming seems trivial but becomes critical once you have months of data and multiple engineers querying it. Inconsistent naming fragmentsyour datasets and makes dashboards unreliable.
Establish a naming convention and document it:
| Good | Bad | Why |
payment_completed |
paymentCompleted, payment_done, order_paid |
One source of truth; case-insensitive; verb-noun pattern |
feature_flag_evaluated |
feature_flag_check, featureFlagEvaluated |
Consistent tense and casing; easy to search |
error_occurred |
error, errorHappened, exception_thrown |
Descriptive enough; consistent across team |
We recommend snake_case (lowercase with underscores) for event names. It's readable, consistent with many analytics platforms' conventions, and less prone to typos than camelCase.
Create a central object that acts as a repository for event names and their schemas:
object AnalyticsEvents {
// Payment events
const val PAYMENT_STARTED = "payment_started"
const val PAYMENT_COMPLETED = "payment_completed"
const val PAYMENT_FAILED = "payment_failed"
// User engagement
const val FEATURE_USED = "feature_used"
const val CONTENT_SHARED = "content_shared"
// Errors
const val ERROR_OCCURRED = "error_occurred"
}
// Usage
fun recordPaymentCompleted(orderId: String, amount: Double) {
val bundle = Bundle().apply {
putString("order_id", orderId)
putDouble("amount", amount)
}
analytics.logEvent(AnalyticsEvents.PAYMENT_COMPLETED, bundle)
}This approach prevents typos and makes refactoring easier. Your teammates can see all available events in one place.
Document the expected parameters for each event. A simple comment or a shared spreadsheet goes a long way:
/**
* Logged when user completes a payment.
*
* Parameters:
* - order_id (String): unique identifier for the order
* - amount (Double): total payment amount in app currency
* - payment_method (String): "card", "wallet", "bank_transfer"
* - currency (String): ISO 4217 currency code
*/
const val PAYMENT_COMPLETED = "payment_completed"Review dashboards regularly
Dashboards are only useful if someone actually looks at them. We've seen teams set up comprehensive analytics infrastructure and then never open the dashboard again because:
- It takes 15 minutes to load
- The data is three days stale
- Nobody knows how to interpret the graphs
- The metrics don't align with current priorities
Build dashboards with a specific audience in mind. Your executive dashboard looks different from your engineering dashboard, which looks different from your product manager's dashboard.
For engineers, focus on:
- Error rates and types (funnel drop-offs)
- Performance metrics (crash rate, ANR rate)
- Feature adoption (is the new feature being used?)
For product teams:
- Conversion funnels
- User retention cohorts
- Session length and frequency
- Feature engagement
Set a cadence: review dashboards weekly or biweekly. Assign ownership. Someone should own the dashboard and have time to investigate anomalies. If a metric suddenly spikes or drops, that's a signal worth investigating.
Automate alerting for critical events. If your error rate exceeds 1% or your payment completion rate drops below 90%, you want to know immediately, not in a weekly meeting.
// Example: monitoring a critical metric in your backend
// (not Android-specific, but relevant)
fun shouldAlertOnPaymentFailureRate() {
val failureRate = getPaymentFailureRate()
if (failureRate > 0.05) { // 5% threshold
notifyOncallEngineer("Payment failure rate at ${failureRate * 100}%")
}
}Common mistakes
Mistake 1: Logging PII unintentionally. You add a user ID to events for debugging, but never remove it. Now you're storing personally identifiable information that you don't need. Solution: use a consistent user hash instead, and only for internal debugging events that never leave your servers.
Mistake 2: Creating dashboards without a question. Your analytics lead builds a beautiful dashboard with 12 metrics, but no one uses it because the business never defined what they wanted to measure. Solution: start with the question ("Did our retention improve?"), then build the dashboard to answer it.
Mistake 3: Logging synchronously on the main thread. Firebase Analytics handles this well, but if you're using a custom solution, blocking the main thread on analytics calls is a fast path to ANRs. Solution: use a background thread or queue for analytics calls.
Mistake 4: Never revisiting your event schema. You track "screen_viewed" for every screen, but six months later your product has pivoted twice and you're still collecting useless data. Solution: quarterly audit of what you're tracking and why.
Mistake 5: Not handling network failures gracefully. Your app loses analytics events when offline, or worse, crashes when analytics SDK fails. Solution: Analytics libraries should queue events and retry, but test this behavior explicitly.
Key takeaways
- Track 15-30 high-value business events, not hundreds. More data doesn't mean better insights.
- Implement consent checks and respect user privacy. Avoid logging PII.
- Use consistent, snake_case event names and document their parameters. Centralize event definitions in code.
- Set up dashboards with specific audiences in mind and review them on a fixed cadence.
- Start by asking what question you want answered, then design analytics to answer it.
- Audit your analytics quarterly. Remove events that aren't driving decisions.






Comments
Loading comments…