Skip to content

iOS Usage

kirich1409 edited this page May 18, 2026 · 1 revision

iOS Usage

Featured exposes its Kotlin API to Swift via SKIE. No Kotlin toolchain is needed in Xcode — the framework is built by the KMP Gradle build and consumed as a prebuilt XCFramework or Swift Package.

Import the module:

import FeaturedCore

FeaturedCore is the Swift product name. CoreConfigParam is the Kotlin ConfigParam<T> type as seen from Swift.

Initializing ConfigValues

Call initialize() before serving any screen, then trigger a background fetch:

@MainActor
class AppState: ObservableObject {
    let configValues: ConfigValues

    init() {
        configValues = ConfigValues(
            localProvider: nil,
            remoteProvider: nil,
            onProviderError: { error in print("Featured error: \(error)") }
        )
    }

    func setup() async {
        do {
            try await configValues.initialize()
            try await configValues.fetch()
        } catch {
            print("Featured setup error: \(error)")
        }
    }
}

Pass provider instances to localProvider and remoteProvider as needed. At least one must be non-nil.

One-shot async read

let configValue = try await configValues.getValue(param: FeatureFlags.shared.newCheckout)
let isEnabled: Bool = configValue.value

FeatureFlags.shared.newCheckout is a CoreConfigParam<KotlinBoolean> — the Kotlin-generated ConfigParam accessed through the Swift wrapper your team writes (see Declaring Flags).

AsyncStream (reactive)

SKIE bridges the Kotlin Flow as an AsyncStream:

for await configValue in configValues.observe(param: FeatureFlags.shared.newCheckout) {
    updateUI(configValue.value)
}

Use this inside a Task tied to your view's lifetime:

.task {
    for await configValue in configValues.observe(param: FeatureFlags.shared.newCheckout) {
        isNewCheckoutEnabled = configValue.value
    }
}

Combine publisher

Build a Combine publisher on top of AsyncStream:

import Combine

class CheckoutViewModel: ObservableObject {
    @Published var isNewCheckoutEnabled: Bool = false

    private let configValues: ConfigValues

    init(configValues: ConfigValues) {
        self.configValues = configValues
    }

    func startObserving() {
        Task {
            for await configValue in configValues.observe(param: FeatureFlags.shared.newCheckout) {
                await MainActor.run {
                    self.isNewCheckoutEnabled = configValue.value
                }
            }
        }
    }
}

If your Swift wrapper exposes a publisher(for:) method:

featureFlags.publisher(for: newCheckoutFlag)
    .receive(on: DispatchQueue.main)
    .sink { isEnabled in updateUI(isEnabled) }
    .store(in: &cancellables)

SwiftUI integration

struct CheckoutView: View {
    @StateObject private var viewModel: CheckoutViewModel

    var body: some View {
        Group {
            if viewModel.isNewCheckoutEnabled {
                NewCheckoutFlow()
            } else {
                LegacyCheckoutFlow()
            }
        }
        .onAppear { viewModel.startObserving() }
    }
}

Dead-code elimination

For release builds, Featured can physically strip code behind disabled flags at compile time. A flag declared with default = false in the KMP module generates an xcconfig condition DISABLE_<FLAG_KEY> that feeds into Xcode as a Swift compilation condition. See iOS — xcconfig DCE for the setup procedure.

Clone this wiki locally