# 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:

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

Or add it to a `Package.swift`:

```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:

```swift
// 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`:

```swift
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

```swift
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:

```swift
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 }
}
```

```swift
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.

```swift
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

```swift
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
)
```
