Understanding Android Build Flavors very quickly

Build Flavors – a technology that allows you to collect multiple variants of an application with a common code base and shared resources.
The reason for the appearance of the article is the confusion and unnecessary complexity for such a simple topic of the official documentation. In a short post of my Android development channel it didn’t fit, so it’s almost a full-fledged article.

Why use Flavors

Flavors are your choice if:

  • you need 2 versions of the application – with basic and advanced functionality;

  • you need several versions of the application with different designs or partially different functionality.

Flavors are not the best choice if you just need to have multiple build options for your application (with obfuscation enabled and disabled, for example). In this case, look towards Build Types (default: debug, release)

General concept

The idea is very similar to the now fashionable Kotlin Multiplatform – you have common code and resources located in the src / main folder, as well as code and resources that are defined separately for each version (each flavor).

Project structure with two Flavor (build versions) - auidioBook and eBook
Project structure with two Flavor (build versions) – auidioBook and eBook
for instance

For example, you may have two applications about animals – about horses and about fish. The functionality of both is one Activity, which displays a picture of an animal.

You create two Flavors – AnimalHorse and AnimalFish

The src / main / java folder will contain the code of the Activity itself, which outputs a drawable named animal (R.drawable.animal). And the folders src / animalHorse / res / drawable and src / animalFish / res / drawable will contain pictures of a horse and a fish, respectively. However, they must have the same name = animal.

When you build apk, resources are put into it only for the selected Flavor – the rest are simply ignored.

Practice

Let’s create and fill two Flavors step by step, for example animalHorse and animalFish.

Create Flavors

In the build.gradle file (module level), you need to declare flavorDimensions – the group that unites our flavors (required):

android {
		...
    flavorDimensions "animals"
    ...
}

Next, we add the productFlavors block to the android block, declare our flavors and be sure to prescribe which group they belong to.

android {
		...
    flavorDimensions "animals"
  	productFlavors {
        animalHorse {
            dimension "animals"
        }
        animalFish {
            dimension "animals"
        }
    }
    ...
}

Rebuild the project (Build -> Rebuild Project).

Go to the Build -> Select Build Variants … section and see that 4 build options have appeared:

  • animalHorseDebug

  • animalHorseRelease

  • animalFishDebug

  • animalFishRelease

That is, we will always have n = Build Types * Build Flavors of project build options.

You can choose any of them, after which the studio will rebuild to build the project only from those files that belong to the selected version.

Overriding applicationId

Firstly, I’ll clarify that applicationId! = packageName (packageName = the name of the folders in which the project is located. All flavors must have the same packageName). A post about this in my tg-channel with pictures: https://t.me/dolgo_polo_dev/45

Secondly, you can either override applicationId for each flavor completely, or you can add applicationIdSuffix for each flavor.

android {
    defaultConfig {
        applicationId "my.app"
    }
		...
    flavorDimensions "animals"
  	productFlavors {
        animalHorse {
            dimension "animals"
            applicationId "my.app.horse"
        }
        animalFish {
            dimension "animals"
            applicationIdSuffix ".fish"
        }
    }
    ...
}

In the second option, applicationId = applicationId (from the defaultConfig block) + applicationIdSuffix

Create sections with code

At the moment (12/10/2021) Android Studia itself does not know how to create folders according to the declared Flavors, so we will create them manually.

ps it is not necessary to create folders and files that you are not going to override

  1. Open the project structure in Project mode.

  2. Create animalHorse and animalFish folders in the app / src folder.

  3. In the created folders, create folders java / my / app / com (if packageName! = “My.app.com”, then the folders inside folders will be named differently), res / layout, res / drawable … (not necessary)

  4. In the animalHorse and animalFish folders create an AndroidManifest.xml file (necessarily)

Ready. You can now write generic code in the src / main folder and miscellaneous code in the src / animalHorse and src / animalFish folders.

For example, you can store an Activity in shared code that displays a Fragment named FragmentListAnimals in full screen. And define the fragment itself in both versions of the application separately – create the FragmentListAnimals class and in the folder src / animalHorse / java / my / app / com / fragments, and in the folder src / animalFish / java / my / app / com / fragments

Names of folders and files (classes) and their hierarchy in folders src / main / java and src / animalHorse / java and src / animalFish / java must match.

About AndroidManifest

As stated above, AndroidManifest.xml must be created in all three folders – src / main, src / animalFish, src / animalHorse. In this case, manifests in the src / animalFish and src / animalHorse folders can be empty (contain only the tag).

But if, for example, you need permission to access the camera in only one version of the application, then you can edit the manifest in the desired folder. When building the application, this manifest will be smerdjen with the main manifest lying in scr / main.

The rules for joining manifests are well documented here

Creating BuildConfig Variables

You can create constants that change depending on the selected assembly. To do this, in the gradle file, create a buildConfigField for both flavors:

android {
    defaultConfig {
        applicationId "my.app"
    }
		...
    flavorDimensions "animals"
  	productFlavors {
        animalHorse {
            dimension "animals"
            applicationIdSuffix ".horse"
            buildConfigField "String", "URL", ""https://animals.horse.ru""
        }
        animalFish {
            dimension "animals"
            applicationId "my.app.fish"
            buildConfigField "String", "URL", ""https://animals.fish.ru""
        }
    }
    ...
}

Now, anywhere in the code, you can refer to this constant:

 private fun initRetrofit() {
       	...
        val retrofit = Retrofit.Builder()
            .baseUrl(BuildConfig.URL)
            .build()
 }

Tips and Potential Difficulties

  1. Chances are, when you start working with flavors, you will see how little your code is broken down into classes. And you will have to do a decent code review, which will allow you to override a minimum of code for each version of the application.

  2. The obvious fokap I caught is when you create folders for each version, make sure to create subfolders, not just one. If the package is called my.app.com, then there should be my -> app -> com folders, and not one named my.app.com.

  3. Use Dagger / Hilt for dependency injection. This makes it much easier to order different implementations for different versions.

  4. A complete list of build parameters that can be overridden for each flavor can be found in of. documentation (e.g. minSdkVersion, versionCode …)

And shorter posts about mobile development with pictures can be read tut

Similar Posts

Leave a Reply