Clean and user friendly Android app tracking with Firebase
Usually companies and developers care about the performance of their products or services and how the user interacts with them. The most basic things that come to one’s mind are screen/page views and crashes and interactions like clicking a link or button. To measure these values there are several solutions out there. We could use server side tracking and check how often a page is requested or client side tracking to send events to a server.
One of the solutions for client side tracking is Google Firebase Analytics/Crashlytics, which we will take a look at today. We will learn some basic steps to set it up for an Android application and after that look at how we can improve our most basic implementation.
Set up Analytics and Crashlytics
First step is to get everything up and running. You can skip this if you just came for the “clean and user friendly tracking” part of this post. Also, we got to keep in mind the restrictions for our projects:
- com.android.tools.build:gradle v3.2.1+
- compileSdkVersion 28+
- Gradle 4.1+
I will summarize everything shortly. You can find a detailed guide for the setup here.
1. Add the dependencies to your app
Top level build.gradle:
buildscript {
repositories {
...
google()
...
}
dependencies {
...
classpath 'com.google.gms:google-services:4.3.3'
...
}
}
allprojects {
...
repositories {
...
google()
...
}
...
}
App level build.gradle:
dependencies {
implementation 'com.google.firebase:firebase-analytics-ktx:x.x.x'
implementation 'com.google.firebase:firebase-crashlytics:x.x.x'
}
2. Register your project and add the google-services.json
To use Google Firebase you need to register your application and download the google-services.json.
Tracking interactions and crashes
Now that we added Firebase Crashlytics and Analytics to our project, we are able to get information about crashes and user interaction.
Crashes should now be tracked automatically and you can see an initialization message in logcat.
To track events you can now use analytics like so:
Firebase.analytics.logEvent(FirebaseAnalytics.Event.SELECT_ITEM)) {
param(FirebaseAnalytics.Param.ITEM_ID, "product-1337")
param(FirebaseAnalytics.Param.ITEM_NAME, "Cool product")
param(FirebaseAnalytics.Param.CONTENT_TYPE, "product")
}
The problem: We are now tracking without user consent
To not violate the law in some countries or just to show that we care about our users, we want to ensure that we only track events and crashes if the user allows us to. To do this we have to make some changes to our Analytics and Crashlytics implementations.
1. Set tracking disabled by default
Add following lines to your AndroidManifest.xml file to disable tracking by default.
<manifest ...>
<application ... >
<meta-data
android:name="firebase_analytics_collection_enabled"
android:value="false" />
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="false" />
</application>
</manifest>
2. Ask for consent and enable tracking
To ask the user for consent, we can simply show them a dialog and take action on positive or negative button presses.
MaterialAlertDialogBuilder(this).apply {
setTitle(R.string.title)
setMessage(R.string.summary)
setPositiveButton(android.R.string.yes) { dialogInterface: DialogInterface, _: Int ->
//Enable tracking and crash collection
Firebase.analytics.setAnalyticsCollectionEnabled(true)
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true)
dialogInterface.dismiss()
}
setNegativeButton(android.R.string.no) { dialogInterface: DialogInterface, _: Int ->
//Disable tracking and crash collection
Firebase.analytics.setAnalyticsCollectionEnabled(false)
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(false)
dialogInterface.dismiss()
}
}.create().show()
Wrap it up
Since we now know how to add, enable and disable tracking it is time for cleaning our code up and make things easier for us in the future. Since we might at one point want to change the tracking provider to get other features, better prices or better integrations with other systems, we should prepare for that case. We should not bloat our code with calls to Firebase at any point.
What we can do instead is to create a simple wrapper class, which hides the implementation details of Firebase. A simple implementation could look like this:
class AppTracker(
private val resources: Resources,
private val preferences: SharedPreferences,
private val firebaseAnalytics: FirebaseAnalytics,
private val crashlytics: FirebaseCrashlytics
) {
private var isEnabled: Boolean = preferences.getBoolean(
resources.getString(R.string.preference_tracking_enabled), false
)
fun setTrackingAndCrashReportsEnabled(enabled: Boolean) {
isEnabled = enabled
preferences.edit {
putBoolean(resources.getString(R.string.preference_tracking_enabled), enabled)
}
firebaseAnalytics.setAnalyticsCollectionEnabled(enabled)
crashlytics.setCrashlyticsCollectionEnabled(enabled)
}
fun setCurrentScreen(activity: Activity, screenName: String) {
if (!isEnabled) return
firebaseAnalytics.setCurrentScreen(activity, screenName, null)
}
fun trackEvent(eventType: String, eventName: String, eventParameter: Map<String, String>) {
if (!isEnabled) return
firebaseAnalytics.logEvent(eventType) {
param(eventName, eventName)
eventParameter.forEach {
param(it.key, it.value)
}
}
}
fun trackException(
priority: Int,
tag: String?,
message: String,
throwable: Throwable?
) {
if (!isEnabled) return
crashlytics.apply {
setCustomKey(CRASHLYTICS_KEY_PRIORITY, priority)
setCustomKey(CRASHLYTICS_KEY_TAG, tag ?: "")
log(message)
throwable?.let { recordException(it) }
}
}
fun shouldAskForUserConsent(): Boolean {
return !preferences.getBoolean(
resources.getString(R.string.preference_tracking_enabled),
false
) && !preferences.getBoolean(
resources.getString(R.string.preference_tracking_enabled_do_not_show_dialog),
false
)
}
}
Additional note
I use this pattern every time I need to have tracking or logging in an app, since it makes things easy as soon as you have to change the library you are using. Also, please take care of asking your users for consent before doing any tracking. Value their data as much as you value their money.