Covers advanced topics (different config options and scenarios)
The Harness Feature Flags SDK for Android supports flexible initialization strategies to accommodate different application startup requirements. You can choose between an asynchronous (non-blocking) or synchronous (blocking) approach to initialize the SDK.
To avoid blocking the main thread, the SDK provides asynchronous initialization options. This method allows your application to remain responsive while the SDK initializes in the background.
Usage:
val client = CfClient()
client.initialize(this, apiKey, sdkConfiguration, target)
{ info, result ->
if (result.isSuccess) {
Log.i("SDKInit", "Successfully initialized client: " + info)
} else {
Log.e("SDKInit", "Failed to initialize client", result.error)
}
}
This approach is non-blocking and leverages a callback mechanism to inform the application about the SDK's initialization status. Here's how it works:
-
Success Callback: This callback is invoked once when the SDK is successfully initialized. It signifies that the SDK is ready for use. If the SDK disconnects from Harness SaaS after this point, it will be able to use cached values for evaluations and will attempt to reconnect.
-
Failure Callback: This callback may be invoked multiple times if the SDK encounters errors during the initialization process. Each invocation provides an error detailing the cause of failure. The SDK will automatically attempt to retry authentication, leading to multiple invocations of this callback until a successful initialization occurs.
If you don't want to use a callback, you can simply initialize the SDK. Defaults will be served for any variation calls until the SDK can complete initialization.
val client = CfClient()
client.initialize(this, apiKey, sdkConfiguration, target)
For scenarios where it's critical to have feature flags loaded and evaluated before proceeding, the SDK offers a blocking initialization method. This approach ensures that the SDK is fully authenticated and the feature flags are populated from the cache or network before moving forward. If the SDK disconnects from Harness SaaS after successfully initializing, it will be able to use cached values for evaluations and will attempt to reconnect.
This will block the UI thread, so use synchronous initialization only if absolutely required. It is recommended to provide a timeout to this call, so in the event
the SDK is unable to initialize in a set time, then default variations can be used.
The SDK will retry to initialize in the background after a timeout has occurred, but default variations will be served until it can initialize.
Usage:
client.initialize(context, apiKey, configuration, target);
boolean isInitialized = client.waitForInitialization(timeoutMs);
if (isInitialized) {
Log.i("SDKInit", "Successfully initialized client)
} else {
Log.e("SDKInit", "Failed to initialize client")
}
After invoking initialize(...)
, calling waitForInitialization(long timeoutMs)
blocks the calling thread until the SDK completes its authentication process or until the specified timeout elapses. A timeoutMs value of 0 waits indefinitely.
Use this method cautiously to prevent blocking the main UI thread, which can lead to a poor user experience.
By default, Harness Feature Flags SDK has streaming enabled and polling enabled. Both modes can be toggled according to your preference using the SDK's configuration.
Streaming mode establishes a continuous connection between your application and the Feature Flags service.
This allows for real-time updates on feature flags without requiring periodic checks.
If an error occurs while streaming and pollingEnabled
is set to true
,
the SDK will automatically fall back to polling mode until streaming can be reestablished.
If pollingEnabled
is false
, streaming will attempt to reconnect without falling back to polling.
In polling mode, the SDK will periodically check with the Feature Flags service to retrieve updates for feature flags. The frequency of these checks can be adjusted using the SDK's configurations.
If both streaming and polling modes are disabled (enableStream: false
and enablePolling: false
),
the SDK will not automatically fetch feature flag updates after the initial fetch.
This means that after the initial load, any changes made to the feature flags on the Harness server will not be reflected in the application until the SDK is re-initialized or one of the modes is re-enabled.
This configuration might be useful in specific scenarios where you want to ensure a consistent set of feature flags for a session or when the application operates in an environment where regular updates are not necessary. However, it's essential to be aware that this configuration can lead to outdated flag evaluations if the flags change on the server.
To configure the modes see Configuration options
The following configuration options are available to control the behaviour of the SDK. You can provide options by adding them to the SDK Configuration.
val sdkConfiguration = CfConfiguration.builder()
.baseUrl("https://config.ff.harness.io/api/1.0")
.eventUrl("https://events.ff.harness.io/api/1.0")
.pollingInterval(60)
.enableStream(true)
.enablePolling(true)
.enableAnalytics(true)
.build()
Name | Config Option | Description | default |
---|---|---|---|
baseUrl | baseUrl("https://config.ff.harness.io/api/1.0") | the URL used to fetch feature flag evaluations. You should change this when using the Feature Flag proxy to http://localhost:7000 | https://config.ff.harness.io/api/1.0 |
eventsUrl | eventUrl("https://events.ff.harness.io/api/1.0") | the URL used to post metrics data to the feature flag service. You should change this when using the Feature Flag proxy to http://localhost:7000 | https://events.ff.harness.io/api/1.0 |
pollInterval | pollingInterval(60) | when running in stream mode, the interval in seconds that we poll for changes. | 60 |
enableStream | enableStream(true) | Enable streaming mode. | true |
enableAnalytics | enableAnalytics(true) | Enable analytics. Metrics data is posted every 60s | true |
We use SLF4J. For more information see https://www.slf4j.org/.
To enable it add the following dependencies to your Gradle build file. You can use any framework you prefer, here we are using logback-android
and BasicLogcatConfigurator
to bridge SLF4J to Logcat.
implementation 'org.slf4j:slf4j-api:2.0.9'
implementation 'com.github.tony19:logback-android:3.0.0'
Then add a configurator to you app
companion object {
init {
BasicLogcatConfigurator.configureDefaultContext()
}
}
Logs can be defined for classes like so
private val log: Logger = LoggerFactory.getLogger(MainActivity::class.java)
...
log.info("my log message")
Log items will be visible in Android Studio's Logcat tab for each emulator you are running.
A full example can be found here
CfClient
is base class that provides all features of SDK. This is singleton and it is acessed with CfClient.getInstance()
.
val sdkConfiguration = CfConfiguration.builder()
.baseUrl("BASE_API_URL")
.pollingInterval(60) // Time in seconds
.enableStream(true)
.streamUrl("STREAM_URL")
.build()
val target = Target().identifier("target")
CfClient.getInstance().initialize(context, "YOUR_API_KEY", sdkConfiguration, target)
if (CfClient.getInstance().waitForInitialization(15_000)) {
// Congratulations your SDK has been initialized with success!
// After this callback is executed, You are ready to use the SDK!
} else {
// Timeout - check logcat for reason - SDK will attempt to reauthenticate in the background and serve defaults in the mean time
}
target
represents a desired target for which we want features to be evaluated.
"YOUR_API_KEY"
is a authentication key, needed for access to Harness services.
The Public API exposes a few methods that you can utilize:
initialize(...)
public void initialize(
final Context context,
final String apiKey,
final CfConfiguration configuration,
final Target target
) throws IllegalStateException
-
public boolean boolVariation(String evaluationId, boolean defaultValue)
-
public String stringVariation(String evaluationId, String defaultValue)
-
public double numberVariation(String evaluationId, double defaultValue)
-
public JSONObject jsonVariation(String evaluationId, JSONObject defaultValue)
-
public void registerEventsListener(EventsListener listener)
-
public void unregisterEventsListener(EventsListener observer)
-
public void refreshEvaluations()
-
public void close()
It is possible to fetch a value for a given evaluation. Evaluation is performed based on different type. In case there is no evaluation with provided id, the default value is returned.
Use appropriate method to fetch the desired Evaluation of a certain type.
// Get boolean evaluation:
val evaluation: Boolean = CfClient.getInstance().boolVariation("demo_evaluation", false)
// Get number evaluation:
val numberEvaluation: Double = CfClient.getInstance().numberVariation("demo_number_evaluation", 0.0)
// Get String evaluation:
val stringEvaluation: String = CfClient.getInstance().stringVariation("demo_string_evaluation", "demo_value")
// Get JSON evaluation:
val jsonEvaluation: String = CfClient.getInstance().jsonVariation("demo_string_evaluation", JSONObject("{}"))
Note: These methods must not be executed on the application's main thread since they could trigger the network operations.
This method provides a way to register a listener for different events that might be triggered by SDK, indicating specific change in SDK itself.
private var eventsListener = EventsListener { event ->
CfLog.OUT.v(tag, "Event: ${event.eventType}")
}
val registerEventsOk = CfClient.getInstance().registerEventsListener(eventsListener)
val unregisterEventsOk = CfClient.getInstance().unregisterEventsListener(eventsListener)
val success = CfClient.getInstance().unregisterEventsListener(eventsListener)
Triggered event will have one of the following types:
public enum EVENT_TYPE {
SSE_START,
SSE_RESUME
SSE_END,
EVALUATION_CHANGE,
EVALUATION_RELOAD
}
Following table provides summary on possible event types and corresponding responses.
EVENT_TYPE | Response |
---|---|
SSE_START | - |
SSE_RESUME | - |
SSE_END | - |
EVALUATION_CHANGE | Evaluation |
EVALUATION_RELOAD | List<Evaluation> |
To avoid unexpected behaviour, when listener is not needed anymore, a caller should call
CfClient.getInstance().unregisterEventsListener(eventsListener)
. This way the sdk will remove desired listener from internal list.
Metrics API endpoint can be changed like this:
val remoteConfiguration = CfConfiguration.builder()
.enableStream(true)
.pollingInterval(60)
.enableAnalytics(true)
.eventUrl(METRICS_API_EVENTS_URL)
.build()
Otherwise, the default metrics endpoint URL will be used.
To avoid potential memory leak, when SDK is no longer needed (when the app is closed, for example), a caller should call this method:
CfClient.getInstance().close()
In order to clone SDK repository properly perform cloning like in the following example:
git clone --recurse-submodules [email protected]:harness/ff-android-client-sdk.git