Google offers various services one might want to add to an app. For example:

Firebase Analytics provides insights about an app’s usage

Firebase Crash Reporting provides insights about app stability

Firebase Remote Config allows to change an app’s behavior and look without performing an update; it also allows for example a/b testing

Google AdMob allows to monetize an app

Firebase Analytics replaces Google Analytics for mobile apps on both Android and iOS. This was formally declared at Google I/O 2016 just 3 months ago and many examples still show the old way of doing things. At least for now Google Analytics is still used for web apps which adds to the confusion.

Users can opt out of tracking

This post lays the foundation for integrating Google Play Services and adds tracking of user behaviour through Firebase Analytics. A later post will add Google AdMob and Firebase Crash Reporting to the app.

In this case the first step is to add app flavors because that enables providing multiple versions of an app – like a paid and a free version. Regarding that “flavors” terminology:

Flavors of an app differ by functionality like a paid and a free flavor

Build types control the packaging of an app like a debug and a release build

Each combination of a flavor and a build type is a variant

As you can have multiple flavor dimensions – like paid vs. free and normal vs. mock – things can get complicated. And if you don’t define flavors you get a default one with no name.

1. Create App Flavors

DbTradeAlert will have three flavors:

The current flavor – called “naked” from here on because Gradle doesn’t like “default”

A playStore flavor which adds tracking

A withAds flavor which builds on the playStore flavor and adds advertisements – to be added in a later post

The naked flavor serves as a reference to debug flavor specific problems and will not be available in the Play Store. The playStore flavor will fulfill my curiosity about the feedback one gets from the Play Store. In addition to that I’m interested in usage scenarios and add tracking for them.

App flavors are controlled by Gradle. One reason is that Java has no preprocessor and therefore no conditional compiles. The general steps to create a flavor are:

Add the flavors to build.gradle – only specify what is flavor specific to override defaultConfig settings

A common misconception is that flavors override or overwrite methods or classes of the main directory structure. They don’t but instead are added to the code in main creating so-called source sets. So while classes in a flavor can extend those in main you cannot have two casses with the same name.

In contrast to that resources are merged. That means for example a flavor specific resource will replace a general resource.

I read “somewhere” that Gradle has a problem if flavor names start with a capital letter. As Gradle seems to be a moving target with bugs and functionality coming and going anyway I’m playing it safe and use only lowercase flavor names.

1.1. Add the playStore Flavor to build.gradle

The first step is to add the productFlavors closure to app/build.gradle (Module: app):

If necessary choose the right destination directory – “..\app\src\playStore\java\de\dbremes\dbtradealert” – in the Choose Destination Directory window and click OK

The Project view should now list a new package named “de.dbremes.dbtradealert (playStore)” with PlayStoreHelper.java in it.

To make the app’s various versions easier to discern let’s also add flavor specific labels for the app and its main screen.

1.3 Create playStore Specific Resources

The idea is to have different names for the app’s flavors:

“DbTradeAlert” for the playStore flavor

“DbTradeAlert N” for the naked flavor

“DbTradeAlert A” for the withAds flavor

These names will be used for both the app and its main activity. The app’s name shows up for example in the Settings screen’s app list while the activity’s name shows up in the launcher. That’s because activities are meant to be used to start an app – I found that only later and should have used fragments instead of activities.

Both names come from “app_name” in values/strings.xml – to see it in AndroidManifest.xml click on the respective value. The flavor specific names will make use of reource merging – the default value in strings.xml will automatically be replaced by the value in the flavor specific strings.xml.

Currently the playStoreDebug variant should be selected – otherwise select it. After that:

In the app context menu select New | Android resource directory

In the New Resource Directory window select the Source set “playStore” and click OK

This created the directory “… DbTradeAlert\app\src\playStore\res\values”.

Now add the new resource file:

In the app context menu select New | Android resource file

In the New Resource File window

Enter “strings.xml” as File name

Select Source “set playStore”

Click OK

This created a new file which shows up as “strings.xml (playStore)” in Android Studio’s Project view.

And finally create flavor specific names:

Copy the element defining app_name from the old strings.xml to the new one

In the old strings.xml change app_name to “DbTradeAlert N”

Whenever you build the APK from now on both its titles will depend on the selected app variant. As the other value is missing from the new strings.xml the general one will be used irrespective of the flavor being built.

To try it out build a naked variant and install it – it should show up as “DbTradeAlert N”. The playStore variants still show as “DbTradeAlert”.

2. Integrate Google Play Services

Google Play Services provide the base for many Google and Firebase services. If an app uses Google Play services it needs:

The google-services.json configuration file in place

The dependencies specified in project and app Gradle files

The playStore flavor of DbTradeAlert will use Firebase Analytics which is part of Google Play services.

First make shure your device or emulator came with Google Play store / Google APIs. It seems to be possible to get around that requirement for Firebase Analytics but I didn’t try.

After that download the .json configuration file. That requires the Google developer account created in the sections before.

“apply plugin: ‘com.google.gms.google-services'” enables Gradle to create Google Services related resources – we’ll come back to that later for the naked flavor. It’s important to add it at the bottom of build.gradle and not at the top. Also note that Firebase Analytics which are in firebase-core are only linked to the playStore flavor.

Now let Gradle sync its files and fix any errors it reports. It will complain if google-services.json isn’t in the right place and even point out newer versions of the libraries. Should Gradle report an error like “No matching client found for package name …” when building the variant the applicationId in build.gradle doesn’t match package_name in google-services.json.

This ignores google-services.json files in any directory. Like the passwords you want the keys in google-services.json to stay private.

3. Implement Tracking

Finally it’s time to actually implement the flavor specific functionality. In this case that’s informing Firebase Analytics every time the user taps the Refresh button or opens a screen. The main idea is that they shouldn’t feel the need to refresh quotes manually and if they do there’s something wrong with them the app.

To confine functionality from Firebase Analytics to the playStore flavor code calling the respective APIs needs to reside in files that only the playStore flavor contains. So create a class named PlayStoreHelper in the respective package – “de.dbremes.dbtradealert (playStore)” in my case:

DbTradeAlert logs each user interaction as a Firebase SELECT_CONTENT event specifying CONTENT_TYPE and ITEM_ID. You are free to come up with your own events and parameters but the reports in Firebase console currently won’t show them. They actually only report CONTENT_TYPE for the SELECT_CONTENT event but that’s OK for now.

In a real-world scenario the marketing people would want to slice and dice the raw data anyway either in BigQuery or in a spread sheet after downloading them. Note that you cannot download raw data from Firebase directly but have to go through BigQuery which requires the pay-as-you-go Blaze plan.

Another important fact is that data will take several hours to show up in the Firebase website. For that reason it’s advised to use logcat for a first check. Enable local Firebase logging with:adb shell setprop log.tag.FA VERBOSEadb shell setprop log.tag.FA-SVC VERBOSE

Before running the new variant it’s a good idea to delete the old app from the device to avoid confusion. After that run the app in debug mode and tap the Refresh button. The logcat output from Firebase should look like this – some identifiers were replaced with “nnnnn” to protect the innocent:

Now Android Studio will still complain about a missing google-services.json file and an unresolved R symbol. To remedy that comment the line applying the google-services plugin in build.gradle out – I haven’t found a way to have Gradle apply plugins flavor specific.

After that let Gradle resync, remove the currently installed variant from the device, and let Android Studio install the new one. When you tap the Refresh button there should be no logcat activity pointing to Firebase Analytics.

4. Let Users Opt out of Tracking

Now that Firebase Analytics was added successfully it’s time to deactivate it – well, actually to allow users to opt out like the screenshot at the post’s beginning showed. This is actually a bit develish as the option’s setting of course has to be tracked. Implementing this consists of:

Add a Firebase user property to track the option’s value for each user

4.1 Add a Firebase User Property

Add a new user property specifying a name (up to 25 characters) and a description (up to 150 characters)

Note that the name is case sensitive and it will take a few hours for user property data to show up. And maybe even more important: you cannot rename or delete a user property and they are limited to 25 per app.

Please note also that you need at least 10 users for data from user properties showing up in Firebase Analytics. That makes testing a bit harder as filters and audiences won’t be functional without that data.

4.2 Add Settings to Control DbTradeAlert’s Tracking

There are no built-in options to add a flavor specific setting to preferences.xml – unfortunately preference files don’t provide merging options or access to flavor settings like AndroidManifest.xml. In any case having an almost identical preference file for each flavor is usually the worst idea.

DbTradeAlert gets lucky here because it only needs to remove a preference at runtime. If an app has to add a preference back getting the order right can be tricky.

This code uses the automatically generated BuildConfig class to determine the app’s flavor. Note that getPreferenceScreen() is not available in SettingsActivity and has been deprecated there in API level 11.

4.3 Add Code to Control Google’s Tracking and to Set the Firebase User Property

Flavor specific code can now use the infrastructure built in the previous sections. The first step is to extend PlayStoreHelper for the playStore flavor: