Split OpenFeature Provider for Dart

Adapts the Split Dart client SDK (splitio_client_side) to the OpenFeature FeatureProvider contract defined by openfeature_dart_client_sdk.

Overview

This provider lets you evaluate Split feature flags through OpenFeature's standard client-side (static-context) API.

Getting started

Add the dependency

Both openfeature_dart_client_sdk and splitio_client_side are peer packages and must be declared alongside this provider.

dependencies:
  split_openfeature_provider_dart_client: ^0.1.0
  openfeature_dart_client_sdk: ^0.0.1-beta.1
  splitio_client_side: ^1.1.0

Register the Split provider with OpenFeature

Construct a SplitFactory, pass it to OpenFeatureSplitProvider, and register the provider with the OpenFeature API. The provider never constructs its own factory — the SDK key, config, and initial matching key are the caller's responsibility.

import 'package:openfeature_dart_client_sdk/openfeature_dart_client_sdk.dart';
import 'package:split_openfeature_provider_dart_client/split_openfeature_provider_dart_client.dart';
import 'package:splitio_client_side/splitio_client_side.dart'
    show SplitFactory, SplitClientConfig;

Future<void> main() async {
  final splitFactory = SplitFactory.create(
    'CLIENT_SIDE_SDK_KEY',
    SplitClientConfig(),
    'TARGETING_KEY',
  );

  final provider = OpenFeatureSplitProvider(splitFactory);

  // Waits for the Split SDK to become ready or errors on timeout.
  await OpenFeatureAPI.instance.setProviderAndWait(provider);

  final client = OpenFeatureAPI.instance.getClient();
}

Evaluating flags

After registration you use OpenFeature's evaluation API as documented on openfeature.dev.

The provider uses one field on every EvaluationContext used for evaluation:

  • targetingKey — identifies the subject of evaluation.

trafficType is optional on the evaluation context for flag lookups (Split's client-side evaluation ignores it). It is required only when calling track.

await OpenFeatureAPI.instance.setEvaluationContextAndWait(
  EvaluationContext(
    targetingKey: 'TARGETING_KEY',
  ),
);

final showBanner = client.getBooleanValue('show_banner', false);

Evaluate with details

Use the get*Details variants to retrieve the value alongside variant, reason, error code, and flag metadata. This provider exposes the raw Split treatment config JSON string under flagMetadata['config'].

final details = client.getBooleanDetails('bool_flag', false);
final config = details.flagMetadata['config']; // raw JSON string, or ''

Evaluate with attributes

Attributes on the EvaluationContext (other than trafficType) are forwarded to Split as evaluation attributes.

await OpenFeatureAPI.instance.setEvaluationContextAndWait(
  EvaluationContext(
    targetingKey: 'TARGETING_KEY',
    attributes: {
      'plan': 'premium',
      'coupon': 'WELCOME10',
    },
  ),
);

final details = client.getBooleanDetails('bool_flag', false);

Treatment parsing

The provider maps Split's string treatments to typed OpenFeature values:

OpenFeature method Accepted treatments
getBooleanValue / Details 'on', 'true' → true; 'off', 'false' → false
getStringValue / Details Any Split treatment string
getIntegerValue / Details Any string parseable by int.tryParse
getDoubleValue / Details Any string parseable by double.tryParse (NaN rejected)
getStructureValue / Details Any JSON object literal (arrays, scalars, null rejected)

Boolean matching is case-insensitive. Numeric parsing accepts 'Infinity' and '-Infinity' but rejects 'NaN'. Any treatment that fails to parse surfaces as an OpenFeature evaluation error with ErrorCode.parseError. A Split 'control' treatment surfaces as ErrorCode.flagNotFound.

Provider events

The provider emits OpenFeature ProviderEvents driven by the Split SDK lifecycle:

  • SDK_READY → ProviderEventType.ready.
  • Split flag/segment updates → ProviderEventType.configurationChanged with the changed flag names in flagsChanged.
final client = OpenFeatureAPI.instance.getClient();
client.addHandler(ProviderEventType.configurationChanged, (details) {
  print('Flags changed: ${details.flagsChanged}');
});

Tracking

track forwards to Split's event tracker. The event name must be non-empty and the current EvaluationContext MUST carry a non-empty String trafficType attribute — call setEvaluationContext with a valid trafficType before invoking track().

await OpenFeatureAPI.instance.setEvaluationContextAndWait(
  EvaluationContext(
    targetingKey: 'user-123',
    // trafficType is REQUIRED for track(); must be a non-empty String.
    attributes: {'trafficType': 'account'},
  ),
);

client.track(
  'checkout.completed',
  details: TrackingEventDetails(
    value: 19.99,
    attributes: {'plan': 'pro', 'coupon': 'WELCOME10'},
  ),
);

TrackingEventDetails.value is a num?; integer values are forwarded to Split as doubles. attributes on the details is forwarded as Split event properties.

If trafficType is missing when track() is called, the provider throws OpenFeatureException(ErrorCode.invalidContext, ...). Note that the OpenFeature client's track() is fire-and-forget per spec, so it swallows the exception; the provider also emits a print diagnostic tagged [split_openfeature_provider] so the refusal is observable in the console.

Reconciling context changes

Calling OpenFeatureAPI.instance.setEvaluationContextAndWait(newContext) with a different targetingKey triggers the provider's onContextChanged. The provider rebinds to a new Split client for the new key, cancels the previous flag update subscription, waits for the new client to become ready, and emits ProviderEventType.configurationChanged.

If the new client times out before becoming ready, the future returned by setEvaluationContextAndWait completes with an OpenFeatureException (ErrorCode.general, 'Split SDK ready timed out').

Shutting down

Call shutdown when you're done with the provider (typically only in tests or in short-lived programs). It cancels the flag update subscription, destroys the Split factory, and closes the event stream.

await provider.shutdown();

Behavior notes

  1. trafficType is required at track() time, not evaluation time. Split's client-side evaluation ignores trafficType, so it is optional on the EvaluationContext for resolve*Value calls. Calling track() without a trafficType throws OpenFeatureException(ErrorCode.invalidContext, ...) rather than silently defaulting, which would misattribute tracking events across traffic types. Present-but-invalid values (non-String, empty String, whitespace-only String) are rejected at context transform time.
  2. No ready-from-cache / no update type metadata. The Split Dart client SDK does not currently expose an SDK_READY_FROM_CACHE equivalent or an update-type field on flag-change events, so the provider never emits ProviderEventType.stale and the configurationChanged event carries only flagsChanged (no metadata['type']).

Platform support

Pure Dart. Runs everywhere splitio_client_side runs: Dart VM (server, CLI), Flutter (Android, iOS, Web, Desktop), and browser-compiled Dart. The provider package contains no dart:html, dart:io, or dart:ffi imports.

OpenFeature specification compliance

This provider targets the OpenFeature specification v0.9.0. Section 2 (Provider) conformance:

Clause Status Notes
2.1.1 metadata name Yes metadata.name returns "split".
2.2 flag resolution (all types) Yes Boolean / string / integer / double / structure supported.
2.2.5 reason Yes Populated as "TARGETING_MATCH" for successful evaluations.
2.2.9 flag metadata Yes Raw Split treatment config JSON exposed under flagMetadata['config'].
2.3.1 provider hooks mechanism Yes Empty hook list; no provider-managed hooks.
2.4.1 initialize with domain Yes Signature accepts domain; provider is not domain-scoped.
2.5.1–2.5.3 shutdown Yes Idempotent; provider rejects initialize after shutdown.
2.6.1 onContextChanged Yes Reconciles by rebinding to a new Split client for the new targeting key.
2.7.1 tracking Yes track forwards to Split, requires a trafficType attribute.
2.8.1–2.8.4 provider events Yes Emits PROVIDER_READY, PROVIDER_ERROR, PROVIDER_CONFIGURATION_CHANGED, and PROVIDER_CONTEXT_CHANGED.
PROVIDER_STALE Not emitted The Split Dart client SDK does not expose a SDK_READY_FROM_CACHE signal today.

License

Licensed under the Apache License, Version 2.0. See Apache License.

Libraries

split_openfeature_provider_dart_client
Split OpenFeature provider for Dart — public API.