Skip to main content

Configuration

Complete configuration reference for the Toggly Android SDK.

TogglyConfig​

The TogglyConfig class defines all settings for the SDK.

import io.toggly.core.models.TogglyConfig

val config = TogglyConfig(
appKey = "your-app-key",
baseUri = "https://definitions.toggly.io",
environment = "Production",
featureDefaults = mapOf(
"feature-a" to true,
"feature-b" to false
),
refreshInterval = 180_000L,
useSignedDefinitions = false,
verifySignatures = false,
maxSignatureAgeSeconds = null,
connectTimeout = 10_000L,
requestTimeout = 30_000L,
enableLiveUpdates = true
)

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

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 always fetches evaluated-signed definitions:

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

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.

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​