Skip to content

Instantly share code, notes, and snippets.

@SwiftyAlex
Created July 21, 2020 17:08
Show Gist options
  • Save SwiftyAlex/5da61af0f669a8f66f975eeffc0c1fa2 to your computer and use it in GitHub Desktop.
Save SwiftyAlex/5da61af0f669a8f66f975eeffc0c1fa2 to your computer and use it in GitHub Desktop.

Feature Flags

When building modern applications, things tend to move quite quickly. When you add multiple developers to your team, things really start to get going. With enough developers, you'll find you have multiple in-progress things, that aren't quite ready for production. How do you let the developers keep working at their most efficient speed, whilst keeping your app in a constantly releaseable state?

Enter feature flags.


You'll have heard the term feature flags before.

Here, I'm referring to the process of having a feature ( and all of its code ) hidden behind a flag that can be enabled on an app build. You may choose to allow developers or testers to adjust these at run-time, but the important part is that you can have a hard off for features that aren't quite ready for production.

When you do this, you can let developers merge in their code without working on long-lasting feature branches, without causing any changes to the actual product. This allows you to let your main branch serve as a constantly evolving, releasable snapshot of your code.

Lets look at an example of this.

#### Example

Here we have an app called Cortado. We've got a basic menu and a my orders screen, and our product owners have asked us to add a loyalty screen with a virtual reward card. This will require a whole new tab being added, and some small additions elsewhere such as the my orders screen linking to the reward card.

initialview

This feature will take a little while to build, and it will be chunked up in the following steps to make sure that everyone has something to do, and the work can keep progressing through a kanban board.

  • Add the new tab
  • Add the reward card UI to the new page
  • Add a list of recent orders to the new page
  • Add a mini reward card to the top of the my orders page

This work is going to be worked on whilst another team is building out a new payments feature that will make changes to the payment and order screens. They're going to start whilst we work, and finish ahead of us.

There's a few ways we could go about this, and they all have a few downsides. By the end of this post, we'll have a nice middle ground that should keep product & tech happy.

Option 1: Feature branches

The first solution, is to have two long lasting feature branches that the teams work from. This means that the work is kept entirely seperate, and the first time both sets of code see eachother will be the second merge.

When we do this, main stays always ready to release, and hotfixes can easily be applied.

![image-20200721165207311](/Users/alexlogan/Library/Application Support/typora-user-images/image-20200721165207311.png)

There's some problems here that we need to consider:

  • Integration testing can only be done when the loyalty branch merge is complete, and any issues found will delay the launch of that feature.

  • The Payments branch will be ahead of Loyalty in terms of the main branch, so it could have various features in it that the loyalty one isn't aware of.

  • Any bug fixes applied to main are potential sources of conflict with both branches, and conflict resolution can take a substantial amount of time - espeically with an iOS xcodeproject.

  • If bugs are found with the payments feature, they will delay the ability to create a build with loyalty in, another possible way to delay the launch.

Option 2: Short lived branches

An alternate solution, is to have small, single task branches off the main branch that don't stick around for long. This helps keep pace strong, but kills the ability to create a release whenever desired.

Here, you can see that we have a lot of different branches, merging together more often. This opens the door for more frequent integration testing, makes it easier to spot bugs, and stops huge conflicts - although doesnt quite get rid of them entirely.

![image-20200721170352504](/Users/alexlogan/Library/Application Support/typora-user-images/image-20200721170352504.png)

This time, there's less problems to consider, but they're still there:

  • You can't release main until the features are finished. If you have to do a bug fix, you'd have to branch from main before this work started, and then merge that back in later.
  • The code thats in main is in a potentially un-stable state, and it'll certainly be unfinished. Our example first task would create an empty loyalty tab on the first merge.
  • Many little branches could mean many little conflicts.
  • Following a refined process, each individual merge would require individual code reviews, which would potentially slow down developer velocity.
Option 3: Short lived branches with feature flags

Using the exact same approach before, here we're going to make one little change. The first task for each feature, is to add a feature flag that can be turned on and off when we run builds. This means that from that point onwards, the main branch will behave as if these features aren't being worked on, despite their code lying their dormant.

You also gain a little bonus ablility - if you release a feature and you don't like it, you can just turn it off.

![image-20200721170742158](/Users/alexlogan/Library/Application Support/typora-user-images/image-20200721170742158.png)

There's still some risks here - but very few:

  • Whilst this code is dormant, its still there, and can cause problems. This requires a good eye in code reviews to avoid releasing part finished features.
  • You have to remember to tidy up these flags, or you'll get into situations where you're turning on muliple to just see one feature.
  • Somtetimes a feature flag is impractical, especially if you're doing something in a storyboard , meaning even when this featrue is turned off you mighr end up seeing little bits of new stuff.

If we chose option 3, we can get working with both teams, as soon as we add those feature flags. Both teams set off, build their features, and when they send a build to a tester they simply turn on their feature. When the work starts on some of our secondary features such as the mini loyalty UI on the my orders screen, that can be in its own feature flag.

If any fixes are needed, or emergency releases, they can be done confidently no matter what state the two features are in.


Implementation

Feature flags can and should be built simply to begin with. You can extend these later with things like the previoulsy mentioned run-time customisation, but its not needed to get started.

We're going to be using Swift for this example, but you could easily replicate this in Kotlin , Javascript, Dart or others for the platform you're on.

https://gist.github.com/b719074d22b3682b720356492967cd3a

We simply have a list of features and a basic switch that enables or disables them. Defaulting to false creates a safety first situation that should help prevent features that aren't ready from sneaking into your next release. A great way to extend this woudl be to store values in user defaults, core data, or even make them configurable in an .xcconfig file so you can have targets for each feature.

Using a feature flag

Having the feature flag is only the beginning. Now, we actually have to use it.

Feature flags tend to expose spaghetti code very quickly, as you'll notice loads of these if Feature.loyalty.isEnabled's scattered throughout your codebase.

We'll look at three different examples here, a View in SwiftUI, a ViewModel, and a UIKit nib.

SwiftUI

SwiftUI's view builders seem like they were built for these. You can now use control flow in your view code, and add views based on the flag being enabled.

Here's our view before we have a feature flag and the new code.

https://gist.github.com/f145a8a40a20d3d889bb23ba3482035d

Initial View

Now, lets add our feature flag. All we have to do is make sure to add the @ViewBuilder attribute, and a quick if statement for our loyalty feature.

https://gist.github.com/2f1dfd60c11bf9f4ce2fbf5cdec56225

Loyalty Placeholder View

If we go into our feature file, and make the following change:

https://gist.github.com/d64b3b0fe0fc12697f01a737f6a9083f

We'll see the following when we run our app.

Disabled

There's absolutley no trace of our new feature, and we could release our app with that tab being invisible - great!

View Models

Something a little more complicated, is the changes you might have to make to your viewmodels. If you don't use MVVM, feel free to skip this section.

There's a couple steps we need to take here to make sure that we can properly use feature flags with view models. The first, is to extract a protocol for our View model. The example one we're going to use fetches a list of orders, and will be extended after this to fetch the users stamps.

https://gist.github.com/ff33eb352a0dcd94b803990ffee4fab5

Extracting a protocol, is easy. By doing this, we can change the type our view references, and inject in a different view model depending on what state we're in. A good approach if you're building apps where you know you'll be using feature flags, is to build this protocol driven way from the start.

https://gist.github.com/601411d24a2952b3af3d597d07222416

Now we've done that little refactor, we can build our new view model, that has some extra little properties on it.

https://gist.github.com/ef131ff7d7eb8eac6ad98fe6104622ce

Here, we've been able to hide away the extra features behind both a feature flag and a type check. This means that in a higher level view we can inject the correct view model and also ensure the feature flag is set correctly. When this feature is eventually released, we can add the properties to the protocol, use our new view model only, and remove both the checks in our if statement - neat!

UIKit

Something i've come across a few times with feature flags, is that they tend to fall apart when you have a storyboard. You can't change the storyboard and hide that behind a feature flag, so you have to make a compromise:

  • Add the UI and make sure it gets turned off in code - which is easy to forget
  • Change the UI by adding it in code - creating a mix of programatic and interface builder designs
  • Accept that some of the UI will be differnet even with the feature turned off - this will happen in a feature where you're not just adding something, you're also rebuilding existing UI

My solution, is to use nibs for my view controllers. There's many advantages of these ( which we won't get into here ) but a great one - is being able to progranatucally pick which nib you want for a given view controller. This allows you to have two nibs for the same view controller, or simply present a whole different one instead. Lets look at those options in code.

Here is a typical way of presenting a nib-backed view controller in code.

https://gist.github.com/b24625b3cc69ea39fb9cdd9d7ada7ed9

Now, lets see the two different ways we can use our feature flag to change this code. The first option is to simply change the nib in our initialiser to use different UI.

https://gist.github.com/dd622376fae0ed6486888a9386fe9678

The second way, is to give this responsibility to our co-ordinator or presenting view controller, this is arguably the correct way to do it, as a view shouldn't have to decide if its showing a new or old version.

https://gist.github.com/418d535a62ec0cef9925e361c14a4eb8

In both examples, we've managed to remove the need to create a whole second view controller, by just having a seperate UI file that backs the new UI. This allows for the old UI to remain completley intact.


Conclusion

Feature flags are a powerful tool that can help drive confidence in your product. Whatever technology you use to build your app, there's a way to get feature flags in. We've talked about some advanced usage, like allowing users to change them at run time, or using xcconfigs, and these are all great additions you can make once you have these integrated into your development process.

Once you're comfortable, you can even look at extending these. What if the answer to is this enabled came from a web service, or you addded A/B testing functionality to them?

There's a lot of opportunity - give it a try.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment