Codync
免費、開源的 1:1 替代方案,取代 Grok Bot 與 Muse。從 iPhone、Mac 或 Linux 桌面將 Claude Code、Codex、Cursor 與 40+ 程式碼代理人作為持續機器人傳訊。群組聊天、主題串…
AI 翻譯
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Official Zenmanage feature flags SDK for Android (Kotlin/Java)
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 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