Building a lightweight feature flagging system
Ship your code to production with confidence with this enum based feature flagging system
Why do we even need feature flags 🤔
While we build our apps, it’s often necessary to ship code to the App Store, but you don’t want your users to use it yet because it’s not production ready or its meant to go live at a specific date. Why ship to the App Store when the code isn’t ready yet, you ask? This is often the case if your team is following a continuous delivery pipeline. Instead of having a feature branch, all new code is merged into the develop branch and kicks off a build. With multiple members on a team, you need to be able to release features only when they are complete, fully QA’d and are ready to go into the wild 🐯
Lets talk code 👨🏼🚀
enum FeatureFlag: String, CaseIterable {
case featureOne
}extension FeatureFlag { var isEnabled: Bool {
switch self {
case .featureOne:
if isRunningInAppStore() {
return false
}
return featureFlagValue()
}
} private var key: String {
return "com.companyname.featureflag." + rawValue
} private func featureFlagValue() -> Bool {
return UserDefaults.standard.bool(forKey: key)
} func enable() {
UserDefaults.standard.set(true, forKey: key)
} func disable() {
UserDefaults.standard.set(false, forKey: key)
}
}
See the full gist here:
By using swift enums, we have a very nice API. To use this anywhere in our codebase is easy.
FeatureFlag.featureOne.isEnabled //to check if a feature is enabled
FeatureFlag.featureOne.enable() //to enable a feature
FeatureFlag.featureOne.disable() //to disable a feature
For this specific example, we are using UserDefaults to persist the feature flag. This doesn’t have to be the case at all. Some of the flags can easily check against your bundled plist, a third party service or your own remote server. This gives us a ton of flexibility. We don’t have to couple ourselves to a specific persistence strategy 🤓
As a bonus, we can always add extra properties to our enum. It’s often the case that you want a debug menu somewhere in your app where you can easily toggle each feature flag from a menu. Our FeatureFlag enum can then be extended with a few properties
var showInSettingsMenu: Bool {
switch self {
case .featureOne: return true
case .featureTwo: return true
case .featureThree: return true
}
}var settingsMenuCellTitle: String {
switch self {
case .featureOne: return "👨👩👧 Enable feature one"
case .featureTwo: return "📁 Enable feature two"
case .featureThree: return "Enable feature three"
}
}
Simply add the properties you need to your enum. Easy
Lets Ship it ⛵️
Well, not yet. We need to be 100% sure that our feature flags won’t accidentally be set to true in production when we actually aren’t ready to ship them. So let’s do the responsible thing and write some tests first.
import XCTest
class FeatureFlagTests: XCTestCase {
func setup() {
super.setUp()
AppConfiguration.🔥alwaysReturnAppStoreEnvironment🔥 = true
}
override func tearDown() {
super.tearDown()
AppConfiguration.🔥alwaysReturnAppStoreEnvironment🔥 = false
for flag in FeatureFlag.allCases {
flag.disable()
}
}
func testThatFeatureOneFlagIsOff() {
XCTAssertFalse(FeatureFlag.featureOne.isEnabled)
}
}
Notice the AppConfiguration.🔥alwaysReturnAppStoreEnvironment🔥 which isn't covered in this blog post. It allows us to mimic an AppStore environment for our tests. Depending on your particular feature flag you will most likely need to write some extra tests. For example, checking if the debug settings menu is hidden in production and other UI related checks. So write enough tests here to give you enough confidence to ship.
I hope this helped you out and you learned something new today. If you enjoyed reading this article be sure to throw your hands together for a couple of claps 👏👏 . Feel free to stalk me on Instagram and Twitter 😄.
We have a growing list of open roles for our team. If you’re interested in learning more about working at Over, check out our Careers Page!