Don’t forget to call your dad too 👴
Tags: android, java, tutorial, gradle
Published: 2019-09-24 17:44:25

In this series, I have described why I make the apps named **Call Mom** and **Call Dad**, and some of the issues with creating a phone call reminder app, like using persistant storage, showing notifications and setting alarms. This fourth and final part describes how you can build multiple apps with similar functionality without creating unnecessary code duplication. If you just want the summary and some links, go to the [Summary section](#summary) at the bottom.

# Don’t forget to call your dad too 👴

As I explained in the first article of this series, my reason for creating the **Call Mom** app was that my mother passed away. At first, I used the app for calling my dad, so I soon started looking into how to create two very similar apps with different names, and some small differences in text and color resources.

My first attempt involved a huge refactoring. All the parts common to both apps were broken out into its own module, without any resources, and every `Activity` class was made into an `abstract` class. Then, in each app module, a non-abstract implementation of each activity class was created, connecting it to the resources found in that app module. This method quickly became completely unsustainable, and I started looking for a better way.

## Flavor dimensions to the rescue

After a while, I learned about [product flavors](https://developer.android.com/studio/build/build-variants#product-flavors). This mechanism is mainly used for creating different app variants. If you want to publish a free app, and also provide a paid version with some more functionality, **product flavors** is probably what you are looking for. I had already started thinking about what I could add to my apps to make _"premium"_ versions to sell. This might not happen for **Call Mom** and **Call Dad**, but I did understand that the two apps could be realized as two different flavors of the same app.

This made the development much easier. Instead of three messy modules, I only needed one app module. The first step was to configure a **flavor dimension** named `callwho`, and then one **product flavor** for each app, where the last part of each app's package name is set:

```gradle
// build.gradle file for the app module - not the project

apply plugin: 'com.android.application'

android {
    defaultConfig {
        applicationId "se.atornblad"
        ...
    }

    flavorDimensions "callwho"

    productFlavors {
        calldad {
            dimension "callwho"
            applicationIdSuffix ".calldad"
        }
        callmom {
            dimension "callwho"
            applicationIdSuffix ".callmom"
        }
    }

    ...
}
```

The configuration shown above will create two different **"callwho"** flavors of my application:

* Product flavor "calldad", application id `se.atornblad.calldad`
* Product flavor "callmom", application id `se.atornblad.callmom`

With just this one configuration change, you can now compile and package two apps that are identical. Instant code sharing with zero code duplication. What makes this interesting is when you introduce the differences between the two apps flavors.

Start by creating flavor-specific resources for each app. If you have just recently created your app, you probably have one single `strings.xml` file that looks something like this:

```xml
<!-- This is ./src/main/res/values/strings.xml -->
<resources>
    <string name="app_name">Call Reminder App</string>
    <string name="action_settings">Settings</string>
    <string name="call_button_text">Call Now</string>
    ...
</resources>
```

A mistake that would be easy to make is to copy this file twice and change the values that are different between app flavors. That would introduce a lot of duplication. Instead, create one override xml file for each app. Start by creating two new `strings.xml` files:

* `./src/calldad/res/values/strings.xml`
* `./src/callmom/res/values/strings.xml`

```xml
<!-- This is ./src/calldad/res/values/strings.xml -->
<resources>
    <string name="app_name">Call Dad</string>
    <string name="call_button_text">Call Dad Now</string>
    ...
</resources>
```

```xml
<!-- This is ./src/callmom/res/values/strings.xml -->
<resources>
    <string name="app_name">Call Mom</string>
    <string name="call_button_text">Call Mom Now</string>
    ...
</resources>
```

After saving these files, when you look at the **Project** tool window, you will only see one of them. It will look something like this:

```
> values
      colors.xml
      dimens.xml
    > strings (2)
          strings.xml
          strings.xml (calldad)
      styles.xml
```

This is when I started thinking I had done something wrong. I double-checked and double-double-checked my `build.gradle` file, and meticulously checked the spelling of every directory and `strings.xml` file that I had. Don't worry! This is just Android Studio telling you which product flavor is the active one.

To switch between product flavors and build types, open the **Build Variants** tool window. That's where you can switch between your app's different variants. Switch from `calldadDebug` to `callmomDebug`, and your resources will look like this:

```
> values
      colors.xml
      dimens.xml
    > strings (2)
          strings.xml
          strings.xml (callmom)
      styles.xml
```

Go through the same process to create flavor-specific **layouts**, **drawables**, **menus** and other resource types.

If you want flavor-specific variants of your Java or Kotlin classes, create a folder called `./src/calldad/java` and mimic the directory structure of `./src/main/java`. If the flavor-specific `java` directory contains a file named exactly like in the non-specific `main` directory, the specific one will be used instead.

## Multidimensional app configurations 🧊

Android Studio is not limited to just one flavor dimension. You could, for example, create one `callwho` flavor to separate calling mom from calling dad, and another `market` flavor to separate a free ad-supported app from a paid premium one with extra features.

Add another dimension to your app module's `build.gradle` file:

```gradle
// build.gradle file for the app module - not the project

apply plugin: 'com.android.application'

android {
    defaultConfig {
        applicationId "se.atornblad"
        buildConfigField 'boolean', 'ENABLE_ADMOB', 'false'
        ...
    }

    flavorDimensions "callwho", "market"

    productFlavors {
        calldad {
            dimension "callwho"
            applicationIdSuffix ".calldad"
        }
        callmom {
            dimension "callwho"
            applicationIdSuffix ".callmom"
        }
        free {
            dimension "market"
            buildConfigField 'boolean', 'ENABLE_ADMOB', 'true'
        }
        premium {
            dimension "market"
            applicationIdSuffix ".premium"
        }
    }

    ...
}
```

This gradle configuration would produce four different apps:

* Call Mom (free), application id `se.atornblad.callmom`
* Call Dad (free), application id `se.atornblad.calldad`
* Call Mom (premium), application id `se.atornblad.callmom.premium`
* Call Dad (premium), application id `se.atornblad.calldad.premium`

One detail that you might notice is that I have created a build configuration field called `ENABLE_ADMOB`, which is set to `false` by default, but is set to `true` for the `free` flavor. To check this field in an `Activity` method, you can do something like this:

```java
@Override
protected void onCreate(Bundle savedInstanceState) {
  if (BuildConfig.ENABLE_ADMOB) {
    MobileAds.initialize(this);
    // ... continue to initialize AdMob ads
  }
}
```

# Summary

- Use product flavors to generate multiple variants of your app
- Override resources or code that are specific to each variant
- Put specific resources in the `./src/FLAVOR/res` directory
- Put specific code in the `./src/FLAVOR/java` directory
- Use as many flavor dimensions as you need

_Cover photo by [James Sutton](https://unsplash.com/photos/qXn5L9BqRbE) on [Unsplash](https://unsplash.com/)_