Android SDK App Features
App Features let you change application settings, parameters, and behavior remotely, including values selected by A/B tests or user state.
The SDK provides:
- Downloading and caching — the remote configuration is cached, and stored-feature files are downloaded and cached on disk.
- Reactivity — RxJava observables report changes to resolved values or stored-feature descriptors.
- Flexible value types — value-features support booleans, numbers, strings, maps, JSON, and custom objects.
- Context-aware resolution — features are re-resolved when relevant user state changes, including subscription, in-app, authorization, referrer, and purchased-product state.
Before accessing App Features, configure and initialize the SDK. For more information, see the Configuration section.
Preparation
Configure and initialize Magify before reading App Features.
Create the SDK instance, subscribe to feature updates, and then initialize the SDK.
class App : Application() {
lateinit var magify: Magify
override fun onCreate() {
super.onCreate()
val config = MagifyConfig(
applicationName = "your_app_name",
defaultConfig = "default-config.json",
isSandbox = false
)
magify = Magify.createInstance(this, config)
magify.features.observeUpdate()
.subscribe {
// Re-read your value-features here.
}
magify.storedAppFeatures.observeUpdate()
.subscribe {
// Re-read the stored feature list here.
}
magify.initSdk {
// Local and default configurations are initialized here.
}
magify.initialSetup()
}
}
features.observeUpdate() emits when resolved value-feature values actually change, for example after a new configuration is applied or relevant user context changes.
storedAppFeatures.observeUpdate() emits when the resolved stored-feature descriptors change. A descriptor contains the feature name and the URL selected for the current user context. This event does not mean that the file content has finished downloading.
Basic Usage
The Android SDK exposes two different providers:
magify.features
// Typed value-features resolved in memory.
magify.storedAppFeatures
// Resolved file descriptors and file loading/caching.
Value-features arrive as part of the configuration and can be read through typed getters.
Stored App Features resolve to URLs, while their content is stored in separate files that are loaded through loadContent(...).
The two providers expose separate interfaces.
Value Features
Value-features are delivered as part of the remote configuration and can be accessed through typed getters.
Typed value getters return null when a key is missing or its value cannot be converted to the requested type. Use the Elvis operator (?:) to provide a default value.
Accessing the Features object
Use the features property on your Magify instance:
val features = magify.features
Reading typed values
The SDK provides typed accessors for reading feature values.
Boolean
val isEnabled: Boolean? =
magify.features.getBoolean("dark_mode_enabled")
String
val title: String? =
magify.features.getString("onboarding_title")
Long (integer)
val retryCount: Long? =
magify.features.getLong("max_retry_count")
Double
val discount: Double? =
magify.features.getDouble("discount_rate")
Dictionary (map)
Returns Map<String, Any?>?, which is suitable for loosely typed composite values.
val config: Map<String, Any?>? =
magify.features.getCustom("paywall_config")
val variant = config?.get("variant") as? String
Raw JSON string
Use getCustomJsonData(key) to obtain the raw JSON representation of a feature value.
val json: String? =
magify.features.getCustomJsonData("paywall_config")
Custom object
Use getCustomObject(key, Class<T>) to deserialize a JSON-serialized feature into any class.
Returns null when the key is absent or deserialization fails.
data class PaywallConfig(
val variant: String,
val price: Double
)
val config: PaywallConfig? =
magify.features.getCustomObject(
"paywall_config",
PaywallConfig::class.java
)
config?.let {
// use it.variant, it.price
}
getCustomObject() deserializes feature values using Gson.
If your class property names differ from the JSON keys, annotate them with @com.google.gson.annotations.SerializedName("json_key").
Reacting to feature updates
To react to changes, subscribe once and re-read the values whenever observeUpdate() emits.
class GameActivity : AppCompatActivity() {
private val disposables = CompositeDisposable()
private val magify: Magify =
(application as App).magify
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
disposables.add(
magify.features.observeUpdate()
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
applyHintBoosterState()
}
)
if (magify.isInitialized()) {
applyHintBoosterState()
}
}
private fun applyHintBoosterState() {
val isAvailable =
magify.features.getBoolean("is_hint_booster_available")
?: false
hintBooster.isVisible = isAvailable
}
override fun onDestroy() {
disposables.clear()
super.onDestroy()
}
}
observeUpdate() is an RxJava observable and supports multiple subscribers.
It is backed by a BehaviorSubject, so a new subscriber immediately receives the latest update event if one has already been emitted.
Always read the current values after initialization and re-read them on later emissions.
Stored App Features
Stored App Features are useful for larger payloads that should be delivered as separate files.
Accessing the Stored App Features object
Use the storedAppFeatures property on your Magify instance.
val storedFeatures = magify.storedAppFeatures
Reading available features
Retrieve the currently resolved Stored App Features.
val keys = storedFeatures.keys()
val values = storedFeatures.values()
val feature = storedFeatures.feature("startup_critical_features")
feature(name) returns null if the feature is not available for the current user.
You can also inspect all currently resolved descriptors.
val features: List<StoredAppFeature> =
magify.storedAppFeatures.toList()
val feature =
magify.storedAppFeatures.feature("startup_critical_features")
val exists =
magify.storedAppFeatures.contains("startup_critical_features")
You can also iterate through all resolved Stored App Features.
for (feature in magify.storedAppFeatures) {
// feature
}
Loading feature content
First resolve the feature descriptor, then load its content on an I/O scheduler.
The result contains the descriptor and a cached local File.
class Bootstrapper(
private val magify: Magify,
private val disposables: CompositeDisposable
) {
fun loadStartupConfig(onLoaded: (StartupConfig) -> Unit) {
disposables.add(
magify.storedAppFeatures
.loadContent("startup_critical_features")
.subscribeOn(Schedulers.io())
.map { content ->
Gson().fromJson(
content.file.readText(),
StartupConfig::class.java
)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe(onLoaded, ::handleError)
)
}
private fun handleError(error: Throwable) {
// The feature may be absent,
// its URL may be invalid,
// or neither remote nor cached content may be available.
}
}
You can also load content from an already resolved feature.
val feature =
magify.storedAppFeatures.feature("startup_critical_features")
?: return
magify.storedAppFeatures
.loadContent(feature)
.subscribeOn(Schedulers.io())
.subscribe { content ->
val cachedFile = content.file
}
Preloading and cache behavior
There is no preloading flag.
When network connectivity is available, the SDK attempts to preload all currently resolved Stored App Features in the background:
- when a session starts;
- after the first successfully synchronized remote configuration;
- when network connectivity becomes available.
Files are cached on disk, and requests use ETag / If-None-Match when the server supplies an ETag.
Concurrent requests for the same URL share one in-flight load.
If a request fails and a cached file exists, the cached file is returned.
A cached file's last-used time is refreshed whenever the SDK successfully returns it, including when it falls back to cached content.
Files that have not been returned for more than seven days are removed when the storage is created.
loadContent(...) still performs or joins a remote request before returning the file. It is therefore asynchronous and should run on an I/O scheduler.
A Stored App Feature update reports only that its resolved name/URL set has changed.
Subscribe to the returned Single to know when the feature content is ready.
When user context changes, Stored App Feature URLs are re-resolved and observeUpdate() emits if the descriptors change.
In the current implementation, that event by itself does not immediately start a preload for the newly selected URL.
Call loadContent(...) when the new content is needed.
Next step
For server-driven content items, see the Content section.