It’s been a while since my last blog post but I was busy figuring out how to develop multiple flavors of Android applications in the best possible way. But first, what they really are?
We can think about them in terms of preparing different versions of our applications that differ from user’s perspective. We have already written common code that creates core product, and we can adjust it, modify it, or change few key resources, to distribute different product versions.
In other words, flavor is a specific variant of an application, which is different from the user’s perspective. You might see it in Google Play – its common to have a free version of application with limited feature set and a premium version with the full feature set.
By using our codebase, we can create a product A, and separate product B, which has different name, app icon, and graphics, so the user will never figure out that they have something in common.
Currently, I’m working in a project, in which situation is quite complex. We have a common code used for basic functionality, and we have created four different products based on that code. They have free or premium versions. I’d like you to know how we handle it in the code, so it’s easy to program, maintain, and release them without pain and fear.
Why to create different product versions?
Because we love to automate everything that can be automated. Of course, we could just set different values to xml, and then release this app. But it’s manual work and we, as programmers, don’t like it. Now, let me introduce you to the real world, where proper product management is required.
There are some reasons to create multi flavor apps:
- We can offer version of application with no ads inside for premium users
- We can build different versions with different library sets, e.g. we can offer special version of application to premium users, with no ads included
- We can have different products with different branding for each one. Let’s imagine that you have created a simple app, which shows quote of a day. You can try to upload to Google Play first version of application, which is meant for kids, and second, which is more serious, and prepared for industry professionals
- We can bring order into testing environments. Sometimes, our application use API, and it has different versions. Thanks to multi flavor apps, we can have separate builds for each server, so any team member can always download correct files and start testing
- We can seperate developer applications from production. Thanks to that, a person responsible for monitoring erros will sleep like a baby
All right, it’s perfect time to dive into the Android dictionary. Fortunately for you, you need to know just four terms:
- Gradle
- Buildconfig fields
- Source Sets
- Manifest Merger
These tools are embedded into Android Studio, and you might have used them, without even knowing about that.
Gradle is a default Android build tool. It’s being used everyday for building, managing dependencies, running tasks, automating our job. For now, we are using only a fraction of its potential.
What does it look like inside Gradle? Let’s focus on Build Types, which are our friends in development process. This section is generated by default, and we can add custom options. Mostly, we are using it to distinguish debug version from production.
From user’s perspective, there is no difference between them. But it’s difference for us, developers. Release can result in shrinking resources and a code is protected from decompilation – that makes whole compilation process longer, and that’s why we don’t use it everyday.
Uploading application with flag debuggable set to true is forbidden in Google Play. In that case, only release can be uploaded. We can also have different signings for each type.
Inside this section, we can override default settings or set new ones. As a good example, we can set applicationIdSuffix. It will be added to app package name. For more information, please take a look at the Android Developer documentation.
Product flavors vs build variants
What are differences between them? From a convention standpoint, build type mostly tells us about build environment. It’s better to keep just one Google Play release, and to use another one just for sending files to testers and to build application locally. If you use different build types, you still have one and the same application. It can be pointing to different server, but it’s still the same application.
On the other hand, we have product flavors. Thanks to them, we have a power to choose app name, package it, set of resources and many, many more. From user’s perspective, an application with different build types is something unrecognizable, but different flavour could be like brand-new product.
We live in Android World, and there, we combine build types and flavors. This combination gives us a list of options. We can find it in a small window inside Android Studio, under the name of Build variants. Our modules are listed there, and we can choose different variant for each module. It will take a moment for Gradle to sync its data, and after that we are ready to go. We can install selected variant immediately on our phone.
BuildConfig
When we already know what’s the difference between types and flavors, then we can use powerful way to solve problems, which is called BuildConfig. It’s an auto-generated class with constants, and it’s possible that you saw it already, when checking if you are using debug build. Not everyone know what we can define config fields for our custom build. It’s a great way to control our application.
if( BuildConfig.DEBUG){
showToast("Its a debug!");
}
Take a look at sample usage – that’s how we call methods in Gradle. We need to provide type, name and value of our constant. We can define it everywhere, and then override it inside one of the build types or flavors. Usage of any Java type is acceptable – android.net.Uri, String, Integer, custom.
Making our app better
It’s crucial to keep our application thin. The smaller apk file, the better. It’s enough if just one of our products uses heavy library – others don’t have to contain it. This is dependencies section of Gradle file. We usually type “compile”, and it’s done. We have libraries (like jUnit) compiled only while running tests. But we can also compile libraries just for one flavor.
Source sets need to be configured in Gradle file, where we specify paths for each one of them. What’s important, src dirs take array as a param, so our structure is really flexible, and so is a way in which we can share items across our app.
For example, we have created a login page for free version of app, which has such a beautiful image letting users know what we think about free users. And as a reference, there is a second image, which shows landing page for premium users. We used different drawable folder, strings values and code behind login page.
Previously, we talked about creating totally different products by using shared code. In that case, we don’t want to store all the code in the main folder, and product specifics can be inside source set.
Let’s take a look at example structure. Please notice that we have the same class, AdFragment, inside both premium and free flavors. Please also notice that we can’t have class inside main source set, and another one with the same name in flavor source set. But as long as a package name is the same, we can replace the code inside different flavors.
In a case of premium version, our ad fragment is just a placeholder, and there is nothing to be done here. In free version, we can use related code – inflate view with ad views, start ad request, etc. All of that with full control of a fragment lifecycle.
Then, inside main source set, we can use our fragment. We just need to specify package and class name, and we are ready to go.
Can you guess what the results are? Take a look at the screenshot. I think that’s a reasonable amount of advertisement for a demo. I’m not greedy at all!
PS I have uploaded sample app which uses features mentioned above to GitHub. Feel free to experiment with that.