OpenFeature Android Provider
OpenFeature is an open standard that provides a vendor-agnostic, community-driven API for feature flagging that works with DevCycle.
DevCycle provides an Android implementation of the OpenFeature Provider interface, allowing you to use DevCycle as the feature flag management system behind the standardized OpenFeature API.
Requirements
This integration requires Android API version 23+ and is available starting from DevCycle Android SDK version >= 2.6.0
.
Installation
The DevCycle OpenFeature Provider is included in the main Android SDK. To add it to your project:
Gradle
implementation("com.devcycle:android-client-sdk:2.6.0+")
implementation("dev.openfeature:android-sdk:0.4.1+")
Maven
<dependency>
<groupId>com.devcycle</groupId>
<artifactId>android-client-sdk</artifactId>
<version>2.6.0+</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>dev.openfeature</groupId>
<artifactId>android-sdk</artifactId>
<version>0.4.1+</version>
<scope>compile</scope>
</dependency>
This package automatically includes the OpenFeature Android SDK as a dependency.
Getting Started
Initialize the DevCycleProvider
with your <DEVCYCLE_MOBILE_SDK_KEY>
and set it as the provider for OpenFeature, which will initialize the DevCycle Android SDK internally:
Kotlin Example
import com.devcycle.sdk.android.openfeature.DevCycleProvider
import dev.openfeature.sdk.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
// Set up the evaluation context
val evaluationContext = ImmutableContext(
targetingKey = "user-123",
attributes = mutableMapOf(
"email" to Value.String("[email protected]"),
"name" to Value.String("Test User"),
"customData" to Value.Structure(mapOf(
"customkey" to Value.String("customValue")
))
)
)
// Initialize OpenFeature with the DevCycle provider
coroutineScope.launch(Dispatchers.IO) {
// Create the DevCycle provider
val provider = DevCycleProvider(
sdkKey = "<DEVCYCLE_MOBILE_SDK_KEY>",
context = applicationContext
)
// Set the provider and wait for initialization
OpenFeatureAPI.setProviderAndWait(provider, evaluationContext)
// Get a client
val client = OpenFeatureAPI.getClient()
}
Java Example
import com.devcycle.sdk.android.openfeature.DevCycleProvider;
import dev.openfeature.sdk.*;
// Create evaluation context
Map<String, Value> attributes = new HashMap<>();
attributes.put("email", new Value.String("[email protected]"));
attributes.put("name", new Value.String("Test User"));
ImmutableContext evaluationContext = new ImmutableContext(
"user-123", // targetingKey
attributes
);
// Create the DevCycle provider
DevCycleProvider provider = new DevCycleProvider(
"<DEVCYCLE_MOBILE_SDK_KEY>",
getApplicationContext(),
null, // options (optional)
Collections.emptyList(), // hooks (optional)
new DevCycleProviderMetadata() // metadata (optional)
);
// Set the provider
OpenFeatureAPI.setProvider(provider);
Using Variable Value
Use a variable value by passing the variable key and default value to one of the OpenFeature flag evaluation methods:
// Get a client
val client = OpenFeatureAPI.getClient()
// Evaluate flags
val boolValue = client.getBooleanValue("my-boolean-flag", false)
val stringValue = client.getStringValue("my-string-flag", "default")
val intValue = client.getIntegerValue("my-int-flag", 0)
val doubleValue = client.getDoubleValue("my-double-flag", 0.0)
println("Bool flag value: $boolValue")
println("String flag value: $stringValue")
println("Int flag value: $intValue")
println("Double flag value: $doubleValue")
Setting User Context
When the user's context is updated use OpenFeatureAPI.setEvaluationContext()
:
// Update context later if needed
val newContext = ImmutableContext(
targetingKey = "user-123",
attributes = mutableMapOf(
"country" to Value.String("CA")
)
)
// This will trigger DevCycle.identifyUser internally
OpenFeatureAPI.setEvaluationContext(newContext)
Passing DevCycleOptions to the DevCycleProvider
Ensure that you pass any custom DevCycleOptions
to the DevCycleProvider
constructor:
// Configure DevCycle options if needed
val options = DevCycleOptions.builder()
.logLevel(LogLevel.DEBUG)
.build()
val provider = DevCycleProvider(
sdkKey = "<DEVCYCLE_MOBILE_SDK_KEY>",
context = applicationContext,
options = options
)
Evaluation Context
The evaluation context is used to provide user information for targeting. You can set properties such as targetingKey
, userId
, email
, name
, and custom attributes. These are mapped to DevCycleUser
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
:
val newContext = ImmutableContext(
targetingKey = "user-123",
attributes = mutableMapOf(
"userId" to Value.String("user-123"),
"email" to Value.String("[email protected]"),
"name" to Value.String("name"),
"language" to Value.String("en"),
"country" to Value.String("CA"),
"appVersion" to Value.String("1.0.11"),
"appBuild" to Value.Integer(1000),
"customData" to Value.Structure(mapOf(
"custom" to Value.String("data")
)),
"privateCustomData" to Value.Structure(mapOf(
"private" to Value.String("data")
))
)
)
Required Context Properties
For the DevCycle SDK to work properly, the Context
must contain one of the following:
- A
targetingKey
oruser_id
/userId
to identify the user - Set
isAnonymous
totrue
for an anonymous user - An empty
Context
will default to an anonymous user, but it is recommended to settargetingKey
orisAnonymous = true
.
For anonymous users, you can create the context like this:
val userContext = ImmutableContext(
attributes = mutableMapOf(
"isAnonymous" to Value.Boolean(true)
)
)
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 in the following structure:
val newContext = ImmutableContext(
targetingKey = "user-123",
attributes = mutableMapOf(
"obj" to Value.Structure(mapOf(
"key" to Value.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
val arrVar = ofClient.getObjectValue(
"json-flag",
Value.List(listOf(Value.String("array")))
)
val numVar = ofClient.getObjectValue(
"json-flag",
Value.Double(610.0)
)
val boolVar = ofClient.getObjectValue(
"json-flag",
Value.Boolean(false)
)
val stringVar = ofClient.getObjectValue(
"json-flag",
Value.String("string")
)
val nullVar = ofClient.getObjectValue(
"json-flag",
Value.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
val objVar = ofClient.getObjectValue(
"objVar",
Value.Structure(mapOf(
"key" to Value.String("value")
))
)
Tracking Events
The OpenFeature Android SDK supports tracking events through the track
method. You can track custom events with optional numeric values and metadata:
val client = OpenFeatureAPI.getClient()
// Simple event tracking
client.track("page_view")
// Track with event details
client.track(
"purchase",
TrackingEventDetails(
value = 99.99,
structure = ImmutableStructure(mapOf(
"product_id" to Value.String("abc123"),
"quantity" to Value.Integer(2)
))
)
)
Async/Await and Coroutines
The Android SDK provides full support for Kotlin coroutines. All provider operations that involve network calls or initialization are suspend functions:
coroutineScope.launch(Dispatchers.IO) {
try {
// Initialize and wait
OpenFeatureAPI.setProviderAndWait(provider, evaluationContext)
// Provider is ready, evaluate flags
val client = OpenFeatureAPI.getClient()
val flagValue = client.getBooleanValue("my-flag", false)
} catch (e: Exception) {
// Handle initialization errors
println("Failed to initialize: ${e.message}")
}
}
You can also use the non-blocking API:
// Set provider without waiting
OpenFeatureAPI.setProvider(provider)
// Check status later
val status = OpenFeatureAPI.getStatus()
if (status == OpenFeatureStatus.Ready) {
// Provider is ready to use
}
Example App
An example Android application demonstrating how to use the DevCycle OpenFeature Provider can be found in the Examples directory.
Development
For more details, see the DevCycle Android SDK GitHub repository.