ابرووا
شروع رایگان
☰ فهرست مستندات · Android
به‌زودی. Abrova Flags به‌زودی فعال می‌شود. این صفحه توضیح می‌دهد چطور کار خواهد کرد؛ هنوز قابل استفاده نیست.
این صفحه هنوز ترجمه نشده — نسخه‌ی انگلیسی نمایش داده می‌شود. English

Android SDK

Feature flags for Android in Kotlin, evaluated by Abrova Flags for the current user and cached on the device. Typed getters, anonymous id, offline cache. Public API key only.

Android 0.2.0minSdk 21به‌روزرسانی: ۵ آبان ۱۴۰۵
Client-Safe

Uses the public API key only. Safe to ship in an app

Evaluated by the Service

The app receives values for this user. Targeting rules never reach the device

Typed Getters

getBoolean, getString, getNumber and getJson, each with a default

Offline Cache

The last values are stored in SharedPreferences and served at start and offline

Anonymous Id

Percentage rollouts also work before sign-in

Never Blocks

Getters return at once and never throw. Network work runs off the main thread

Abrova Flags Android SDK v0.2.0.

This page describes ir.abrova:abrova-flags-android 0.2.0. It is a breaking release: flags are evaluated by the service instead of on the device, and the getters changed. Coming from 0.1.x? See Upgrading from 0.1.x at the end of this page.

Requirements.

Android min SDK 21. The INTERNET permission is merged from the library manifest.

Installation#

Install from Abrova Center in two steps.

  1. Add the Abrova Center repository in settings.gradle (or the project build.gradle):
groovy
repositories {
    google()
    mavenCentral()
    maven { url "https://center.abrova.ir/api/packages/abrova/maven" }
}

Kotlin DSL: maven { url = uri("https://center.abrova.ir/api/packages/abrova/maven") }

  1. Add the dependency to your app's build.gradle.kts:
kotlin
dependencies {
    implementation("ir.abrova:abrova-flags-android:0.2.0")
}

Groovy:

groovy
implementation 'ir.abrova:abrova-flags-android:0.2.0'

Initialization#

Create one client for the app, for example in Application.onCreate, and call start(). Use your public API key (ab_live_...).

kotlin
import android.app.Application
import ir.abrova.flags.AbrovaFlags
import ir.abrova.flags.AbrovaFlagsConfig

class MyApp : Application() {
    lateinit var flags: AbrovaFlags
        private set

    override fun onCreate() {
        super.onCreate()
        flags = AbrovaFlags(
            applicationContext,
            AbrovaFlagsConfig(
                apiKey = "ab_live_xxx",
                environment = "production", // null uses the Default values
            ),
        )
        flags.start()
    }
}

start() returns immediately. It serves the values cached on the device and fetches fresh ones in the background. There is no shared instance in 0.2.0: keep the client you created.

To wait for the first values, for example on a splash screen:

kotlin
lifecycleScope.launch {
    val ready = flags.awaitReady(timeoutMs = 3_000) // false when the time ran out
}
Never use the server secret in an app.

The constructor throws an IllegalArgumentException if apiKey is a server secret (ab_srv_...). The server secret belongs on your backend, with the Node.js or Next.js server SDK.

Read flags#

Getters never block and never throw. Each one takes the default to use when the flag is unknown, when no values are available yet, or when the flag has another type.

kotlin
if (flags.isEnabled("new-checkout")) {
    // Show the new checkout
}

val dark = flags.getBoolean("dark-mode", false)
val theme = flags.getString("theme", "light")
val maxItems = flags.getNumber("max-items", 10.0)
val layout = flags.getJson("home-layout", mapOf("columns" to 2.0))
MethodReturns
isEnabled(key, defaultValue = false)true when the flag is served on for this user
getBoolean(key, defaultValue)Value of a boolean flag
getString(key, defaultValue)Value of a string flag
getNumber(key, defaultValue)Value of a number flag, as Double
getJson(key, defaultValue)Value of a json flag: Map, List, String, Double, Boolean or null
getAll()Every value held, as Map<String, FlagResult>

Details and reasons

kotlin
val d = flags.getStringDetail("theme", "light")
Log.d("flags", "${d.value} ${d.enabled} ${d.variant} ${d.reason} ${d.version}")

getBooleanDetail, getStringDetail, getNumberDetail and getJsonDetail check the flag type and return a FlagDetail. getDetail(key, defaultValue = null) returns the stored value without a type check.

ReasonMeaning
ONThe flag is on and has no variants. The value is true
VARIANTThe flag is on and a variant was served
DISABLEDThe flag is off in this environment. The flag off value is served
NOT_TARGETEDThe targeting rules did not match this user. The off value is served
OUTSIDE_ROLLOUTThe user is outside the rollout percentage. The off value is served
PREREQUISITE_FAILEDA flag this one depends on did not give the required result. The off value is served
NO_BUCKETING_IDA rollout or split needed a user id or anonymous id and none was sent
ERRORThe flag could not be evaluated. The off value is served
FLAG_NOT_FOUNDNo such flag. Your default is returned
NOT_READYNo cached or fetched values yet. Your default is returned
TYPE_MISMATCHFor example getString on a boolean flag. Your default is returned

Compare with the constants on FlagReason, for example d.reason == FlagReason.NOT_READY. isEnabled is true only for ON and VARIANT.

Identify the user#

Tell the SDK who the user is and what you know about them. Targeting rules and percentage rollouts are evaluated against this context.

kotlin
// After sign-in
flags.identify("user-123", mapOf("plan" to "pro", "country" to "IR", "beta" to true, "age" to 31))

// Replace all attributes
flags.setAttributes(mapOf("plan" to "business"))

// Signed out, attributes stay
flags.setUserId(null)

// Sign-out: clears the user id, the attributes and the cached values
flags.reset()
  • Each change fetches new values after a short pause (250 ms), so several calls in a row send one request. A call that changes nothing sends no request.
  • setAttributes and the attributes of identify replace the whole set. They do not merge.
  • When the user id changes, the previous user's values are dropped at once. Until the new values arrive, getters return your defaults (reason NOT_READY).
  • Attributes are a String (at most 1,024 characters), a Number, a Boolean, null, or a list of those. At most 100 attributes. Anything else is dropped with a log warning.
  • The user id and attributes are remembered on the device, so after a restart the client starts with the same user. Call reset() on sign-out.

If you call identify before start(), the first request already carries the user.

Anonymous id

Every install gets a random anonymous id (a UUID). It is stored on the device and sent with every request, so a 10% rollout reaches about 10% of signed-out installs too. When a user id is set, rollouts use the user id instead.

The anonymous id survives reset(). flags.resetAnonymousId() creates a new one. Read it with flags.anonymousId.

Events#

kotlin
import ir.abrova.flags.AbrovaFlagsError
import ir.abrova.flags.AbrovaFlagsListener

val listener = object : AbrovaFlagsListener {
    override fun onReady() {
        // First values are available (from the cache or the network). Fires once
    }

    override fun onUpdate(changedKeys: Set<String>) {
        // Values changed after a fetch
    }

    override fun onError(error: AbrovaFlagsError) {
        Log.w("flags", "${error.kind} ${error.statusCode} ${error.code} ${error.message}")
    }
}

flags.addListener(listener)
// later
flags.removeListener(listener)

All three methods are optional. Callbacks run on the main thread.

Activity Example#

kotlin
class CheckoutActivity : AppCompatActivity(), AbrovaFlagsListener {
    private val flags get() = (application as MyApp).flags

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        flags.addListener(this)
        render()
    }

    override fun onUpdate(changedKeys: Set<String>) {
        if ("new-checkout" in changedKeys) render()
    }

    override fun onDestroy() {
        flags.removeListener(this)
        super.onDestroy()
    }

    private fun render() {
        if (flags.isEnabled("new-checkout")) {
            setContentView(R.layout.activity_checkout_new)
        } else {
            setContentView(R.layout.activity_checkout)
        }
    }
}

Refresh, offline and errors#

The SDK fetches values:

  • on start(),
  • after a change of user or attributes, reset() and resetAnonymousId(),
  • when the app returns to the foreground and the values are older than 15 minutes,
  • on refresh(),
  • on a timer, only if you set refreshIntervalSeconds.
kotlin
// In a coroutine: true when the values are current
val ok = flags.refresh()

// With a callback on the main thread
flags.refresh { ok -> /* ... */ }
Each fetch counts as one evaluation.

Every request for values is counted as one flag evaluation, whatever the number of flags it returns (100,000 evaluations are free every month; the price above that is set at launch). This is why polling is off by default.

The last values are stored in SharedPreferences (never the API key) and served at the next start, before the network answers. A failed request never clears them.

  • Offline or server error: the last values keep being served. The SDK retries with a growing delay (up to 5 minutes).
  • Rate limited (429): the SDK waits the time the service asks for, then retries.
  • 401, 403 or 404 (wrong key, Flags not enabled for the project, unknown environment): automatic fetches stop until you call refresh().
  • Before the first values: flags.isReady is false and getters return your defaults.

flags.close() stops all network work. The cached values stay on the device.

Configuration Options#

OptionTypeDefaultDescription
apiKeyStringrequiredYour public API key (ab_live_xxx)
environmentString?nullEnvironment key (production, staging, development or your own). null uses the Default values
flagKeysList<String>?nullOnly fetch these flags. null fetches every flag of the project
refreshIntervalSecondsLong0Polling while the app is in the foreground. 0 is off; otherwise at least 60
cacheEnabledBooleantrueStore the last values and the user context on the device
requestTimeoutMsLong10000Network timeout per request
debugBooleanfalseVerbose logs. They never contain the API key

Upgrading from 0.1.x#

0.2.0 is a new API, and 0.1.x clients stop receiving flags. Update the dependency and change these calls:

0.1.x0.2.0
suspend fun init(callback)start(), plus addListener or awaitReady()
setUserId + setUserAttributes, mergedidentify(userId, attributes), setUserId, setAttributes. Attributes are replaced
getValue(key, defaultValue)getBoolean / getString / getNumber / getJson
evaluate(key) returning FlagEvalResultgetDetail(key) returning FlagDetail, with reason and version
getAll() returning List<FeatureFlag> with rulesgetAll() returning Map<String, FlagResult>, values only
AbrovaFlags.getInstance()Keep your own instance
enableOfflineCachecacheEnabled
reset() / dispose()reset() for sign-out, close() to stop the client
  • The values cached by 0.1.x are removed the first time 0.2.0 runs.
  • Rollouts use a new bucketing method, so users are assigned to rollouts and variants again once.

Best Practices#

One client per app

Create the client in your Application class and reuse it

Always give a sensible default

The default is what your users see before the first values and when a flag is removed

Reset on sign-out

The user id and attributes are remembered on the device. Call reset() when the user signs out

Listen for updates

Use addListener to redraw when values change instead of calling refresh() often

Leave polling off

Values refresh on start, on context changes and on app resume. Turn polling on only if you need faster updates

این صفحه مفید بود؟
گزارش مشکل