SDKs

Flagbearer for iOS

The Swift SDK for Flagbearer, for iOS and other Apple platforms. Requires iOS 15, macOS 12, tvOS 15 or watchOS 8.

Install

In Xcode, choose File › Add Package Dependencies and enter:

https://github.com/flagbearer-dev/sdks

Or add it to a Package.swift:

dependencies: [
    .package(url: "https://github.com/flagbearer-dev/sdks", from: "1.0.0")
]

Then add the Flagbearer product to your app's target.

Keys

Use a client key, which starts with fbc_. Client keys are public and safe to include in an app. Server keys, which start with fbs_, are secret and belong only on your server.

Set up one client

Create one client for the whole app:

// Flags.swift
import Flagbearer

let flags = Flagbearer(clientKey: "fbc_...")

Flags load in the background and are kept on the device, so the app starts with the flags each user last had, even offline.

While the app is in the foreground, a change you make in the dashboard reaches it within a couple of seconds, over a live connection. If that connection drops, the SDK checks every 30 seconds until it's back. After five minutes in the background it stops both, and catches up when the app returns.

Users

When the signed-in user is known or changes, call identify:

try await flags.identify(User(key: user.id, email: user.email))

The email is optional. Include it to turn flags on for testers by email. After sign-out, call identify(User()).

Checking a flag

if flags.isEnabled("new-checkout") {
    showNewCheckout()
}

isEnabled never waits and never throws. It returns false until the current user's flags have loaded, and when the flag doesn't exist. Pass another default as flags.isEnabled("new-checkout", default: true).

flags.status is .loading until the current user's flags arrive. Use it to hold back UI that mustn't switch between the default and the real value.

SwiftUI

Wrap the client in an observable object, so views redraw when flags change:

import Flagbearer
import SwiftUI

final class FlagStore: ObservableObject {
    init() {
        flags.subscribe { [weak self] in self?.objectWillChange.send() }
    }

    func isEnabled(_ flag: String, default defaultValue: Bool = false) -> Bool {
        flags.isEnabled(flag, default: defaultValue)
    }

    var loaded: Bool { flags.status != .loading }
}
struct CheckoutView: View {
    @EnvironmentObject var flagStore: FlagStore

    var body: some View {
        if flagStore.isEnabled("new-checkout") {
            NewCheckout()
        } else {
            Checkout()
        }
    }
}

subscribe calls its listener on the main queue whenever flags or status change. It returns a function that stops listening.

Error reports

allFlags() returns every flag's current value, so error reports show exactly which flags were on. It doesn't count as checking them.

crashReporter.setContext("flags", flags.allFlags())
// ["new-checkout": true, "dark-mode": false]

When something is wrong

The SDK never crashes or blocks the app over a flag problem. It keeps the last flags, or defaults, and says what's wrong in the Xcode console:

  • A key the server refuses is explained once, with the server's reason.
  • A server key (fbs_) is never sent. The console says to use a client key.
  • In debug builds, checking a flag that doesn't exist warns once per flag.

Pass debug: true to also log each fetch, and to get the missing-flag warning in release builds.

Options

let flags = Flagbearer(
    clientKey: "fbc_...",
    user: User(key: "42"),  // the user at launch, if already known
    debug: false,
    appVersion: nil,        // defaults to the app's version
    cacheFlags: true        // start with the flags this user last had
)