How to Implement Feature Flags and Gain More Control over Your App?

How to Implement Feature Flags and Gain More Control over Your App?
Every person with software development experience can share a few stories about incorrectly working features. We build our apps out of small pieces, introduce advanced architecture patterns, and yet sometimes one of the elements fails, causing bugs or even system failures. In such cases, feature toggling can save the day! Find out how to implement feature flags, and make your app more stable.

What is feature toggling?

Simply saying, feature toggles (sometimes also called feature flags) are on/off switches that allow us to dynamically enable and disable some parts of a system. Even during its runtime.

The idea is pretty straightforward – our app or system communicates with another service that decides, if a given feature should be enabled, or not.

And then what? For example, depending on this factor our app can show the user some set of buttons or hide it away from him. It can also display a popup with information that this function is currently unavailable.

Benefits of feature toggling

Feature toggles are a field-tested solution that proves useful in multiple situations and is widely adopted in software development. But why should we spend time on implementing and supporting it? Here’s a short list of reasons:

1. More control over our own software
When the system fails, a problematic element can be disabled, but with feature toggle the rest can still function. This ensures a better user experience than the classic approach when the whole system is locked out and the user can only see the Something went wrong screen.

2. Simplified integration of deployment process between back-end and front-end services
Client apps can be released without the need for the back-end to be ahead of them. The features not yet supported by the back-end can be hidden behind the feature toggles and disabled until they’re implemented on both sides.

3. Easier to maintain release cycle
This depends on the release plan for our software, but we usually encounter issues concerning unfinished or incorrectly working features that make sticking to the schedule really difficult. Thanks to feature toggles, when this happens, we can still release our application on time and simply keep the unfinished parts disabled until they’re ready.

4. Releases for groups of users
Specific features can be enabled only for fractions or groups of users, so we could analyze how a given feature impacts the business and how it’s perceived by users. This can provide a lot of valuable info and simplify A/B testing, helping our application to grow.

These arguments should be enough to convince most people that feature toggles are something that everybody can benefit from – end users, business, and software developers.

Defining feature flag values – best practices

Implementation of feature flags in mobile applications can be really easy if we know what tools to use, and what principles we should follow during the setup.

These are some of the most important rules for defining feature toggles:

  • Simplicity – the toggle should be no more than an on/off switch that describes a single feature, or a connected group of features. Introducing more complexity may lead to unnecessary confusion as the number of feature toggles grows.
  • Refreshing and caching data – we often want to remember the last state of our feature toggles and save it for offline use. But we also want feature states to refresh themselves from time to time, so our application could stay in sync with the current state of toggles.
  • Ability to override values – we should provide some kind of solution to change the state of feature toggles on-demand. This makes development and testing much easier. Trust us, the QA team will be really thankful!

Taking all of the arguments above into consideration, we can start setting up a feature flag in our application.

How to implement feature flags?

Source of feature flags

First of all, we need to define where our feature toggle states will be stored. The most natural choice seems to be Firebase Remote Config since most mobile apps use Firebase services anyway. But it’s not the only solution – any key-value store will do just fine. If we want, we can even use REST API and retrieve toggles from our own server.

The remote config approach, however, has some nice out-of-the-box benefits:

  1. Official native SDKs for Android, iOS, and Flutter.
  2. Ability to store a file (XML on Android; Plist on iOS) with default values, that can be used if the application is unable to fetch data from the server.
  3. Setting different values depending on specified conditions, which comes in super handy, for example, when we want to enable one feature for Android but disable it for iOS.

For detailed setup instructions, it’s probably best to follow dedicated Android and iOS guides.

Adding parameters in remote config

Firebase web UI provides a set of controls, allowing us to define key and description for each value.

Default values can contain any valid JSON type data, but in our case, a simple boolean value will be enough. After adding a parameter, we can publish the changes, and our remote config will be instantly available for client services.

Setting conditions for feature flags

When adding a parameter, Firebase allows us to set different values depending on specified conditions.

Condition sets can be built on top of different analytics or groups provided by Firebase Analytics. But they can also be extended by specifying custom values in Firebase.

For example, we can set a condition value specifically for Android devices:

Feature flags: Defining a new condition for parameters

Similarly, we can make the same type of condition for iOS devices. This allows us to enable given feature on only one platform:

Adding a parameter key only for Android platform

Using feature flags on Android

When our instance of Firebase Remote Config is ready, we can start implementing support for it in Android and iOS apps. In this article, we use Android and Kotlin as examples, but the whole procedure is almost the same for iOS and Swift.

Fetching values of feature flags

Before we start using feature flags, we need to fetch the Remote Config. This operation will retrieve the latest values from Firebase. Usually, it should be conducted just once, when the application starts. But we also recommend checking out Firebase Remote Config Loading Strategies to see other possible approaches. The entire process is well documented in Android and iOS guides, so we’ve decided to skip it here.

Defining the feature data model

For each feature, we have to define a “key” field that will match the key stored in Remote Config. In addition, it may be a good idea to add fetchImmediately. It’s a boolean value that determines whether Remote Config should be refreshed before checking feature value or not.

enum class Feature(
	val key: String,
	val fetchImmediately: Boolean = false
) {
	FLAG_TEST(
    	key = "feature_flag_test",
    	fetchImmediately = false
	),
	DEPENDING_ON_PLATFORM(
    	key = "feature_depending_on_platform",
    	fetchImmediately = true
	)
}

Implementing feature flag checks

The interface is so simple, it takes only one method to check our feature flag:

interface FeatureManager {
	suspend fun isEnabled(feature: Feature): Boolean
}

Proper implementation of the interface above must rely on Remote Config data and respect our fetchImmediately parameter.

The example below should be suitable in most cases, but if needed, we can extend it with additional features.

class RemoteFeatureManager : FeatureManager {
	// Instance of remote config, probably best to inject it using DI
	// In order to make testing easier
	private val remoteConfig = Firebase.remoteConfig.apply {
    	// Attaching default values from XML file
    	setDefaultsAsync(R.xml.remote_config_defaults)
    	// In this block additional parameters can be configured
    	setConfigSettingsAsync(remoteConfigSettings {
        	minimumFetchIntervalInSeconds = 300
    	})
	}

	override suspend fun isEnabled(feature: Feature): Boolean {
    	// Refreshing remote config depending on fetchImmediately value
    	// If needed, additional check can be added to check if config fetch was successful
    	if (feature.fetchImmediately) remoteConfig.fetchAndActivate().await()
    	// Returning remote config value assigned to feature key
    	return remoteConfig.getBoolean(feature.key)
	}
}

For debugging and quality assurance purposes, we can use either separate Firebase instances with different Remote Config values, or a local override.

The latter can be easily implemented with SharedPreferences. All we need to do is to set the features as enabled by default and add one more method for changing feature toggle states. Later, it can be available from our debug screen or QA plugin (we recommend Hyperion), so we could quickly switch values if needed.

class LocalFeatureManager(context: Context) : FeatureManager {
 
	companion object {
  	const val FEATURE_PREFERENCES_NAME = "feature_flags"
	}
 
	// Instance of shared preferences instead of remote service
	private val sharedPreferences = context.getSharedPreferences(
        	FEATURE_PREFERENCES_NAME,
        	Context.MODE_PRIVATE
	)

	// Value is read from SharedPreferences, and enabled by default
	override suspend fun isEnabled(feature: Feature): Boolean {
    	return sharedPreferences.getBoolean(feature.key, true)
	}
 
	// Can be used to turn features on and off from
    fun setFeatureState(feature: Feature, isEnabled: Boolean) {
  	  	sharedPreferences.edit()
        	.putBoolean(feature.key, isEnabled)
        	.apply()
	}
}

Since these implementations are built on top of a common interface, we can also add an option that allows switching between them, somewhere in our app. This way the QA team can use both remote and local feature flag sets on-demand.

 

Feature flags allow us to deliver a more stable, modular system with an additional fallback mechanism we can use when something goes wrong. Sure, they make the code more complex and require some additional checks here and there, but their return value is often very high. They are extremely useful as our system grows and the number of features increases. Give them a shot!

 

Learn more

Bitrise Tests Made Easier: Update JIRA Issues with Build Number and Forget About Delays in QA Testing

When you trigger Bitrise build with changes and forget to tell QA specialists some essential information, there are two likely scenarios. You waste time waiting for the update from the tester, only you don’t know it’s never coming. Or you get so many questions about builds that you can’t keep up. Sounds familiar? If so, let us tell you about the JIRA issue update that keeps workflow in order.
Read more

ConstraintLayout, or not so fast, after all…

Recently, while browsing through news from Android world I came across a concept of ConstraintLayout. It is a new layout delivered by Android and Google, supporting Android versions from API 9 on. Digging into possibilities it is to give, I decided to check how new Layout Builder behaves and what is ConstraintLayout like in use.
Read more

Android Small Talks: CoordinatorLayout, or one of the strengths of Android Design Support Library

When Android Lollipop entered the market, there was a breakthrough. Google provided us with an extensive library of Android Design Support Library which facilitates creating applications that are compliant with the principles of Material Design. Creating a user interface in accordance with these guidelines introduces our software to the next level of design and user-application interaction.
Read more

Project estimation

Let us know what product you want to build and how we can help you.

Why choose us?

Logo Mobile Trends Awards

Mobile Trends Awards 2017

Nomination in
M-COMMERCE category

17

client reviews

Clutch logo
Logo Legalni bukmacherzy

Legal Bookmakers Award 2019

Best Mobile App

60+

projects in portfolio