# Feature flags in the Android SDK

`Pushwoosh.getInstance().featureFlags` gives your Android app the [feature flags](/product/audience-data-and-segmentation/feature-flags/) configured for the application in the Control Panel. Pushwoosh evaluates the flag rules on the server for the current device and user, and the SDK returns the result: whether the flag is on for this device and which property values apply.

<Aside type="tip" title="Beta feature">
Feature flags are in beta and available only on plans that include them. On other plans the server returns no flags, so every flag falls back to the default you pass in code.
</Aside>

## Prerequisites

* Pushwoosh Android SDK 6.16.0 or later, integrated as described in the [Android SDK guide](/developer/pushwoosh-sdk/android-sdk/). Feature flags are part of the main `com.pushwoosh:pushwoosh` library, so you don't add another dependency. Until the SDK is initialized, `get` returns `null`.
* At least one flag created in **Audience** → **Feature Flags**. You need its **Key** and the keys and types of its properties. Use the keys exactly as they appear there, letter case included. For example, `get("New_Checkout")` doesn't find a flag with the key `new_checkout`.

## Read a flag

Always pass a default. It applies until the SDK has flags for the device, and whenever the flag doesn't exist, is archived, or the plan doesn't include feature flags.

```kotlin
import com.pushwoosh.Pushwoosh

val flags = Pushwoosh.getInstance().featureFlags

val checkout = flags.get("new_checkout")
if (checkout != null && checkout.isEnabled) {
    showNewCheckout(
        buttonColor = checkout.getString("button_color", "blue"),
        maxItems = checkout.getNumber("max_items", 3.0).toInt(),
    )
} else {
    showOldCheckout()
}
```

To check only whether the flag is on, use `isEnabled`:

```kotlin
val isNewCheckout = Pushwoosh.getInstance().featureFlags.isEnabled("new_checkout", false)
```

In Java, call `Pushwoosh.getInstance().getFeatureFlags()`.

## What a flag returns

`get(key)` returns a `FeatureFlag` object, or `null` when the SDK has no flags for the device yet or the flag isn't in the answer. An archived flag is not in the answer. `get` reads only the stored flags and never waits for the network. The object has these fields:

| Field | Type | Description |
| :---- | :---- | :---- |
| `key` | String | The flag key. |
| `isEnabled` | Boolean | Whether the flag is on for this device. |
| `ruleId` | String | The rule that decided the result, for example `r_1a2b3c4d`. `default` when no rule matched the device, and an empty string when the flag is killed. |

Read property values with the getter that matches the property type. Each getter returns your default when the property is missing or holds a value of another type. When the flag is off, the properties hold the default values set in the Control Panel.

| Method | Returns |
| :---- | :---- |
| `getString(property, defaultValue)` | The value of a **String** property. |
| `getNumber(property, defaultValue)` | The value of a **Number** property, as a `Double`. |
| `getBool(property, defaultValue)` | The value of a **Boolean** property. |
| `getJsonObject(property, defaultValue)` | The value of a **JSON** property that holds an object, as a `JSONObject`. |
| `getJsonArray(property, defaultValue)` | The value of a **JSON** property that holds an array, as a `JSONArray`. |

`isEnabled(key, defaultValue)` returns `defaultValue` when `get(key)` returns `null`.

## Keep flags up to date

The SDK stores the last answer on the device for up to 24 hours. The stored flags are valid only for the User ID they were requested for. The SDK refreshes them:

* When a session starts, that is, when the app comes to the foreground for the first time or after at least 30 minutes in the background.
* Every hour while the app is in the foreground.
* After the device registers for the first time.
* Right after the User ID changes with `setUserId`. The stored flags of the previous user are discarded, so a new user never sees them.

To react to new values, register a listener. It's called after a refresh that changed any flag value:

```kotlin
import com.pushwoosh.featureflags.FeatureFlagsUpdateListener

private val flagsListener = FeatureFlagsUpdateListener { render() }

override fun onStart() {
    super.onStart()
    Pushwoosh.getInstance().featureFlags.addUpdateListener(flagsListener)
}

override fun onStop() {
    Pushwoosh.getInstance().featureFlags.removeUpdateListener(flagsListener)
    super.onStop()
}
```

To request the flags yourself, for example after an action that may move the user into a different segment, call `refresh`. While the stored flags are valid, it sends at most one request per 5 minutes and answers from the stored flags within that window. The callback runs on the main thread:

```kotlin
Pushwoosh.getInstance().featureFlags.refresh { result ->
    if (!result.isSuccess) {
        val status = result.exception?.httpStatus
        // the stored flags, or your defaults, stay in effect
    }
    render()
}
```

On failure, `result.exception` is a `FeatureFlagsException`. Its `httpStatus` is `0` when the request got no HTTP response, and `200` when the server answered but the answer couldn't be used.

If Pushwoosh can't match the device's segments in time, it returns a partial answer. The SDK doesn't store it, and the next refresh brings the full answer.

When a flag is killed in the Control Panel, devices get it off with default property values at the next refresh. When the SDK can't reach the server, it keeps using the stored flags until they are 24 hours old, and your defaults after that.

## Log impressions

An impression tells Pushwoosh that a user actually reached the code a flag controls, so you can compare users who got the feature with those who didn't. Call `logImpression(key)` at the moment the user sees the feature or its old version, for both the enabled and the disabled branch, because users with the flag off are your comparison group:

```kotlin
val flags = Pushwoosh.getInstance().featureFlags
val isOn = flags.isEnabled("new_checkout", false)
flags.logImpression("new_checkout")
if (isOn) {
    showNewCheckout()
} else {
    showOldCheckout()
}
```

The SDK posts the `PW_FeatureFlagImpression` event with these attributes:

| Attribute | Type | Value |
| :---- | :---- | :---- |
| `flag_key` | string | The flag key. |
| `rule_id` | string | The rule that decided the result, as in `ruleId`. |
| `enabled` | boolean | Whether the flag was on. |

You don't need to create the event in advance, because Pushwoosh adds `PW_FeatureFlagImpression` with these attributes to **Audience** → **Events** when the first impression arrives. The SDK sends one impression per flag, rule, and state in a session, and sends nothing when there is no flag for the key.

## Method reference

`Pushwoosh.getInstance().featureFlags` has these methods:

| Method | Description |
| :---- | :---- |
| `get(key)` | Returns the `FeatureFlag` for the key, or `null`. Reads the stored flags only. |
| `isEnabled(key, defaultValue)` | Returns whether the flag is on, or `defaultValue` when there is no flag. |
| `refresh(callback)` | Requests the flags, throttled to once per 5 minutes while the stored flags are valid. The callback gets a `Result` with a `FeatureFlagsException` on failure, and can be `null`. |
| `addUpdateListener(listener)` | Calls `listener` after every refresh that changed a flag value. |
| `removeUpdateListener(listener)` | Removes a listener added with `addUpdateListener`. |
| `logImpression(key)` | Posts a `PW_FeatureFlagImpression` event for the flag's current rule and state. |