Implementing Custom Event Tracking for Business Metrics in Mobile Apps
Standard events like screen_view and app_open answer "was the user in the app?" Custom Event Tracking answers business questions: how many users reached checkout, which content type drives subscriptions, at which registration step users drop off.
Without a thoughtful event taxonomy, tracking becomes a dump: 400 unique event names, half outdated, iOS and Android data named differently, and nobody can say what btn_click_3 means.
Event Taxonomy
A good naming scheme: [object]_[verb] or [screen]_[action]:
product_viewed
product_added_to_cart
checkout_started
checkout_step_completed ← with step_name property
checkout_abandoned
payment_initiated
payment_succeeded
payment_failed ← with error_code property
subscription_started
subscription_cancelled
Antipattern: buttonClicked, screenOpened, userAction — these say nothing without extra context.
Property Schema
Each event has a set of properties. Define the schema in a document (or system like Avo.app, Segment Protocols) before implementation:
| Event | Required Properties | Optional |
|---|---|---|
product_viewed |
product_id, product_name, category |
source, position |
checkout_started |
cart_total, item_count |
promo_code |
payment_succeeded |
order_id, total, currency, payment_method |
installments |
subscription_started |
plan_id, billing_period, price |
trial_used |
Implementation: Android + Firebase Analytics
// Wrapper over FirebaseAnalytics for type safety
object Analytics {
private val firebaseAnalytics = FirebaseAnalytics.getInstance(context)
fun trackProductViewed(product: Product, source: String) {
firebaseAnalytics.logEvent("product_viewed") {
param("product_id", product.id)
param("product_name", product.name)
param("category", product.category)
param("price", product.price)
param("currency", "USD")
param("source", source)
}
}
fun trackCheckoutStarted(cart: Cart) {
val items = cart.items.mapIndexed { index, item ->
Bundle().apply {
putString(FirebaseAnalytics.Param.ITEM_ID, item.productId)
putString(FirebaseAnalytics.Param.ITEM_NAME, item.name)
putDouble(FirebaseAnalytics.Param.PRICE, item.price)
putLong(FirebaseAnalytics.Param.QUANTITY, item.quantity.toLong())
putLong(FirebaseAnalytics.Param.INDEX, index.toLong())
}
}
firebaseAnalytics.logEvent(FirebaseAnalytics.Event.BEGIN_CHECKOUT) {
param(FirebaseAnalytics.Param.VALUE, cart.total)
param(FirebaseAnalytics.Param.CURRENCY, "USD")
param(FirebaseAnalytics.Param.ITEMS, items.toTypedArray())
}
}
}
Use standard constants FirebaseAnalytics.Event.* and FirebaseAnalytics.Param.* for e-commerce events — they automatically map to Google Ads and BigQuery without additional setup.
Implementation: iOS + Amplitude
// Amplitude SDK v1.x (Swift)
import AmplitudeSwift
final class AnalyticsService {
static let shared = AnalyticsService()
private let amplitude = Amplitude(
configuration: Configuration(
apiKey: "YOUR_API_KEY",
defaultTracking: DefaultTrackingOptions(
sessions: true,
appLifecycles: true,
deepLinks: false, // manage manually
screenViews: false // manage manually
)
)
)
func trackPaymentSucceeded(order: Order) {
amplitude.track(
eventType: "payment_succeeded",
eventProperties: [
"order_id": order.id,
"total": order.total,
"currency": order.currency,
"payment_method": order.paymentMethod.rawValue,
"item_count": order.items.count
]
)
}
// Global user properties
func setUserProperties(user: User) {
let identify = Identify()
identify.set(property: "plan", value: user.plan.rawValue)
identify.set(property: "registration_date", value: user.registrationDate.iso8601)
amplitude.identify(identify: identify)
}
}
Event Deduplication
A common issue — an event fires twice. For example, payment_succeeded called both on successful API response and in URLSession completion handler:
// Android — guarantee single send
class CheckoutViewModel : ViewModel() {
private var paymentEventSent = false
fun onPaymentSuccess(order: Order) {
if (paymentEventSent) return
paymentEventSent = true
Analytics.trackPaymentSucceeded(order)
}
}
For e-commerce events, Firebase recommends passing transaction_id — this deduplicates at the analytics platform level.
Event Tracking Testing
Without verification, events go to production unchecked — and a month later you discover purchase on iOS and payment_success on Android are the same event with different names.
# Firebase DebugView — enable on device
adb shell setprop debug.firebase.analytics.app com.myapp
# Amplitude — debug mode
amplitude.configuration.logLevel = LogLevelEnum.DEBUG
Avo.app lets you create an event schema and generate a typed SDK wrapper for iOS/Android — schema violations are caught at compile time.
What We Do
- Design event taxonomy with product team
- Create property schema with required and optional fields
- Implement typed wrapper over Firebase/Amplitude/Mixpanel
- Set up DebugView to verify events during development
- Configure BigQuery export for raw data
- Create initial conversion funnel dashboard
Timeline
Event taxonomy and schema: 1 day. Wrapper implementation and codebase integration: 2–4 days. Pricing is calculated individually.







