Skip to main content

OpenFeature iOS Provider

OpenFeature is an open standard that provides a vendor-agnostic, community-driven API for feature flagging that works with DevCycle.

DevCycle provides an iOS implementation of the OpenFeature Provider interface, allowing you to use DevCycle as the feature flag management system behind the standardized OpenFeature API.

GitHub

Requirements

OpenFeature currently only supports Swift Package Manager (SPM), and has higher platform requirements than the base DevCycle SDK:

  • iOS 14.0+
  • tvOS 14.0+
  • watchOS 7.0+
  • macOS 11.0+

Swift Package Manager Installation

Add the dependency to your Package.swift file:

.package(url: "https://github.com/DevCycleHQ/ios-openfeature-provider.git", from: "1.0.0")

Then add DevCycleOpenFeatureProvider to your target's dependencies:

.target(
name: "YourTarget",
dependencies: [
.product(name: "DevCycleOpenFeatureProvider", package: "ios-openfeature-provider")
]
)

This package automatically includes the DevCycle and OpenFeature SDKs as dependencies.

Getting Started

Initialize the DevCycleProvider with your <DEVCYCLE_MOBILE_SDK_KEY> and set it as the provider for OpenFeature, which will initialize the DevCycle iOS SDK internally:

import OpenFeature
import DevCycle
import DevCycleOpenFeatureProvider

// Configure the DevCycle provider (you can also pass DevCycleOptions if needed)
let provider = DevCycleProvider(sdkKey: "<DEVCYCLE_MOBILE_SDK_KEY>")

// Set up the evaluation context
let evaluationContext = MutableContext(
targetingKey: "user-123",
structure: MutableStructure(attributes: [
"email": .string("[email protected]"),
"name": .string("Test User"),
"customData": .structure(["customkey": .string("customValue")])
])
)

// Initialize OpenFeature with the DevCycle provider
Task {
// Set the provider with initial context
await OpenFeatureAPI.shared.setProviderAndWait(
provider: provider, initialContext: evaluationContext)

// Get a client
let client = OpenFeatureAPI.shared.getClient()
}

Use a variable value by passing the variable key and default value to one of the OpenFeature flag evaluation methods:

// Get a client
let client = OpenFeatureAPI.shared.getClient()

// Evaluate flags
let boolValue = client.getBooleanValue(key: "my-boolean-flag", defaultValue: false)
let stringValue = client.getStringValue(key: "my-string-flag", defaultValue: "default")

print("Bool flag value: \(boolValue)")
print("String flag value: \(stringValue)")

When the user's context is updated use OpenFeatureAPI.shared.setEvaluationContextAndWait():

// Update context later if needed
let newContext = MutableContext(
targetingKey: "user-123",
structure: MutableStructure(attributes: [
"country": .string("CA")
])
)
await OpenFeatureAPI.shared.setEvaluationContextAndWait(evaluationContext: newContext)

Passing DevCycleOptions to the DevCycleProvider

Ensure that you pass any custom DevCycleOptions to the DevCycleProvider constructor

// Configure DevCycle options if needed
let options = DevCycleOptions.builder()
.logLevel(.debug)
.build()

let provider = DevCycleProvider(sdkKey: "<DEVCYCLE_MOBILE_SDK_KEY>", options: options)

Evaluation Context

The evaluation context is used to provide user information for targeting. You can set properties such as targetingKey, email, name, and custom attributes. These are mapped to DevCycle user properties automatically.

The provider will automatically translate known DevCycleUser properties from the OpenFeature context to the DevCycleUser object. For example these context properties will be set on the DevCycleUser:

let newContext = MutableContext(
targetingKey: "user-123",
structure: MutableStructure(attributes: [
"userId": .string("user-123"),
"email": .string("[email protected]"),
"name": .string("name"),
"language": .string("en"),
"country": .string("CA"),
"appVersion": .string("1.0.11"),
"appBuild": .double(1000),
"customData": MutableStructure(attributes: [
"custom": .string("data")
]),
"privateCustomData": MutableStructure(attributes: [
"private": .string("data")
])
])
)

Context Limitations

DevCycle only supports flat JSON Object properties used in the Context. Non-flat properties will be ignored.

For example obj will be ignored:

let newContext = MutableContext(
targetingKey: "user-123",
structure: MutableStructure(attributes: [
"obj": MutableStructure(attributes: [
"key": .string("value")
]),
])
)

JSON Flag Limitations

The OpenFeature spec for JSON flags allows for any type of valid JSON value to be set as the flag value.

For example, the following are all valid default value types to use with OpenFeature:

// Invalid JSON values for the DevCycle SDK, will return defaults
let arrVar = ofClient.getObjectValue(
key: "json-flag",
defaultValue: .list([.string("array")])
)

let numVar = ofClient.getObjectValue(
key: "json-flag",
defaultValue: .double(610)
)

let boolVar = ofClient.getObjectValue(
key: "json-flag",
defaultValue: .boolean(false)
)

let stringVar = ofClient.getObjectValue(
key: "json-flag",
defaultValue: .string("string")
)

let nullVar = ofClient.getObjectValue(
key: "json-flag",
defaultValue: .null
)

However, these are not valid types for the DevCycle SDK, the DevCycle SDK only supports JSON Objects:

// Valid JSON Object as the default value, will be evaluated by the DevCycle SDK
let objVar = ofClient.getObjectValue(
key: "objVar",
defaultValue: .structure([
"key": .string("value")
])
)

Tracking Events

The OpenFeature iOS SDK doesn't currently support the track method. You will need to use the underlying DevCycle iOS SDK from the provider to track events:

let client = dvcProvider?.devcycleClient

let event = try! DevCycleEvent.builder()
.type("my_event")
.target("my_target")
.value(3)
.metaData(["key": "value"])
.clientDate(Date())
.build()
client?.track(event)

Example App

An example iOS application demonstrating how to use the DevCycle OpenFeature Provider can be found in the Examples directory.

Development

For more details, see the DevCycleHQ/ios-openfeature-provider GitHub repository.