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
| Option | Type | Default | Description |
|---|---|---|---|
appKey | String? | null | Your Toggly application key |
baseUri | String | "https://definitions.toggly.io" | API base URL |
environment | String | "Production" | Environment name |
featureDefaults | Map<String, Boolean> | emptyMap() | Default feature flag values |
refreshInterval | Long | 180_000L | Auto-refresh interval in milliseconds (0 disables) |
useSignedDefinitions | Boolean | false | Send If-None-Match with the cached ETag on refresh |
verifySignatures | Boolean | false | Verify ES256 signed envelopes via JWKS (SignedDefsVerify) |
maxSignatureAgeSeconds | Long? | null | Reject envelopes older than this many seconds (null / <= 0 disables) |
connectTimeout | Long | 10_000L | Connection timeout in milliseconds |
requestTimeout | Long | 30_000L | Request timeout in milliseconds |
enableLiveUpdates | Boolean | true | Enable WebSocket live updates |
identity | String? | null | Initial user identity; null uses the stored/generated device identity |
instanceId | String? | null | Backend-minted token; nonblank token takes precedence over client targeting |
groups | List<String> | emptyList() | Initial targeting memberships |
claims | Map<String, String> | emptyMap() | Initial targeting attributes |
showFeatureDuringEvaluation | Boolean | false | Show feature content during initial evaluation |
storage | TogglyStorage? | null | Custom storage implementation |
enableTelemetry | Boolean | true | Compact reporter enabled with a nonblank app key; false opts out |
enableVariants | Boolean | false | Fetch signed variant assignments from evaluated-variants-signed (instead of evaluated-signed) and enable getVariant / getVariantValue |
metricsBaseUrl | String | "https://metrics.toggly.io" | Independent telemetry endpoint base URL |
telemetryFlushIntervalMs | Long | 45_000L | Telemetry base interval in ms; outside 30,000–60,000 falls back to 45,000 |
onTelemetryDiagnostic | ((String) -> Unit)? | null | Optional 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.
Enable verification (recommended for production)
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
)
| Option | Behavior |
|---|---|
useSignedDefinitions | When true and an ETag is cached, refresh sends If-None-Match (HTTP 304 reuses cache). Does not switch the fetch URL. |
verifySignatures | When true, parse the signed envelope, fetch JWKS, and verify with SignedDefsVerify before applying flags. Default false parses the body without crypto checks. |
maxSignatureAgeSeconds | When 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:
- Parse the response envelope (
defs/data,signature,timestamp,kid) - Optionally enforce
maxSignatureAgeSeconds - Fetch JWKS from
{baseUri}/.well-known/jwksand persist it in storage - Verify ES256 (ECDSA P-256) over a double SHA-256 digest of
<raw defs json>|<timestamp> - Apply only the verified raw
defsbytes
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
kidis 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 Case | Recommended Interval |
|---|---|
| Development | 10-30 seconds |
| Staging | 1-2 minutes |
| Production | 5-10 minutes |
| High-traffic apps | 10-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
- Explore Advanced Usage for identity and events
- Check Storage Options for persistence strategies
- See the API Reference for all methods