Tracking Without Taxonomy – A Data Dump
Standard events like screen_view and app_open only answer the question 'was the user in the app?' For business, what matters more is: how many reached payment, what content leads to subscription, at which point during registration users drop off. Custom event tracking provides answers, but only if the system is designed correctly.
Without a carefully thought-out taxonomy, tracking becomes a dump: 400+ unique event names, half of which are outdated, iOS and Android data are named differently, and no one can explain what btn_click_3 means. A typed wrapper over Firebase Analytics reduces tracking errors by 3x compared to raw calls via Bundle – this is confirmed by our experience on over 50 projects. Additionally, DebugView speeds up debugging 10x compared to manual log analysis.
Debug time savings reach 40%, and analytics costs are reduced by 20% due to automatic deduplication and built-in schema validation.
Why Standard Tracking Is Not Enough
screen_view shows that the user opened a screen, but does not answer the question 'what did they do there?' Custom event tracking captures specific actions: button tap, started filling a form, selected a plan. Without such events, it is impossible to build an accurate conversion funnel and identify bottlenecks. For example, a typical problem is the payment_succeeded event firing twice due to a race condition: once in the API response, another in the URLSession completion handler. Deduplication via a guard flag and transaction_id solves this, but if not architected properly, it will inflate conversion.
How to Develop Taxonomy and Property Schema
A good naming scheme is [object]_[verb] or [screen]_[action]:
product_viewed
product_added_to_cart
checkout_started
checkout_step_completed ← with property step_name
checkout_abandoned
payment_initiated
payment_succeeded
payment_failed ← with property error_code
subscription_started
subscription_cancelled
Anti-pattern: buttonClicked, screenOpened, userAction – these events say nothing without additional context.
The property schema must be documented or stored in a system like Avo.app 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 |
Platform Comparison: Firebase vs Amplitude
| Criteria | Firebase Analytics | Amplitude |
|---|---|---|
| Event type | via logEvent | via track |
| Deduplication | transaction_id | event_id |
| BigQuery export | built-in | via plugin |
| Cost | free up to 500 events/hour | paid per event count |
Platform choice depends on scale and budget. Firebase suits startups, Amplitude for products with high analytics requirements.
Implementation: Android + Firebase Analytics
According to the Firebase documentation, passing transaction_id allows deduplicating transactions on the platform side.
// 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())
}
}
}
We 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,
screenViews: false
)
)
)
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
]
)
}
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)
}
}
Deduplication and Testing
One common problem is an event firing twice. For example, payment_succeeded is called both on successful API response and in the URLSession completion handler. Solution – a guard flag:
// Android — ensure 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 allows deduplication at the analytics platform level.
Without verification, events go to production unchecked – and after a month you discover that 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
The tool Avo.app allows you to create an event schema and generate a typed SDK-wrapper for iOS/Android – schema violations are immediately visible at compile time.
Process and Timelines
- Design event taxonomy with the product team
- Create property schema with required and optional properties
- Implement typed wrapper over Firebase/Amplitude/Mixpanel
- Set up DebugView for event verification during development
- Configure BigQuery export for raw data
- Build initial conversion funnel dashboard
- Prepare event documentation for the team (QA, analytics)
Over the course of our work, we have completed more than 50 mobile tracking projects – from startups to large fintech products. We hold Firebase and Amplitude certifications.
Timelines: taxonomy and event schema – 1 day. Implementing wrapper and integration into codebase – 2–4 days. Cost is calculated individually. To evaluate your project, contact us – we will send a proposal within one business day.
Get a consultation on improving tracking in your app. If you already have an existing integration, we will evaluate it for free and suggest optimization options. Contact us for a free audit.







