Codync
A free, open-source 1:1 alternative to Grok Bot and Muse. Message Claude Code, Codex, Cursor and 40+ coding agents as persistent bots from your iPhone, Mac or Linux desktop. Group chats, threads, appr
Install the Kotlin SDK from Maven Central, read a flag in an Android app, and target users with context.
Developer toolsKotlinGitHub @zenmanageOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Official Zenmanage feature flags SDK for Android (Kotlin/Java)
Translation pending
Taken from the repository README
Loading the full README.
Read the full README ↗Add feature flags to your Kotlin and Android applications in minutes. Control feature rollouts, run A/B tests, and manage runtime configuration without redeploying.
The SDK is published to Maven Central.
dependencies {
implementation("com.zenmanage:zenmanage-kotlin:1.0.2")
}
dependencies {
implementation 'com.zenmanage:zenmanage-kotlin:1.0.2'
}
import com.zenmanage.kotlin.Zenmanage
import com.zenmanage.kotlin.config.ConfigBuilder
val config = ConfigBuilder.create()
.withEnvironmentToken("mob_your_mobile_key_here")
.build()
val zenmanage = Zenmanage(config)
val enabled = zenmanage.flags()
.single("new-dashboard", false)
.isEnabled()
srv_ and mob_cli_import com.zenmanage.kotlin.context.Attribute
import com.zenmanage.kotlin.context.Context
val context = Context.single("user", "user-123", "Alice")
.addAttribute(Attribute("country", listOf("US")))
val premiumEnabled = zenmanage.flags()
.withContext(context)
.single("premium-feature", false)
.isEnabled()
import com.zenmanage.kotlin.flags.DefaultsCollection
val defaults = DefaultsCollection.fromMap(
mapOf(
"new-ui" to true,
"api-version" to "v2",
"max-items" to 100
)
)
val version = zenmanage.flags()
.withDefaults(defaults)
.single("api-version")
.asString()
val config = zenmanage.flags()
.single("checkout-config", mapOf("maxItems" to 10, "theme" to "dark"))
.asJson()
asJson() decodes both JSON objects (as Map<String, Any?>) and JSON arrays (as List<Any?>).
import com.zenmanage.kotlin.context.Context
val context = Context.single("user", "user-42")
val inRollout = zenmanage.flags()
.withContext(context)
.single("new-checkout-flow", false)
.isEnabled()
A flag's type is one of boolean, string, number, or json. Each type has a matching accessor (asBool(), asString(), asNumber(), asJson()), plus isEnabled() for boolean flags specifically.
asJson() returns the decoded value: a JSON object decodes to Map<String, Any?>, a JSON array decodes to List<Any?>.
asString(), asNumber(), and asJson() each only recognize their own value wrapper ({"string": …}, {"number": …}, {"json": …}) and fall back to a safe zero value for every other type — they never attempt a lossy conversion between types:
| Called on →Flag type ↓ | asString() | asNumber() | asJson() |
|---|---|---|---|
boolean | "" | 0 | emptyMap() |
string | the string | 0 | emptyMap() |
number | "" | the number | emptyMap() |
json | "" | 0 | the decoded map/list |
isEnabled() returns false outright for any non-boolean flag — use it, not asBool(), when you specifically mean "is this boolean flag on". A json flag whose decoded value isn't itself an object or array (a bare JSON scalar such as 5 or "x", which the API allows but which real flags won't normally use) makes asJson() return emptyMap() too — the same safe-zero-value behavior as calling it on a non-json flag, rather than wrapping the scalar or throwing.
asBool() predates this coercion convention and behaves differently: rather than switching on the flag's type, it coerces whatever the underlying value happens to be (a non-boolean Number is truthy if non-zero, a non-boolean String is parsed with toBooleanStrictOrNull(), and a json value — which asBool() does not look at — falls back to false). This SDK's asBool() is intentionally left as-is here; only asJson() follows the "recognize your own type, safe zero value otherwise" rule strictly.
Default values passed to single(key, default) or DefaultsCollection are typed from the Kotlin value itself: a Map or List default becomes a json-typed flag (not a stringified fallback), so asJson() on a missing flag with a Map/List default returns that value unchanged.
ConfigBuilder.create()
.withEnvironmentToken("mob_your_mobile_key_here")
.withCacheBackend("memory")
.build()
ConfigBuilder.create()
.withEnvironmentToken("srv_your_server_key_here")
.withCacheBackend("filesystem")
.withCacheDirectory("/tmp/zenmanage-cache")
.build()
import android.content.Context
import com.zenmanage.kotlin.cache.SharedPreferencesCache
fun createZenmanage(appContext: Context): Zenmanage {
val prefs = appContext.getSharedPreferences("zenmanage", Context.MODE_PRIVATE)
val config = ConfigBuilder.create()
.withEnvironmentToken("mob_your_mobile_key_here")
.withCache(SharedPreferencesCache(prefs))
.build()
return Zenmanage(config)
}
The API is intentionally Java-friendly:
See examples/README.md for the full sample set:
Run tests and coverage report:
./gradlew clean test jacocoTestReport jacocoTestCoverageVerification
Coverage report output:
build/reports/jacoco/test/html/index.html