Integrating Grantiva Feature Flags in a SwiftUI App

I built Grantiva because I wanted a feature flag service that was simple to integrate, had a free tier that didn't feel crippled, and didn't require me to learn a new DSL or pull in a massive SDK. This post walks through integrating it into a SwiftUI app from scratch.

If you're coming from the Feature Flags and Gradual Rollouts post in the Making It Real series, this shows how to implement the FeatureFlagProvider protocol with Grantiva as the concrete backend. If you're not, no worries - this stands on its own.

Setup

Create a free account at grantiva.io and create a project. You'll get an API key and a project ID. The free tier includes unlimited flags and up to 10,000 evaluations per month, which is more than enough for most indie apps.

Add the SDK

Add the Grantiva Swift SDK to your Package.swift:

.package(url: "https://github.com/grantiva/grantiva-swift.git", from: "1.0.0"),

And to your target:

.product(name: "Grantiva", package: "grantiva-swift"),

Initialize

Configure it early in your app's lifecycle:

import Grantiva

@main
struct LandmarksApp: App {
    init() {
        Grantiva.configure(
            apiKey: "your-api-key",
            projectID: "your-project-id"
        )
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Creating Flags

In the Grantiva dashboard, create a flag. Give it a key (like reservations), set it to disabled, and optionally configure a rollout percentage. The dashboard is straightforward - there's no concept of "environments" or "segments" to learn upfront. A flag is a name, a boolean, and an optional rollout.

The Grantiva Provider

If you're using the provider-agnostic pattern from the series, here's the FeatureFlagProvider implementation:

import Grantiva

struct GrantivaFlagProvider: FeatureFlagProvider {
    func fetchFlags() async throws -> [String: Bool] {
        try await Grantiva.fetchAll()
    }

    func observeFlag(_ name: String, handler: @Sendable @escaping (Bool) -> Void) {
        Grantiva.observe(name) { value in
            handler(value)
        }
    }

    func stopObserving(_ name: String) {
        Grantiva.stopObserving(name)
    }
}

That's the entire integration. Three method calls. Pass GrantivaFlagProvider() as your provider and everything works.

let flagStore = FeatureFlagStore(provider: GrantivaFlagProvider())

Without the Provider Pattern

If you're not using the abstraction layer and just want flags in your SwiftUI views, the SDK has a property wrapper that works directly:

import Grantiva

struct LandmarkDetailView: View {
    let landmark: Landmark
    @GrantivaFlag("reservations") private var reservationsEnabled
    @GrantivaFlag("social_sharing") private var socialSharingEnabled

    var body: some View {
        ScrollView {
            LandmarkHeader(landmark: landmark)
            LandmarkDescription(landmark: landmark)

            if reservationsEnabled {
                ReserveVisitButton(landmark: landmark)
            }

            if socialSharingEnabled {
                ShareButton(landmark: landmark)
            }
        }
    }
}

The @GrantivaFlag property wrapper fetches the flag value, caches it locally, and observes real-time changes from the dashboard. When you toggle a flag in the Grantiva dashboard, the view updates within seconds without the user restarting the app.

User Targeting

To enable percentage-based rollouts, pass a user identifier when configuring the SDK:

Grantiva.configure(
    apiKey: "your-api-key",
    projectID: "your-project-id",
    userID: authStore.userID?.uuidString
)

Rollout evaluation is deterministic - the same user always sees the same result for a given flag and percentage. This is calculated client-side using a hash of the user ID and flag name, so it works offline too.

Offline Defaults

The SDK caches the last known flag state locally. If the network is unavailable, it falls back to cached values. You can also provide compile-time defaults:

@GrantivaFlag("reservations", default: false) private var reservationsEnabled

The resolution order is: real-time update > cached value > default. The app always has an answer, even on first launch with no network.

Testing

For tests, disable the SDK and inject values directly:

Grantiva.setTestMode(flags: [
    "reservations": true,
    "social_sharing": false
])

Or if you're using the provider pattern, just use a mock provider - no SDK involvement at all.

What This Costs

The free tier covers most indie apps. You get unlimited flags, 10,000 evaluations per month, and real-time updates. If you outgrow that, paid tiers scale based on evaluation volume.

I built this because I needed it for my own apps. The pricing reflects that - it's meant to be accessible to solo developers and small teams, not just enterprises with procurement departments.