Mastering Firebase Remote Config & Feature Flags in iOS Apps

Mastering Firebase Remote Config & Feature Flags in iOS Apps

Mastering Firebase Remote Config & Feature Flags in iOS Apps

The Night We Nearly Burned the “Upgrade to First Class” Button

Last year, our team was prepping a big feature for our airline booking app: “Upgrade to First Class in-app flow.” It was flashy, revenue-driving, but also risky. One wrong logic branch and suddenly economy passengers could see the button — or worse, tap it and crash the booking flow.

During internal testing we slipped. A config rule leaked it to 5% of real users. Within an hour, crash rates doubled on booking flows. The save? The feature was behind a Firebase Remote Config flag. We flipped it off in minutes, stabilized, patched, and lived to fight another day. Without flags, that would’ve been a multi-day App Store update, angry customers, and sleepless nights.

That’s when it hit me: feature flags aren’t toggles, they’re insurance policies for production apps.

Why Remote Config in Enterprise Apps?

Firebase Remote Config lets you store and serve parameters — booleans, strings, JSON blobs — from the cloud. The SDK fetches, caches, and activates them so your app adapts in real time. What makes it work at scale:

  • Conditional values: OS, app version, locale, user-property, or randomized percentage. This is the killer feature when you want to run something only on iOS 18 and later.
  • Analytics + A/B testing: Measure impact, don’t just hope.
  • Real-time updates: Since SDK v10.7.0 on Apple platforms, config changes can propagate instantly. Dangerous if misused, but powerful when handled right.
  • Semantic version targeting: No more brittle regex hacks — use proper comparisons like “≥ 2.3.0”.

This makes Remote Config the sweet spot for many iOS shops: fast, cloud-driven flags without extra infrastructure. But it comes with quotas, complexity, and risks you need to manage.

Remote Config vs Alternatives

Before you bet the farm on Firebase, here’s a quick rundown of how it compares to other common feature flag platforms:

Firebase Remote Config

  • Strengths: Free/cheap, integrated with Analytics & Crashlytics, real-time support, decent targeting.
  • Weaknesses: Quotas (2k params, 500 conditions), weaker governance, console UI can get messy.

LaunchDarkly

  • Strengths: Rich targeting, enterprise governance, audit logs, SDKs across platforms.
  • Weaknesses: Expensive, adds external dependency, not as integrated with Firebase stack.

Optimizely

  • Strengths: Great for experiments & growth teams, advanced segmentation.
  • Weaknesses: Overhead for mobile, pricing.

Custom Backend Flags

  • Strengths: Tailored to your domain, full control, no vendor lock-in.
  • Weaknesses: Build + maintain infra, hard to do targeting at scale, requires dev effort.

For many iOS teams already in Firebase, Remote Config is the right balance. But if you need strict governance (audit trails, approvals), LaunchDarkly or a custom solution may be the play.

Feature Flags in Action

In an airline app, you’ll typically use flags for:

  • Simple On/Off → Enable/disable “Lounge Access” tab.
  • Progressive Rollout → Roll out “In-flight WiFi upgrade” to 1%, then 5%, then 25%, then 100%.
  • OS/Device Segmentation → Only enable new 3D seat map on iOS 18+ and iPad Pro.
  • VIP/Segment Targeting → Show special promotions only to Gold members in EMEA.
  • Kill Switch → Instantly disable a buggy payment flow without shipping a new build.

Flags give you speed and safety, if you wield them carefully.

FlightDeck Demo: SwiftUI + Remote Config

Let’s wire up a SwiftUI demo called FlightDeck with multiple flags:

  • booking_enable_firstClassUpgrade → Show the upgrade button.
  • seatMap_layout → “classic” or “modern”.
  • offers_percent → Int (percentage for special offers).
  • booking_enable_ios18_feature → Boolean.

Remote Config Manager (with DI)

protocol FeatureFlagProvider {
    func bool(for key: String) -> Bool
    func string(for key: String) -> String
    func int(for key: String) -> Int
}

@Obserable
class RemoteConfigManager: FeatureFlagProvider {
    var enableFirstClassUpgrade = false
    var seatMapModern = false
    var showSpecialOffers = false
    var ios18Feature = false

    private let remoteConfig: RemoteConfig

    init(remoteConfig: RemoteConfig = .remoteConfig()) {
        self.remoteConfig = remoteConfig
        let settings = RemoteConfigSettings()
        settings.minimumFetchInterval = 12 * 60 * 60 // 12h in prod
        self.remoteConfig.configSettings = settings

        self.remoteConfig.setDefaults([
            "booking_enable_firstClassUpgrade": false as NSObject,
            "seatMap_layout": "classic" as NSObject,
            "offers_percent": 0 as NSObject,
            "booking_enable_ios18_feature": false as NSObject
        ])

        fetchAndActivate()
    }

    func fetchAndActivate() {
        remoteConfig.fetchAndActivate { _, error in
            if let error = error { print("⚠️ fetch error: \(error)"); return }
            self.applyFlags()
        }
    }

    func bool(for key: String) -> Bool { remoteConfig[key].boolValue }
    func string(for key: String) -> String { remoteConfig[key].stringValue ?? "" }
    func int(for key: String) -> Int { remoteConfig[key].numberValue.intValue }

    private func applyFlags() {
        DispatchQueue.main.async {
            self.enableFirstClassUpgrade = self.bool(for: "booking_enable_firstClassUpgrade")
            self.seatMapModern = (self.string(for: "seatMap_layout") == "modern")
            let percent = self.int(for: "offers_percent")
            self.showSpecialOffers = Int.random(in: 1...100) <= percent
            if #available(iOS 18, *) {
                self.ios18Feature = self.bool(for: "booking_enable_ios18_feature")
            }
        }
    }
}

Now your SwiftUI views can depend on FeatureFlagProvider instead of hard-coding Remote Config. Makes testing easier.

Structuring Your Configurations

Here’s a realistic example of a Firebase Remote Config defaults JSON blob that mixes general config items (like API endpoints, thresholds) with multiple feature flags you’d flip in an enterprise airline app:

{
  "api_base_url": "https://api.flightdeckapp.com/v1",
  "support_contact_email": "support@flightdeckapp.com",
  "min_app_version": "3.5.0",
  "max_check_in_hours_before_departure": 24,
  
  "featureFlags": {
    "booking_enable_inflight_wifi_upgrade": true,
    "booking_enable_seat_map_modern": false,
    "offers_enable_dynamic_pricing": true,
    "offers_special_percent": 10,
    "profile_enable_dark_mode_toggle": true,
    "profile_enable_loyalty_points_display": false
  },
  
  "experiments": {
    "boarding_pass_layout": "variant_b", 
    "check_in_button_style": "rounded"
  }
}

Config items:

  • api_base_url, support_contact_email, etc. → not flags, but general parameters your app can pull.

Feature flags:

  • booking_enable_inflight_wifi_upgrade → toggle showing upgrade UI.
  • offers_special_percent → control percentage of users who see a promo.

Experiments:

  • boarding_pass_layout → A/B test value (variant_a vs variant_b).

In Firebase Remote Config, these would normally be flat key-value pairs. But you can still group them logically in your code (e.g., prefixing booking_, offers_, profile_). Firebase doesn’t natively store nested JSON — but you can store JSON strings and parse them on the client if you want structured configs.

Best Practices (Beyond the Basics)

  • Prefix keys by domain (booking_, offers_, etc.). Avoid cryptic names.
  • Lifecycle management: Flags should have a ticket or task tied to their removal. Audit quarterly to avoid “flag graveyards.”
  • Documentation: Maintain a single source of truth for all flags, their owners, and conditions.
  • Fetch strategy: Fetch early at launch, or explicitly behind a splash. Never flip mid-session without a plan.
  • Kill switches: Always have a few flags reserved for critical systems (payments, login). They can save your release.
  • Governance: Don’t give every marketer console access. Set rules: who flips what, and when.
  • Metrics: Track flag flips with audit logs. Who changed what, when, and what impact it had.

Common Gotchas

  • Quota ceilings: Remote Config allows 2000 parameters and 500 conditions. Large enterprises can hit this wall fast.
  • 90-day version history: Older templates get dropped. Export if you need longer audits.
  • Offline users: Always fall back safely — assume no network.
  • Race conditions: Don’t render UI before flags activate, or you’ll see flicker.
  • Flag drift across envs: QA, STAGE, and PROD configs can drift. Use templates or scripted syncs.
  • Over-segmentation: Too many conditions → unpredictable rule collisions. Keep conditions minimal.

Testing, CI/CD, and Chaos

  • Unit Testing: Inject a fake FeatureFlagProvider for deterministic results.
struct FakeFlags: FeatureFlagProvider {
    let store: [String: Any]
    func bool(for key: String) -> Bool { store[key] as? Bool ?? false }
    func string(for key: String) -> String { store[key] as? String ?? "" }
    func int(for key: String) -> Int { store[key] as? Int ?? 0 }
}
  • SwiftUI Previews: Inject different flag states to preview multiple UIs.
  • CI Pipelines: Run tests with multiple flag sets. In Azure DevOps or GitHub Actions, you can matrix builds with flags flipped on/off.
  • Chaos Testing: In QA, randomly flip flags during a session to catch brittle code assumptions.

Real War Stories

  • The Seat Map Fiasco: A bug in the new seat map flow caused booking crashes on iOS 17. Only 3% of users had it due to progressive rollout. We disabled the flag instantly, patched, and avoided a PR nightmare.
  • The Promo Popup Storm: Marketing set offers_percent to 100% overnight. Suddenly everyone saw popups. Crash-free, but user complaints flooded in. Lesson learned: governance matters.

Future Outlook

  • Swift Macros: Imagine @FeatureFlag("booking_enable_firstClassUpgrade") giving you type-safe flag access. With Swift 6 macros, this is within reach.
  • VisionOS + tvOS: Different device families = different rollout strategies. Remote Config supports this, but few teams plan for it.
  • Apple’s potential move: Could Xcode Cloud eventually ship native config toggles? Don’t bet against it.

Wrap-Up

When we rolled out the “Upgrade to First Class” flow, a bug only hit a tiny slice of users because of feature flags. We fixed it same day, flipped the flag, and avoided a meltdown. That’s the power of controlled rollouts.

Flags aren’t just toggles. They’re safety nets, cultural practices, and technical levers that let enterprise apps move fast without self-destructing. Treat them like production code — because they are.

Resources

🎯 Bonus: More Real-World iOS Survival Stories

If you’re hungry for more tips, tricks, and a few battle-tested stories from the trenches of native mobile development, swing by my collection of articles: @wesleymatlock. These posts are packed with real-world solutions, some laughs, and the kind of knowledge that’s saved me from a few late-night debugging sessions. Let’s keep building apps that rock — and if you’ve got questions or stories of your own, drop me a line. I’d love to hear from you.