» » Understanding Android Build Flavors very quickly

Understanding Android Build Flavors very quickly

Build Flavors is a technology that allows you to build multiple variants of an application with a common code base and shared resources.

Why Use Flavors

Flavors is your choice if:

  • you need 2 versions of the application - with basic and advanced functionality;
  • you need several versions of the application with different design or partially different functionality.


Flavors is not the best choice if you just need to have multiple application build options (with and without obfuscation, 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 in the src/main folder, as well as code and resources that are defined separately for each version (each flavor).


Project structure with two Flavors (assembly versions) - auidioBook and eBook

For example, you may have two applications about animals - about horses and about fish. The functionality of both is one Activity that 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 displays a drawable named animal (R.drawable.animal). And in the folders src/animalHorse/res/drawable and src/animalFish/res/drawable there will be pictures of a horse and a fish, respectively. However, they must have the same name = animal.

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

Practice

Step by step we will create and fill two Flavors, for example, animalHorse and animalFish.

Creating Flavors

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

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


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

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


Rebuild the project (Build -> Rebuild Project).
We go to the Build -> Select Build Variants... section. And we 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 select any of them, after which the studio will do a rebuild to build the project only from those files that belong to the selected version.

Overriding applicationId

First, I'll clarify that applicationId != packageName (packageName = the name of the folders where the project is located. all flavors must have the same packageName).

Second, you can either override the applicationId for each flavor completely, or you can add an 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 Code Sections


At the moment (03/01/2022) Android Studio itself does not know how to create folders based on 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 the 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 folders inside folders will be named differently), res/layout, res/drawable... (optional)
  4. In animalHorse and animalFish folders create AndroidManifest.xml file (required)




Ready. Now you can write general 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 the shared code that fills the screen with a Fragment named FragmentListAnimals. And define the fragment itself in both versions of the application separately - create the FragmentListAnimals class both in the src/animalHorse/java/my/app/com/fragments folder and in the src /animalFish/java/my/app/com/fragments folder .
The names of folders and files (classes) and their hierarchy in the src/main/java and src/animalHorse/java and src/animalFish/java folders must match.

About AndroidManifest


As mentioned above, AndroidManifest.xml must be created in all three folders - src/main, src/animalFish, src/animalHorse. At the same time, manifests in the src/animalFish and src/animalHorse folders can be empty (contain only the <manifest/> tag).
But if you, for example, 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 merged with the main manifest located in scr/main.
The rules for connecting manifests are well described here .

Creating BuildConfig Variables


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

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 your code, you can refer to this constant:

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

Tips and possible difficulties


  1. Most likely, when you start working with flavors, you will see how your code is not sufficiently divided into classes. And you will have to do a decent code review that will allow you to redefine a minimum of code for each version of the application.
  2. An obvious faux pas that I caught - 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, not one named my.app.com.
  3. Use Dagger/Hilt for dependency injection. This will make it much easier to organize different implementations for different versions.
  4. A complete list of build options that can be overridden for each flavor can be found in of. documentation (e.g. minSdkVersion, versionCode...)



Related Articles

Add Your Comment

reload, if the code cannot be seen

All comments will be moderated before being published.