A little bit about White Labeled Solutions
Don’t worry I am not going to mention any definition or something like that. I’ll try to explain in a way that I have understood and I myself interpret it.
So.. You have a product which you feel can be used by various companies, for example, an ERP system, or an HRMS system or something on similar lines. Just that, these companies would love to have their branding on it to have a personal touch for their users. So you change the logo, the colors, and the fonts as per company’s branding and give the exact same (or with a few customizations) product to that company.
Voila! That product is a White Labeled Solution. Congrats buddy, you are a creator & an owner of a White Labeled Solution!
Now that you have a pretty good idea about what it is. I would like to share my experience of building one. I’ll share issues I came across, solutions I implemented. Also, I’ll show you how to generate a new app for a new client with completely customized branding within minutes and all this while maintaining a single structured codebase.
It all started a few hundred years ago…
I had developed Android and iOS apps for one of our company’s product. We delivered it to our first client. It was a huge success!
Then we had our second client for the same product. They needed some slight modifications in the app, branding was obvious. Apart from branding, there were also some minor changes that they required. So I did what every new developer would do, created a clone of this project and made changes in the new project, changed the branding and delivered it. Voila, Success!!
Then came the third… Story repeated… Success repeated!!
Now came the Nightmare…
I got a crash report from one of the client’s app. But the code was same across all clients apps, so that meant the issue would exist in all of them. I had to quickly fix the issue, copy-paste the same in all the projects, push all of them to production and create APKs and IPAs for all of them. All these while switching from one project to another. Ufff… Somehow managed to remain sane throughout the day :D
Then came a feature request from one of our client. Again as a smart new developer, I jumped right into that client’s repository, Implemented the feature, pushed it to production. Done and dusted, right?
Nope. Not quite. After a while, that same feature request came from a different client. I had to dabble around the earlier implemented code, copy-paste this in current client’s project and release new updates. Also, don’t forget to count in the double efforts as we had to update both Android and iOS apps.
Now it was becoming a headache to maintain all these different projects for each client. Keeping track of bug fixes and new features over the period of time was a nightmare.
Product Flavors — some light in this dark world!
Now, what is Product Flavors? In a very small gist, It helps us create multiple variants of an app from a single code base. You can configure the resources, branding, hell even the working of an app.
Now for all the iOS developers reading this and wondering why didn’t we came across this yet? Well because it's not in iOS, it’s an Android feature. What a bummer! Well, if it gives any hope, people have replicated this manually in iOS using the Build Configurations, but that’s another post in itself. You can find it here.
Coming back to Flavors in Android, I would quote a line from Android developer documentation itself here, which can sum up Product flavors for you:
“You can define basic functionality in the main/ source set and use product flavor source sets to change the branding of your app for different clients.”
For developers wanting to know more, you can find it here and for a small realistic example here.
Hell Yeah! This is what we were looking for, right?
Well, it gets a little bit tricky managing for a large number of clients.
See Android goes on adding flavors as folders in your source code so that you can separate the custom code/resources for each flavors here. Also, if you have to just change a little bit of functionality, you can use If Else in your class to separate client wise functionalities. This sounds great at first, but as your clientele will increase managing and reading this code later with a lot of If and Else throughout your codebase will be a headache.
Also, on a higher level, if at any point you would want separate developers to handle separate clients parallelly, It wouldn’t be possible.
Same goes for iOS, even if you manage to replicate the working of Flavors using the Build Configurations, you would end up in the same place as our fellow Android Developers.
GitFlow Workflow (A bit modified one!)
When I was facing all this on Earth-1, my doppelgänger from Earth-88 was researching on implementing a Git workflow for the team. He comes across various Git workflows here and emails me the link. Yeah, emails work across the multiverse!
There were these 2 workflows which I could implement for my scenario — GitFlow Workflow and Forking Workflow. You can read about the workflows on the Atlassian link.
In Forking workflow, you can fork out the repo for each client from your base project, but since I wanted to keep the Codebase same and wanted to manage a single repository since it was a single product, I chose to stay with GitFlow Workflow.
Now for those who know GitFlow workflow or those who did go through the above link might be wondering there is no such thing about managing multiple clients with GitFlow workflow out there.
And you are right! That’s something I came up by modifying the workflow a bit and setting some rules!
Types of Branches in the app :
- Master Branch: Will always contain the latest stable code.
- Development Branch: Will contain the latest merged code from Developer Branches / Feature Branches.
- Developer Branch: Every Developer’s Individual Branch
- Release Branch: Will be created at the time of Release and will be deleted once the code is merged into Master and Development.
- Feature Branch: A Specific Feature Branch. There will be multiple feature branches, each for the individual feature.
- Hotfix Branch: Will be created from master when an issue appears in production code.
When a new client comes into the picture :
- A new branch will be created from master Branch => ClientName-master
- A new branch will be created from ClientName-master => ClientName-development.
- All the branding and client-specific customizations will take place in ClientName-development branch.
- Once a release date is fixed, we will plan “Submit to QA” Date and create a ClientName-release Branch from ClientName-development on that date and APK and IPA will be generated from ClientName-release Branch.
- If there are a list of issues to be fixed, we will plan next release date, all bug fixes will be done in ClientName-release Branch and then new APK and IPA will be created and given to QA again.
- Once a release is good to go live, we will merge ClientName-release into ClientName-master and ClientName-development and delete the ClientName-release Branch. We will tag the commit in ClientName-master branch to the specific version number and create the APK and IPA and submit it to client.
If an issue is found out in live app:
- If it is an issue in client-specific customization, we will create a new branch from ClientName-master => ClientName-hotfix and fix the issue in this and after testing merge this branch back into ClientName-master and ClientName-development and then delete this ClientName-hotfix branch.
- If the issue is not client-specific i.e. it is in base code, we will create a new branch from master => hotfix and fix the issue in this and after testing merge this branch back into master, development, ClientName-master, ClientName-development and then delete this Hotfix branch.
Specific to each client these will be the branches:
- ClientName-feature Name
NEVER EVER will changes be merged FROM Client Branches TO Main Branches. The flow will always be from Main Branches to Client Branches.
That little problem…
Sounded all good? Yeah! It’s just this little tiny problem in Android. It’s the Application ID or in iOS development terms, it is the Bundle ID. Application ID in Android looks like the package name. For those who have worked in Android might know that unlike iOS where Bundle ID is just a parameter in .plist file, here its actually the folder structure of the app. So when we say package name is “com.company.product”, there is a folder “com” inside which there is another folder called “company” inside which there is another one “product”.
But how is this a problem?
Well, when we create client branches from the master branch, the package name remains the same. With their branding, they might also wanna have their package name or signing configurations, etc. Also, you might want to have multiple APKs installed on your device for different clients for testing/ debugging purpose which requires unique Application ID for each APK.
Doppelgänger to the rescue!
Do I sense a dead end here? No, not really. Remember my doppelgänger from Earth-88? He emails me this link about a feature in Android Product Flavors where you can define the Application ID in each product flavors. Hell Yeah!!
So I created a single Flavor named “client” (Not the client name) in the base codebase. Now when I Checkout and create new Client branches, I just go into the flavor block and update the application ID parameter.
Also, as I said above, in iOS, this can be handled pretty straightforwardly by updating the Bundle ID in each client’s branches.
Now all the customizations specific to particular clients were in their own branches while also allowing me to keep a singular codebase updated with all my features and handling crashes and bugs across multiple apps like a pro.
Take it to the next level with Firebase Remote Config!
Guess what? My doppelgänger is always one step ahead! This time he emails me about Firebase Remote Config.
You can update your app’s look and feel, enable disable some features remotely without pushing an update to your app. Really? We can do that?
Yeah, as it turns out, we can remotely manage all this stuff using Firebase Remote Config. So if you are planning for a white label solution, there might be some features that you feel may depend on the client to whether take it or not. Or you might want to have some premium features or whatever your reason might be. There are always such set of features. You just have to think and jot down these features first hand. Make a config like “feature name: true” where keys can be your feature names and value can be a Boolean flag to determine the availability of that feature in the app. Now, while developing your app, check for these flags and appropriately display/hide these features.
Time for the conclusion!
Before setting up a workflow about how you are going to architect your codebase, there are some questions that you might want to answer first.
How big customizations are you ready to accommodate per client?
Will these changes turn my app or product into something like a whole new customised solution?
If that’s the case, I would rather go with Git Forking workflow and maintain a whole another repository for such clients.
Also, try and find out the dynamic features of your product which can change client wise. Implement these using Firebase remote config or you can create a remote configuration framework of your own too with the help of your Backend team.
Also, if you plan on keeping your clientele to bare minimum, which I am sure no one would want. But still, if that’s the case, I would suggest going with Product Flavors only without the client wise GitFlow.
That’s all for this one. If you liked this article and if it helped in any way, then like and share with your Friends and Colleagues. Also, I would love to hear how you handle such scenarios and also if you have any suggestions please shoot them.
If you find this interesting, chances are you’ll find my twitter feed too! I keep sharing articles and stuff related to development, design and productivity on a daily basis. So hop on, follow me on Twitter and enjoy the learning ride!
Till next article…
This article was originally written & published on Medium
Top comments (1)