How Feature Flags can boost your mobile app

Modern mobile development moves quickly; here in our team, we release a new version of our iOS app every two weeks.

If you are lucky enough to live the moment of a team expansion you will also see the exact moment where everything starts slowing down because you have multiple in-progress things that aren’t quite ready for production.

It’s a typical scaling problem where you need to rethink how you and your team perform everyday jobs (if you are experiencing this transition you should really take a look at “Mobile Apps at Scale” by Gergely Orosz).

Great features take time to build. How do you let the team work efficiently — free to experiment — whilst keeping the app in a releasable state?

Feature Flags is one of the strategies you can use to better handle the increase of traffic in your team repositories.

What’s a Feature Flag?

Some people call them “Feature Toggles”, some other “Feature Switch” or “Feature Controls”. All of them are names for a feature management solution that enables developers or product manager to change product’s functionality without deploying new code.

You can consider a feature flag as a decision stop-point in your code that can change the behavior of your product. You may choose to allow developers/tester/product team to adjust these values at runtime.

“(…) feature flagging is to experimentation as machine learning is to AI, you cannot have the second without the first one” JustEat Tech Blog

Common Scenarios

Feature flags are helpful for numerous scenarios typical with mobile development teams.

Avoid Long-Lasting Branches

In this environment, you can let developers merge in their code without working on long-lasting branches (every dev hates resolving boring merge conflicts every time for days or, worse, entire weeks).

Feature flags allows to keep your MRs small & focused, the main branch can be continuously updated and you can release nightly builds with no worries (*).

Reduce the risk

Bugs happen — even with the best developers and rigorous testing techniques.

Imagine this: after months of work-in-progress your team have just release a new amazing feature and you’re exicited for your users to try it out. Unfortunately, things don’t go the way you thought they would and you found an unexpected bug. Now you can either deploy an hot fix or revert the feature.

However, deploying mobile apps is not easy as for web apps or sites and with the app review you may wait 1–2 days. That’s terrible as you risk losing users.
You have to do something immediately!
With remote-controlled flags, you can temporarily disable the feature while taking your time to release the hotfix.

A/B Testing

Feature flags can be used to activate different experiences for sets of users. You can, for example, divide your user base into several sections and measure the conversion performance in order to pick the higher product conversions, engagement, growth, subscription rates, or revenue.

Product Campaigns/Early Access

Product and marketing teams can safely roll out features when they are ready, from remote. They can grant access to a feature to a group of elite users or new users to drive engagement.

Our Development Scenario

In our team, we use feature flagging to gate in-progress features so we can continue to build & test them without exposing them to end-users before they’re ready. Merge Requests are short and focused and enter into the main branch even if they are not complete yet. Obviously, they are disabled by default until everything is ready, but the team can continue the standard release cycle.

To make it effortless, at the iOS team we’ve developed an in-house opensource library named RealFlags to handle feature flags without the hassle. The idea behind the library is to use the best of new Swift language features to create a powerful abstraction layer over multiple providers you can use to setup your experiment lab.RealFlags born with Swift in mind and the vast majority of the core concepts we used to make it are introduced in the current major 5.x.

In the post below I’ll describe the architecture and the core engine with code samples and suggestions based on our experience. I should like to thank several open-source libraries who inspired the work below: [JustTweak(https://github.com/justeat/JustTweak)] by JustEat, Override by Yahoo and FeatureFlags by Ross Butler.

What you want from Feature Flags

As I said a the beginning of the post, mobile development moves quickly; the product still evolving, constraints change, your code easily becomes a legacy code you need to refactor.

Our new Swift app was born less than 4 years ago but during this period we rewrote several parts: feature flags helped us to make this process gradual. In fact, we moved from old to new implementations in a way transparent to the end user.

But a product is not only a tech affair (some devs tend for forget it). As a continuous experiment your product team would introduce new features, enable/disable existing ones via A/B testing, and so on.

So the feature flag engine must fulfill these requirements:

[RealFlags(https://github.com/immobiliare/RealFlags)] acts as an orchestration library both when you need to choose as organize your flags and how to query for values.

Introduction

RealFlags provides a simple UI to interact and modify your flags. Feature flags are organized in collections; you can define collections by using your own criteria.Once done you can add some sort of hidden menu section of your app and tell the browser to show them.

Below you can see the RealFlags menu which perfectly fits in our Developer Tools menu. This section is still visible only when the app is running in debug or from a TestFlight installation.

private lazy var shouldShowDevMenu: Bool = {
  #if DEBUG
  return true // XCode
  #else
  guard let path = Bundle.main.appStoreReceiptURL?.path else {
    return false // AppStore
  }
  return path.contains("sandboxReceipt") // TestFlight
  #endif
}()

We have different flags and we have choosen to organize them by conpetence area with a final collection dedicated to remote controlled flags.

At the time of this writing, RealFlag’s last version is 1.2.0; you can use it via CocoaPods or SPM.

Setup

It’s time to setup your stack; now you’re ready to create your first collection. Suppose you wanna make a section dedicated to the user’s experimental features.

We’ll call it UserExperiments: in this section product team can enable or disable some amazing features. Some flags may also be controlled from remote using Firebase Remote Config.

As good habit RealFlags allows you to declare the blueprint of the collection (a collection is a struct conforms to FlagCollectionProtocol) including the inner flags. While you can create collections of collections I strongly suggest to avoid it for the sake of clarity.

public struct UserExperiments: FlagsCollectionProtocol {
   // ....
   public init() {}
}

A feature flag is just a variable wrapped by @Flag decorator. Technically speaking it’s a Property Wrapper that defines all the attributes and metadata of the flag. In short, a property wrapper is a generic data structure that encapsulates read/write access to property while adding some extra behavior to augment its semantics while avoiding code duplication.

Suppose you want to create a enableSSON flag which allows the user to use single-sign-on to login inside the app. By default this flag is turned off:

public struct UserExperiments: FlagsCollectionProtocol {
   
   @Flag(key: "sson", default: false, description: "Enable Single-Sign-On")
   var allowsSSON: Bool
   
   public init() {}
}

allowsSSOn is a Bool flag which by default is false (RealFlags supports Int, String, Data, Date, URL, Dictionary, JSON and all the data types conforms to Codable) which .

The description field is used to describe the flag inside the browser so be careful when you fill it. We have also specified key: it represent the string RealFlags will use to query every Data Provider (you can configure it as you wish). You can also set more configuration parameters.

A Data Provider is just a source of data; when you query for a value of a flags, RealFlags query — in order — every provider gets the value for that key. The first one who responds with a non-nil value stops the chain with that value. If no data provider is able to respond (or you have not defined any provider) the default value is therefore returned.

Load a Collection

Since the code above it’s only a blueprint of our collection, now we need to load it. FlagsLoader is a Generic-aware class responsible to read (via mirroring) the structure of a collection.

Typically you would use a single loader to handle a single collection. When you instantiate a loader you will also specify - in order - the Data Providers you wanna use to query each flag’s value (note: you can still exclude some data providers per single flag or query a specific data provider type when you want to get the value of a flag).

Actually, you can choose two different Data Providers (but you can easily make your own as you need):

Our FFService is a simple helper class inside our app that holds the value of any loader:

public class FFService {

  public let main = FFService()
  
  public let user: FlagLoader<UserExperiments>

  private init() {
     let fileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]..appendingPathComponent("user_flags.xml"))
     user = FlagsLoader(UserExperiments.self,
                                  description: .init(name: "User Features", description: "Experimental lab"),
                                  providers: [
                                     FirebaseRemoteProvider(),
                                     LocalProvider(localURL: fileURL)
                                  ])
  }

}

The order of the data provider in the init defines the query priority. When fetching a flag, the engine will inspect the chain of providers in order and pick the flag from the first configuration having it.

Query for values

Now you are able to query for User Experimental features by calling userExpsLoader: thanks to DynamicMemberLookup. Xcode will autocomplete for you each flag defined by the collection. Just magic!

Sometimes you may want to get the value of the flag inside a specific provider. In this case, you may refer to the projected value. We will get the value of LocalProvider by ignoring the Firebase which has a higher priority in our loader:

As we said we may have nested collections:

struct UserExperiments: FlagCollectionProtocol {
    @FlagCollection(description: "A nested collection")
    var secrets: SecretsExperiments

    @Flag(key: "sson", default: false, description: "Enable Single-Sign-On")
    var allowsSSON: Bool
}

struct SecretsExperiments: FlagCollectionProtocol {
    @Flag(default: false, description: "Risky feature!")
    var boomFeature: Bool
}

But, what’s the key queried for boomFeature? If you don’t specify the key for a flag the snake_case version of the variable name is combined with the path down to the variable itself. In this case the key queried is secrets/boom_feature.

Set Values

LocalProvider is an example of a writable Data Provider: this means you can alter a flag value and it persists between app launches.

FFService.main.$allowsSSON.setValue(true, providers: [LocalProvider.self])

Since we maintain several target applications for different countries we have used setValue() to keep a single blueprint file of definitions while altering default values inside a conditional init:

Computed flag

Sometimes you may need to create a feature flag where the value is a combination of other flags or runtime values you need to evaluate dynamically.

This is the perfect example to use the computedValue which is just a callback function called before querying any data provider. Returning a non-nil value here stops the chain.

Our suggestion is to limit the use of this pattern in order to keep the code readable. We have introduced it to handle several edge cases we have in our app.

Show the browser

We started looking at the feature flag browser. But we haven’t said how to show it. That’s simple:

In this article, I illustrated how RealFlags can be a great help in adding an abstract yet flexible approach to feature flagging.

Currently, we’re using it in our flagship product; if you want to contribute, provide additional features or providers check out the Github project page!

· swiftlang, programming