Skip to main content

Configuration

Public configuration reference for the Toggly Android SDK.

Telemetry and session ownership​

Use the existing Core service for frontend telemetry and session token rotation. Telemetry is on by default with an app key; configure enableTelemetry = false to opt out. metricsBaseUrl and telemetryFlushIntervalMs are independent of definition refresh settings. UI evaluation records checks; record usage and views explicitly from the real interaction or navigation callback. Forward background lifecycle to the owning service and dispose directly owned services at teardown. Storage and UI companions reuse the owner; they do not create additional reporters.

TogglyConfig​

The TogglyConfig class configures the public SDK options below.

import io.toggly.core.models.TogglyConfig

val config = TogglyConfig(
appKey = "your-app-key",
baseUri = "https://definitions.toggly.io",
environment = "Production",
identity = null,
featureDefaults = mapOf(
"feature-a" to true,
"feature-b" to false
),
showFeatureDuringEvaluation = false,
refreshInterval = 180_000L,
useSignedDefinitions = false,
verifySignatures = false,
maxSignatureAgeSeconds = null,
connectTimeout = 10_000L,
requestTimeout = 30_000L,
storage = null,
enableLiveUpdates = true,
groups = emptyList(),
claims = emptyMap(),
enableTelemetry = true,
metricsBaseUrl = "https://metrics.toggly.io",
telemetryFlushIntervalMs = 45_000L,
onTelemetryDiagnostic = { code -> println("Toggly telemetry: $code") },
instanceId = null, // Replace with a backend-minted token when available.
enableVariants = false // Fetch evaluated-variants-signed and enable getVariant/getVariantValue.
)

Initial groups and claims​

Supply known context before initialization so the first evaluated request has the intended user, memberships and rule attributes:

val service = TogglyService(TogglyConfig(
appKey = "your-app-key",
identity = "user-123", // Stable user identifier.
groups = listOf("beta"), // Memberships used by targeting rules.
claims = mapOf("plan" to "pro"), // String rule attributes.
))
service.init() // Run in a coroutine.

Empty groups/claims add no memberships/attributes. Omitted identity retains the stored/generated device ID; explicit empty identity stays empty. Context is copied at construction. Blank groups and empty claim names/values are omitted, with at most 20 claims sent in deterministic order. Use separate group entries, not commas inside group names. Per-check entity context is separate from this user targeting.

Configuration Options​

OptionTypeDefaultDescription
appKeyString?nullYour Toggly application key
baseUriString"https://definitions.toggly.io"API base URL
environmentString"Production"Environment name
featureDefaultsMap<String, Boolean>emptyMap()Default feature flag values
refreshIntervalLong180_000LAuto-refresh interval in milliseconds (0 disables)
useSignedDefinitionsBooleanfalseSend If-None-Match with the cached ETag on refresh
verifySignaturesBooleanfalseVerify ES256 signed envelopes via JWKS (SignedDefsVerify)
maxSignatureAgeSecondsLong?nullReject envelopes older than this many seconds (null / <= 0 disables)
connectTimeoutLong10_000LConnection timeout in milliseconds
requestTimeoutLong30_000LRequest timeout in milliseconds
enableLiveUpdatesBooleantrueEnable WebSocket live updates
identityString?nullInitial user identity; null uses the stored/generated device identity
instanceIdString?nullBackend-minted token; nonblank token takes precedence over client targeting
groupsList<String>emptyList()Initial targeting memberships
claimsMap<String, String>emptyMap()Initial targeting attributes
showFeatureDuringEvaluationBooleanfalseShow feature content during initial evaluation
storageTogglyStorage?nullCustom storage implementation
enableTelemetryBooleantrueCompact reporter enabled with a nonblank app key; false opts out
enableVariantsBooleanfalseFetch signed variant assignments from evaluated-variants-signed (instead of evaluated-signed) and enable getVariant / getVariantValue
metricsBaseUrlString"https://metrics.toggly.io"Independent telemetry endpoint base URL
telemetryFlushIntervalMsLong45_000LTelemetry base interval in ms; outside 30,000–60,000 falls back to 45,000
onTelemetryDiagnostic((String) -> Unit)?nullOptional callback receiving bounded status-code strings, without keys, identities or event payloads

Basic Configuration​

Minimal Setup​

val config = TogglyConfig(
appKey = "your-app-key"
)

Production Setup​

val config = TogglyConfig(
appKey = "your-app-key",
environment = "Production",
refreshInterval = 300_000L, // 5 minutes
useSignedDefinitions = true,
verifySignatures = true
)

Development Setup​

val config = TogglyConfig(
appKey = "your-app-key",
environment = "Development",
refreshInterval = 10_000L // Faster refresh for testing
)

Default Flags​

Provide fallback values for feature flags when offline or before initialization:

val config = TogglyConfig(
appKey = "your-app-key",
featureDefaults = mapOf(
"new-dashboard" to false,
"beta-feature" to false,
"premium-access" to true
)
)

Default flags are used when:

  • The SDK hasn't been initialized yet
  • Network requests fail
  • A feature key doesn't exist on the server

Signed definitions​

The Android client fetches evaluated-signed definitions by default:

GET {baseUri}/evaluated-signed/{appKey}/{environment}

Setting enableVariants = true switches this to evaluated-variants-signed instead — see Feature variants below.

Signature verification is controlled separately from conditional requests.

val config = TogglyConfig(
appKey = "your-app-key",
environment = "Production",
useSignedDefinitions = true, // ETag / If-None-Match on refresh
verifySignatures = true, // ES256 verify via SignedDefsVerify
maxSignatureAgeSeconds = 300L // optional freshness window
)
OptionBehavior
useSignedDefinitionsWhen true and an ETag is cached, refresh sends If-None-Match (HTTP 304 reuses cache). Does not switch the fetch URL.
verifySignaturesWhen true, parse the signed envelope, fetch JWKS, and verify with SignedDefsVerify before applying flags. Default false parses the body without crypto checks.
maxSignatureAgeSecondsWhen set to a positive value, reject envelopes whose timestamp is older than this many seconds (or too far in the future). null or <= 0 skips freshness.

JWKS and verification details​

When verifySignatures is enabled:

  1. Parse the response envelope (defs / data, signature, timestamp, kid)
  2. Optionally enforce maxSignatureAgeSeconds
  3. Fetch JWKS from {baseUri}/.well-known/jwks and persist it in storage
  4. Verify ES256 (ECDSA P-256) over a double SHA-256 digest of <raw defs json>|<timestamp>
  5. Apply only the verified raw defs bytes

On failure (invalid envelope, bad signature, unknown kid, stale timestamp), the SDK falls back to cache or featureDefaults and emits an error event — it does not apply the unverified payload.

Cold-start re-verification​

With verifySignatures = true, loading a persisted cache:

  • Re-verifies using stored signature metadata (signature, timestamp, kid) and the last JWKS
  • Clears the cache (fail closed) when signature metadata is missing, the signature is invalid, the kid is unknown, or freshness fails
  • Soft-keeps last-known-good flags if JWKS is temporarily unavailable (offline)

Android does not expose an allowedKeyIds allow-list; trust is the JWKS set from /.well-known/jwks.

For the shared evaluated-signed contract, see Evaluated-signed.

Feature variants​

val config = TogglyConfig(
appKey = "your-app-key",
enableVariants = true
)

With enableVariants = true, definitions are fetched from evaluated-variants-signed; each feature carries an optional assigned variant name and configuration value alongside its boolean state. Read them with getVariant(featureKey) (returns VariantResult(name, configurationValue) or null) and getVariantValue(featureKey) (returns just the configuration value, or null). Both are suspend functions on TogglyService and the global Toggly facade.

A null result means the feature is disabled, has no variant assignment, or enableVariants is false — isFeatureOn and the rest of the boolean API are unaffected by this setting. Variant assignments are verified and cached the same way as boolean definitions (see JWKS and cold-start re-verification above).

Local-Only Mode​

Run without connecting to Toggly servers:

val config = TogglyConfig(
appKey = "", // Empty app key
featureDefaults = mapOf(
"feature-a" to true,
"feature-b" to false
)
)

Toggly.configure(
config = config,
storage = MemoryStorage()
)

Auto-Refresh​

Enable automatic background refresh of feature flags by setting a non-zero interval:

val config = TogglyConfig(
appKey = "your-app-key",
refreshInterval = 60_000L // 1 minute; 0 disables
)

Refresh Intervals​

Use CaseRecommended Interval
Development10-30 seconds
Staging1-2 minutes
Production5-10 minutes
High-traffic apps10-15 minutes

Network Configuration​

Timeouts​

val config = TogglyConfig(
appKey = "your-app-key",
connectTimeout = 5_000L, // 5 seconds
requestTimeout = 15_000L // 15 seconds
)

Custom Base URL​

For self-hosted or enterprise deployments:

val config = TogglyConfig(
appKey = "your-app-key",
baseUri = "https://your-custom-domain.com"
)

Environment-Based Configuration​

Using BuildConfig​

val config = TogglyConfig(
appKey = BuildConfig.TOGGLY_APP_KEY,
environment = if (BuildConfig.DEBUG) "Development" else "Production",
refreshInterval = if (BuildConfig.DEBUG) 10_000L else 300_000L,
verifySignatures = !BuildConfig.DEBUG
)

Using Gradle Build Variants​

// build.gradle.kts
android {
buildTypes {
debug {
buildConfigField("String", "TOGGLY_APP_KEY", "\"debug-key\"")
buildConfigField("String", "TOGGLY_ENV", "\"Development\"")
}
release {
buildConfigField("String", "TOGGLY_APP_KEY", "\"prod-key\"")
buildConfigField("String", "TOGGLY_ENV", "\"Production\"")
}
}
}
val config = TogglyConfig(
appKey = BuildConfig.TOGGLY_APP_KEY,
environment = BuildConfig.TOGGLY_ENV
)

Configuration with Dependency Injection​

Hilt​

@Module
@InstallIn(SingletonComponent::class)
object TogglyModule {
@Provides
@Singleton
fun provideTogglyConfig(): TogglyConfig = TogglyConfig(
appKey = BuildConfig.TOGGLY_APP_KEY,
environment = BuildConfig.TOGGLY_ENV
)

@Provides
@Singleton
fun provideTogglyStorage(
@ApplicationContext context: Context
): TogglyStorage = SharedPreferencesStorage(context)
}

Koin​

val togglyModule = module {
single {
TogglyConfig(
appKey = BuildConfig.TOGGLY_APP_KEY,
environment = BuildConfig.TOGGLY_ENV
)
}

single<TogglyStorage> {
SharedPreferencesStorage(androidContext())
}
}

Initialization​

Application Class​

class MyApplication : Application() {
override fun onCreate() {
super.onCreate()

val config = TogglyConfig(
appKey = "your-app-key",
environment = "Production"
)

Toggly.configure(
config = config,
storage = SharedPreferencesStorage(this)
)

// Initialize in background
CoroutineScope(Dispatchers.IO).launch {
try {
Toggly.shared.init()
} catch (e: Exception) {
Log.e("Toggly", "Initialization failed", e)
}
}
}
}

Lazy Initialization​

class MyApplication : Application() {
override fun onCreate() {
super.onCreate()

Toggly.configure(
config = TogglyConfig(appKey = "your-app-key"),
storage = SharedPreferencesStorage(this)
)
// Don't call init() here - wait until needed
}
}

// Later, when needed
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

lifecycleScope.launch {
if (!Toggly.shared.isInitialized) {
Toggly.shared.init()
}
// Now use feature flags
}
}
}

Reconfiguration​

Change configuration at runtime (e.g., after user login):

// Initial anonymous configuration
Toggly.configure(
config = TogglyConfig(appKey = "your-app-key"),
storage = SharedPreferencesStorage(context)
)
Toggly.shared.init()

// After user login, reconfigure with identity
suspend fun onUserLogin(userId: String) {
Toggly.shared.setIdentity(userId)
Toggly.shared.refresh()
}

Best Practices​

1. Validate Configuration​

fun validateConfig(config: TogglyConfig): Boolean {
return config.appKey.isNotBlank() &&
config.refreshInterval >= 10_000L &&
config.connectTimeout > 0L
}

2. Use Appropriate Timeouts​

// Mobile networks can be slow
val config = TogglyConfig(
appKey = "your-app-key",
connectTimeout = 15_000L, // 15 seconds
requestTimeout = 30_000L // 30 seconds
)

3. Handle Configuration Errors​

try {
Toggly.configure(config = config, storage = storage)
} catch (e: IllegalArgumentException) {
Log.e("Toggly", "Invalid configuration", e)
// Use fallback or default configuration
}

Next Steps​