Handling Real-Time Remote Config Update on Stockbit

Unlocking the power of instant adaptability: A deep dive into Firebase Real-Time Remote Config for Optimal Performance and User Satisfaction

Ricki Bin Yamin
Stockbit-Bibit Engineering
10 min readApr 3, 2024

--

Photo by Veri Ivanova on Unsplash

Feature Flag

Imagine your newly released feature, which has been published to the App Store, encountering a crash. Unfortunately, rolling back to the previous version is impossible on the App Store Connect portal. Now, you are faced with two choices: either fix the issue immediately or revert your new features in the next version. However, both options require you to re-submit new binaries to the App Store and wait for Apple’s team to review it. This step can take an uncertain amount of time, while user reports increase. You can’t do anything at this time other than wait for your apps to be accepted.

We can avoid the chaos described above by implementing a Feature Flag. It is a tool for mobile apps to quickly turn ON or OFF a feature without the need to submit new versions of your apps. The configuration for a feature’s availability can be deployed to a self-hosted server or a 3rd party SDK that offers Feature Flag functionality. As these configurations are deployed to the server, they are often referred as Remote Config. However, Remote Config has a broader scope than just a Feature Flag.

Remote Config

Feature Flagging is one implementation within Remote Config. Additionally, Remote Config can be leveraged for various purposes beyond feature flagging, including conducting partial releases to whitelisted users, or remotely customizing a page without the need to deploy a new build on the App Store.

Avoid deploying credential data on remote config. It is highly NOT RECOMMENDED due to security issue. Explore other alternative methods for it.

As stated on Firebase documentation, we are prohibited to store confidential data in remote config because the end user can access or fetch all of hosted remote config keys or values. The client app does not need to know what specific remote config keys to retrieve, because the client app will obtain all key-value pairs from the server.

Several third-party SDKs for Remote Config are available, such as Firebase Remote Config, split.io, and Flagsmith. Each SDK comes with its own set of specifications. Here is a summary of the comparison between these three Remote Config SDKs.

  • Integration and Ecosystem: Firebase Remote Config is tightly integrated with the Firebase ecosystem, making it seamless for developers already using Firebase services. Split.io is a standalone platform, while Flagsmith offers flexibility as an open-source solution.
  • Control and Customization: Flagsmith, being open-source, offers greater control and customization. Firebase Remote Config is part of the Firebase suite, and Split.io is a hosted service, limiting customization to some extent.
  • Ease of Use: Firebase Remote Config is known for its ease of integration within the Firebase environment. Split.io and Flagsmith aim to provide user-friendly interfaces.
  • Pricing: Firebase Remote Config is free. Split.io operates on a subscription-based model, with costs based on feature flags, users, and events. Flagsmith is free for self-hosting, with potential costs associated with the hosted version based on usage.

On our company, we choose Firebase Remote Config because it’s free, easy to integrate since we also use other Firebase products such us FCM and Crashlytics, and also match our needs for now: only for Feature Flagging.

Here’s a diagram that show how Firebase Remote Config work.

As we can see on diagram above, the remote config value is always fetched every user open the apps. It helped us so much as first aid if any crash happened to the new released feature. We can immediately turn off “those incident-causing feature”.

Real-Time Remote Config

Scenario above is fast enough for the first aid if an unexpected incident happens to our apps. But after exploring more about Firebase Remote Config, there is an even faster way than that.

Firebase introduced a new API, Real-Time Remote Config, starting from version 10.7.0. This new API allow us to receive updated parameter keys and values as soon as they’re published on the server without waiting the user to re-open the apps. Here are the comparison between before and after applying Real-Time updates for Remote Config.

The major change is the user doesn’t need to re-open the apps to get the new Remote Config value that have been just updated on the server. It creates a seamless experience to the user and fast delivery for Remote Config value to prevent the user from entering the problematic features.

Migration Journeys

After long discussion with engineers teams, Real-Time Remote Config can not be applied immediately to all of Remote Config keys yet. QA teams should validate one-by-one for every feature using Remote Config.

It is a challenge for us to create migration scenarios. Then we have an idea to implement “Partial Real-Time Remote Config”. Some defined Remote Config keys can receive Real-Time update, while others still use the old flow (receive config update after re-open the apps).

Here’s what we do to make it possible.

First and foremost, we have an enum for Remote Config keys that we utilize named RemoteConfigConstant. Following that, we create static properties that contain a list of keys prepared to receive Real-Time updates, named as realTimeRemoteConfigLists.

public enum RemoteConfigConstant: String, CaseIterable {
case isFeatureAEnabled = "IS_FEATURE_A_ENABLED"
case isFeatureBEnabled = "IS_FEATURE_B_ENABLED"
case isFeatureCEnabled = "IS_FEATURE_C_ENABLED"
case isFeatureDEnabled = "IS_FEATURE_D_ENABLED"


// TODO: - For partial release of Real-Time remote config updates.
// If any remote config that is safe to support Real-Time remote config
// and has been tested by QA, mention that key on list below.
public static var realTimeRemoteConfigLists: [RemoteConfigConstant] {
return [
.isFeatureBEnabled,
.isFeatureDEnabled
]
}
}

Then, on didFinishLaunchingWithOptions block, we call basic Firebase Remote Config’s fetching method and store the data in the form of key-value pairs into a dictionary called activeRemoteConfig.

The next step is configuring the Real-Time listener. We store the key-value pairs of Remote Config data into a dictionary. We will update that dictionary based on Real-Time listener data. Only value which key is listed inside RemoteConfigConstant.realTimeRemoteConfigLists will be updated, while the others will not. This is our strategy to implement “Partial Real-Time Remote Config”.

private var activeRemoteConfig: [String: RemoteConfigValue] = [:]

private lazy var remoteConfig: RemoteConfig = {
let remoteConfig = RemoteConfig.remoteConfig()
let settings = RemoteConfigSettings()
settings.minimumFetchInterval = 0
remoteConfig.configSettings = settings

return remoteConfig
}()

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Firebase basic Remote Config fetch
remoteConfig.fetch(withExpirationDuration: 300) { [weak self] (status, error) in
switch status {
case .success:
self?.remoteConfig.activate()
self?.saveRemoteConfig()

case .failure:
// Do something here when error
break
default:
break
}
}

// Firebase Real-Time Remote Config listener
remoteConfig.addOnConfigUpdateListener { [weak self] configUpdate, error in
guard let _ = configUpdate, error == nil else {
return
}

self?.remoteConfig.activate()
self?.updateRealTimeRemoteConfig()
}

return true
}

private func saveRemoteConfig() {
// Get current live remote config
let allConfigs: [String: RemoteConfigValue] = getAllRemoteConfigs()
activeRemoteConfig = allConfigs
}

private func getAllRemoteConfigs() -> [String: RemoteConfigValue] {
var dict = [String: RemoteConfigValue]()
remoteConfig.keys(withPrefix: "")
.forEach { key in
dict.updateValue(self.remoteConfig.configValue(forKey: key), forKey: key)
}

return dict
}

private func updateRealTimeRemoteConfig() {
// Get current live remote config
let allConfigs: [String: RemoteConfigValue] = getAllRemoteConfigs()

// Fiter only remote config stated in RemoteConfigConstant.realTimeRemoteConfigLists that will update to current consumed remote config
let filteredConfigs: [String: RemoteConfigValue] = allConfigs.filter { config in
RemoteConfigConstant.realTimeRemoteConfigLists.contains { remoteConfigConstant in
config.key == remoteConfigConstant.rawValue
}
}

// Update current consumed remote config dictionary with filtered config value
filteredConfigs.forEach {
self.activeRemoteConfig.updateValue($0.value, forKey: $0.key)
}
}

Don’t forget to create a method to consume Remote Config value from our dictionary.

func boolRemoteConfigValue(key: RemoteConfigConstant, defaultValue: Bool) -> Bool {
guard !activeRemoteConfig.isEmpty else {
return defaultValue
}

if let remoteConfigValue: Bool = activeRemoteConfig[key.rawValue]?.boolValue {
return remoteConfigValue
} else {
return defaultValue
}
}

func stringRemoteConfigValue(key: RemoteConfigConstant, defaultValue: String) -> String {
guard !activeRemoteConfig.isEmpty else {
return defaultValue
}

if let remoteConfigValue: String = activeRemoteConfig[key.rawValue]?.stringValue {
return remoteConfigValue
} else {
return defaultValue
}
}

We’ve enhanced our read method to avoid Firebase’s basic method which directly return non-nullable value. It will return its default value which we can’t control (false for Boolean, 0 for number) when unexpected things happened.

// These Firebase's basic method return non-nullable value.
remoteConfig.configValue(forKey: key).boolValue // will return false as its default value
remoteConfig.configValue(forKey: key).numberValue.intValue // will return 0 as its default value
remoteConfig.configValue(forKey: key).numberValue.doubleValue // will return 0 as its default value

If you examine previous code regarding how we consume our remote config value, you’ll notice the presence of the defaultValue parameter in the function. Flowchart below will explain how our read method work.

Defining the correct default value for remote config is crucial for the effective usage of Feature Flagging. This becomes a lifesaver when unexpected issues arise. Additionally, it plays a vital role in handling backward compatibility, especially when a feature has been stable for release and doesn’t need remote config anymore (remote config key has been deleted from the server).

Result

We have an amazing control panel tool developed by the Stockbit iOS teams to inspect the current Remote Config values. Video below show a demo of Partial Real-Time Remote Config. A remote config labeled with IOS_REMOTE_CONFIG_LIVE_UPDATE is instantly updated to the apps because it is listed on RemoteConfigConstant.realTimeRemoteConfigLists, while a remote config labeled with IOS_REMOTE_CONFIG_NON_LIVE_UPDATE still remain its value.

Currently, we are still on the way to migrate remaining Remote Config keys to adapt with Real-Time update. In conclusion, the ability to manage real-time remote configuration updates is a linchpin for Stockbit. The seamless integration of real-time updates ensures that users can access the latest features and optimizations instantly, including crash prevention. In the end, it will increase user satisfaction. 😀

About Firebase Remote Config Usage and TroubleShooting

Is there any limitation for Firebase Remote Config?
Firebase specifies that a project can accommodate up to 2000 Remote Config parameters, and stores up to 300 lifetime versions of our Remote Config template.
If an app makes too many fetch calls in a short time period, fetch calls may be throttled. In older SDK versions, the limit was 5 fetch requests in a 60-minute window (newer versions have more permissive limits). However, in newer SDK versions that support Real-Time Remote Config, it will bypass any caching or minimumFetchInterval setting.

What if Firebase’s server down?
Firebase employs a caching method for remote config values. Every remote config fetch is cached. These values can be read when Firebase’s server is down or when the user’s internet connection is poor, preventing remote config value fetching from failing.

What if a newly added feature has been stable for long-term release and doesn’t need Feature Flag anymore?
If a feature doesn’t need Feature Flag anymore, we can delete the read method from the project first and remove its remote config key from Firebase dashboard later. Make sure removing remote config key from Firebase dashboard doesn’t break for backward compatibility.

Is Firebase safe to deploy remote config value? How does Firebase communicate with its client apps?
Firebase is safe to deploy remote config value as long as your credential key in the form of GoogleService-Info.plist is not published to the public. Firebase use that credential key to communicate with its client. Tap here to read more about Firebase API Key.

What to do Next?

There’s a potential to create more than just a simple switch for turning a feature ON or OFF. For instance, we can make our apps dynamic by deploying configurations to Firebase Remote Config, and just like that, the appearance of our apps will change without the need to submit a new build to the App Store Connect. Here is one idea that can be implemented through the usage of Firebase Remote Config.

Dynamic List Page: Configuring Stockbit’s Setting Page with Remote Config
Our concept involves configuring the content of Stockbit’s Settings page using remote config. All the lists are defined within a JSON structure, and the navigation is specified through deeplink within its JSON properties.

In the future, if we want to add a new list to the Settings page, all we need to do is update the JSON in the remote config.

Conclusion

There are still many features that can be explored by implementing Remote Config. Making dynamic layout from Remote Config value is a potential project that we can execute for the next. Our exploration is also supported by Real-Time capability to unlock the power of instant adaptability for optimal performance and user satisfaction.

--

--