Strongly flavored Android application for 3 HUGE corporations

Kajetan
Level Up Coding
Published in
6 min readMar 8, 2021

--

Plane over a city
Photo by 贝莉儿 DANIST on Unsplash

This article is inspired by true events. Some of the characters, names, businesses, incidents and certain locations and events have been fictionalized for dramatization purposes. Any similarity to the name, character or history of any person is entirely coincidental and unintentional.

Today you’re going to build an Android application for our three business clients. They’re our key partners so we really don’t want to mess it up. Otherwise, it’d bring some unpleasant consequences… But let’s be optimistic, we’ve done it many times. Three apps within one project; a few separate modules, a few build variants and everyone will be pleased. A piece of cake! Let’s do it! Oh, and in case of troubles I’m here to guide you, don’t worry.

Requirements

Okay, let’s start with the modules. What we need:

  • Drive module — some of our clients asked us to implement a mechanism which will allow them to synchronize their customers’ data with an external storage.
  • Analytics module — obviously, they’d like to optimize their strategies so we should be able to collect some basic information about the users.
  • Ads module — some of them chose this way of monetization, so let’s provide them with one, neat module for that.

Hm? Ah, no, our customers are no secret. Currently the following are interested:

  • Defrag GmbH — our German partner wants drive and analytic modules in their solution.
  • Ransom Software Foundation — I know what you think about them, I’m not their fan either but the business knows no personal feelings. They need drive and ads modules.
  • Truman SA — our biggest client asked us for analytic and ads modules.

We are known to create a high quality software so let’s make it smart as always. You don’t want these organizations to have access to the modules they didn’t pay for, do you? Sure, you could block it programmatically, but better not to include these modules at all. Not only they won’t be able to deobfuscate it (though I highly doubt there’s a single person in these companies who knows what deobfuscation is) but also the apk file will be smaller. Okay, enough. Now it’s time for a cup of good coffee. Later we’ll make a prototype of a prototype, shall we?

Project structure sketch

Good job. Thanks for including the file size report too, I appreciate it.

┌────────────────────────┬────────┐
│ Modules │ Size │
├────────────────────────┼────────┤
│ Clean project │ 1MiB │
│ Drive │ 1,2MiB │
│ Drive & Analytics │ 1,4MiB │
│ Drive, Analytics & Ads │ 2,4MiB │
└────────────────────────┴────────┘

Now let’s think how to get it all together into a coherent whole. Mhm. Your idea is to make either four flavor dimensions to represent all the clients and separate modules or three flavors of one dimension to represent each client’s variant? Interesting. Let’s see how both will look like and we’ll choose something. Now excuse me, the boss has just finished another video conference and called me to his office for a moment or two. Wish me luck!

Flavors

I’m back with good and bad news. But first let me see what interesting you’ve made.

Modules integration

Great! Reflection + proguard rules, that’s the way you do it. If those modules had been self-contained and independent within their Android activities it would be even easier as no additional exceptions are needed for an activity. Proguard keeps all of them by default so it’s perfectly safe to start them using their package name (thus implicitly by reflection.) But that’s just my little digression. Keep going.

Four dimensions approach

Ugh! What kind of food is this?! I feel like in an Indian restaurant. Let me count… Three company flavors multiplied by two drive module flavors multiplied by two analytics module flavors multiplied by two ads module flavors multiplied by two build types…

Math lady meme
Calculating…

It gives us forty eight flavors! Indian cuisine indeed! It makes dependency management simpler, right, but look at what price we have to pay for this. Totally not worth it.

Three flavors approach

Hmm. Yes, promising. Simple, minimal amount of flavors and even a protection against undefined buildConfigField (or preventing false definition in each flavor that doesn’t use the given module if you prefer.) Smart. There’s only one con. You have to define all dependencies separately for each client. Not only will it inflate the dependency section if there will be more clients or modules but also it’s error-prone. In the optimistic scenario you could simply get an appropriate message stating that there are unresolved references, but in the worst scenario, you won’t be notified and one of our clients would receive an application with more modules than he bought. The boss would kill us. Or worse… fire us!

Erm… Speaking of more clients, the concept of this application is loved by many business partners. That was the good news. Bad news is that now we have to prepare this application for four additional clients… and four more modules. Yeah, I know, it’s crazy. Fortunately, I have an idea. Come with me, we’ll improve your solution a little…

The final approach

Voilà! We made it! Now we have both reasonable amount of flavors and greatly simplified dependency management. Let me sum up what we’ve achieved:

  • We’ve created an enumerated type representing the modules which makes the build script slightly clearer and less error-prone.
  • We’ve wrote extension functions in order to make the build script easier to read and shorter to write. Sure, it’s nothing more than sugar, but it’s the healthy one!
  • Probably the main point of this solution is a function called getSelectedBuildVariant . Unfortunately, I don’t know about standard/official way to get that information from DependencyHandlerScope so we had to use a little trick and retrieve the list of flavors from IML (IntelliJ IDEA Module) file.
  • Using our extension functions, we’ve defined buildConfigFields for each flavor to indicate which one has access to which modules.
  • We’ve set all missing build config fields to false . It creates unused modules’ flags without having to define them explicitly, thus protecting against unresolved reference errors.
  • We’ve complicated the flavor-dependent imports a little bit in favor of automatic management. Thanks to this, we can be sure that if a module flag is set, the appropriate dependencies are present, and if the flag is unset, the corresponding dependencies won’t take even one byte of memory.

And finally, the report:

┌────────────────────────────┬────────┐
│ Flavor │ Size │
├────────────────────────────┼────────┤
│ Defrag GmbH │ 1,4MiB │
│ Ransom Software Foundation │ 2,2MiB │
│ Truman SA │ 2,2MiB │
└────────────────────────────┴────────┘
Happiness is getting the job done!
Done! Photo by Ali Yahya on Unsplash

Hold on!

Not so fast… First of all, I’m glad we met here, in the end this half-story half-article. 😃 I hope you liked that slightly unusual form. Let me know in the comments or simply clap your hands 👏! It’d be much appreciated. 🙂

Time for drawbacks! Alright, there are two as far as I can predict. It’s unlikely, but if your project has two flavors such that one’s name is contained in another, the code will mistakenly detect the shorter one whenever the longer one is selected. That explanation is probably not very clear, so here’s an example:

Defined flavors: cheese, cheesecake
┌─────────────────┬────────────────────┐
│ Selected flavor │ Detected flavors │
├─────────────────┼────────────────────┤
│ cheese │ cheese │
│ cheesecake │ cheesecake, cheese │
└─────────────────┴────────────────────┘

Another downside is only a potential danger, but still worth mentioning. I don’t know how stable the structure of IML file is, but I think it’s not impossible that JetBrains will change it someday, say to JSON format. Needless to say, the getSelectedBuildVariant function would break then.

Previously, I was using a little customized version of Polana Apuana’s script found here. It’s quite interesting solution, I encourage you to check it out, too. It has, however, one flaw. In some cases, especially during synchronization, it doesn’t discover any flavor at all which makes dynamic dependency management painful in the best scenario and impossible in the worst.

What’s next

--

--