White label structure for Android

In the projects we worked on, I often faced the need to create multiple builds of one application for different clients. Android SDK is very powerful in such situation and exposes many possibilities.

Here a proposition of different solutions for an Android application that is expected to be built into many different targets.
The main purposes I see are :

  • reducing the number of code lines
  • propagating the bug fixes to every flavour
  • isolating the specific codes of one flavour

Some definitions are needed, taken from the the Android Documentation:

  • Flavour: A product flavor defines a customized version of the application build by the project.
  • Build Type: A build type allows configuration of how an application is packaged for debugging or release purpose.
  • Build Variant: This is a combination af a flavour and a build Type.
      

White label sample

This article is based on a fake sample: a white label application that is declined for different restaurants, among which “PizzaGood”, “Kebab200” and “JapSushi”. The design will change as well as the available features.

Building the variant with gradle

  1. Create the main folder as well as one folder for each white flavour, in the “src” folder of the project.

  2. Edit your project build.gradle to configure the flavour

1
2
3
4
5
6
7
8
9
10
productFlavors {
main {
versionName "6.0.0"
packageName "eu.hithredin.sample"
}
pizzaGood {
versionName "1.0.1"
packageName "com.pizzagood"
}
}

Implementing specific features

The config file method

A good solution consists on implementating a custom config file that is read dynamically at startup. The code will be activated and parametrized according to this config.

PROS:

  • Only one code to handle all the specific features
  • Easy to re-configure and create a new white label

CONS:

  • Impose a reflexion to generify the specific features
  • Complex if the specific requirement is a big feature

HOWTO:

  1. Create a Json config file in the “res/raw” folder of each flavour
1
2
3
4
5
6
7
8
9
10
11
{
"flavorSearch": {
"filters": ["ingredient", "price"],
},

"flavorGlobal": {
"facebookEnabled": false,
"subscribeEnabled": true,
"noteAppEnabled": false,
"shareAppEnabled": false
}

}

  1. Create a singleton manager, named FlavorParam, which is basically a model binded to the Json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class FlavorParam {

private static FlavorParam instance;

public FlavorSearch flavorSearch;
public FlavorGlobal flavorGlobal;

public static void init(Context ctx) {
//Load config
Gson gson = new Gson();
try {
InputStream inStream = ctx.getResources().openRawResource(R.raw.flavor_param.json);
instance = gson.fromJson(BaseUtils.inputStreamToString(inStream), FlavorParam.class);
} catch (Exception e) {
Log.e("AppContext", "FATAL error, cannot load AppContext");
e.printStackTrace();
}
}

public static FlavorParam get() {
return instance;
}

public static class FlavorGlobal implements Serializable {
public boolean subscribeEnabled;
public boolean facebookEnabled;
public boolean noteAppEnabled;
public boolean shareAppEnabled;
}

public static class FlavorSearch implements Serializable {
public List<String> filters;
}

}
  1. Implement the thing, for exemple the activation of a Facebook login for PizzaGood but not Kebab200.
1
2
3
if(FlavorParam.get().flavorGlobal.facebookEnabled()){
showFacebookLogin();
}

  

The build constants method

Use the gradle system to send the configuration of the flavour.

PROS:

  • Very efficient for constants
  • Easy to re-configure and create new white label

CONS:

  • No code, just data

HOWTO:

Just add a line to the gradle configuration:

1
2
3
4
5
6
7
productFlavors {
pizzaGood {
versionName "1.0.1"
packageName "com.pizzagood"
buildConfigField "String", "PUSH_SENDER_ID", '"1010010404040"'
}
}

That can be easily accessed in Java:

1
GCMRegistrar.register(context, BuildConfig.PUSH_SENDER_ID);

  

The flavour file resolution method

Each xml or java file with a specific behaviour is duplicated in the flavour corresponding folder

PROS:

  • Very flexible: do what you want
  • Very efficient for styles, images and sizing customisation

CONS:

  • Code is duplicated
  • Specific code cannot be shared between multiple while label
  • No bug fix or new features propagation

How to reduce duplication

  • Do not hesitate to create multiple files for the same fonctions. For instance, create two dimen.xml files, one with generic values and the other one for specific values.
  • Highly rely on the composite design pattern, and divide your once unique class into multiple features class. Each class can then be redefined in the flavour without recoding everything
  • If not wanted, in the main project create an empty class that extends the real class, and use this one. This empty class can be redefined in the flavour. It will be able to keep or override all the generic code.

TODO UML diagram
  

The custom global code

Use a simple “if” each time you write a new feature (LabelManager.isPizzaGood())

PROS:

  • Very flexible

CONS:

  • The code is spread everywhere
  • Difficult to debug
  • Difficult to add a new white label
  • Complex if the specific requirement is a big feature

I would definitly not recommend to use this method. Easy at the beginning but the developer will faced parts of specific code everywhere which makes a new white label quite an horror to add.
  


Which solution for which case?

Cases Flavour file resolution Gradle build file Config file method
Resources (color, drawable, string, dimen, ….) V
Constants configuration V
Independant Features activated for multiple flavour V
Complex features that are specific to one flavour only V