Product Flavors — Simple Sample
This story explains Android Product flavors with a simple Sample Application.
Use Case:
I have a requirement where I need to fetch data from the backend using retrofit/any other network API and populate my user interface.
Challenges:
The backend is not yet ready and I will not be able to fetch data from the back end and populate my user interface.
Though my development is ready, I can’t test my user interface.
One of the Solution :
Rather than waiting till backend development is done, it is better to test the app with mock JSON without any network interaction.
This helps to test the user interface flow and also helps to fix the defects that encounter.
Once the back end is ready, we need to replace the mock JSON code with real API code.
Problem with the above approach:
The problem with the above approach is we need to replace the code that contains mock JSON with API interaction code. Also if after some time if the backend is down and API does not work again we need to modify the code related to API interaction and replace it with mock JSON code to proceed with app development.
Solution 1: Maintain two codebases
Maintain 2 codebases, one for mock JSON and another with API interaction.
The problem with this approach is difficulty in maintenance. If there is some change in the UI layer, we need to modify both the very difficult codebases.
Solution 2: Product flavors
Android Product Flavors are used to create different app versions from a single code base. App versions can be free or paid. They can have different themes and texts. They can use different environments or APIs similar to our requirements.
Simply put, a product flavor is a variant of your app. It is very useful when you want to create multiple versions of your app. This means you can generate different versions or variants of your app using a single codebase.
Product flavors are a powerful feature of the Gradle plugin from Android Studio to create customized versions of products. They form part of what we call Build Variants.
Build variants are the result of Gradle using a specific set of rules to combine settings, code, and resources configured in your build types and product flavors. Although you do not configure build variants directly, you do configure the build types and product flavors that form them.
Configure Product for the use case:
We need to configure product flavors for the above use case, which means we create two build variants one to fetch data from real API and other build variants to fetch data from mock JSON.
The Architecture that will be used is MVVM, in which ViewModel interacts with the repository which in turn interacts with real API or mock JSON to return data to viewmodel which will be observed by activity/fragment to populate UI.
So, we need to need to have a repository class in each of the product flavors with the same name, same package(I am using the same package). When a particular build variant is selected, the repository is picked from that particular flavor and data will be fetched accordingly.
Steps to implement product flavor:
Step 1: Add the below block of code in app-level Gradle in android block
flavorDimensions "fetchdata"
productFlavors {
real {
dimension "fetchdata"
applicationIdSuffix ".real"
}
mock {
dimension "fetchdata"
applicationIdSuffix ".mock"
}
}
The above code block contains 2 product flavors real and mock, real fetches data from the back end whereas mock fetches data from the mock JSON file present in the assets folder.
All flavors must belong to a named flavor dimension, which is a group of product flavors. You must assign all flavors to a flavor dimension; otherwise, you will get the build error. If a given module specifies only one flavor dimension, the Android Gradle plugin automatically assigns all of the module’s flavors to that dimension.
application suffix: It specifies the package name for each flavor. Suppose if the application has applicationId as “com.android.productflavorsdemo” under default config, then for each of the product flavor a suffix is added as below :
when ‘real’ product flavor is selected we see the package as com.android.productflavorsdemo.real and if mock flavor is selected “com.android.productflavorsdemo.mock”
Step2: After completion of step1, perform Gradle sync:
In Android Studio: Build -> Select Build Variant, we should be able to see like below:
Each product flavor generates 2 build variants, one for Debug and the other for Release build types. So a total of 4 build variants.
Step 3: Source code structure.
Create 2 directories/folders under src directory as below so that we can see 2 new directories with name real and mock along with main as below:
Step 3: The package structure for the main is as below. Observe “Repository” folder is empty since we use product flavors to fetch data from different sources based on selected build variants.
Step 4: Structure for mock product flavor:
The assets folder contains mock JSON and the utils folder contains JSONHelper that reads data from JSON.
The PlaceHolderRepository class fetches data from JSonHelper. Please find below the code sample for the repository class:
class PlaceHolderRepository(private val apiServiceAPI: PlaceHolderServiceAPI) {
init {
println("From Mock ") //Debug stmt that is displayed when mock flavor repository is executed.
}
suspend fun fetchPosts(): Response<PlaceHolderPostsDataModel> = JSONHelper.fetchParsedJSONForPosts()
suspend fun fetchComments(): Response<PlaceHolderCommentsModel> = JSONHelper.fetchParsedJSONForComments()
}
Step 4: Structure for real product flavor:
Observe in the above screenshot the real product contains only the Repository class.
The source code will be as below as it fetches data from real service:
class PlaceHolderRepository(private val apiServiceAPI: PlaceHolderServiceAPI){
init {
println("From Real ")
}
fun displayInfo() = "From Real"
suspend fun fetchPosts(): Response<PlaceHolderPostsDataModel> = apiServiceAPI.fetchPlaceHolderPosts()
suspend fun fetchComments(): Response<PlaceHolderCommentsModel> = apiServiceAPI.fetchPlaceHolderComments()
}
//Fetches data from backend using Retrofit library
Output :
When a specific build variant is selected, data is fetched accordingly. Can check-in logcat whether data is received from mock or real when build variant is selected as log statement is executed in the init block of Repository class?
Note: Similarly if we want a build variant to fetch data from prod server/syst server or white labeling, paid / free versions of the app create different product flavors and move flavor specific code to corresponding product flavor directories.
Source for the above sample -> Available in Github: