Skip to main content

Advanced Usage

Advanced patterns and features for the Toggly Android SDK.

Entity context​

setIdentity is user only. Entity evaluation uses local registerContext and a per-call entity. See Entity context and Entity & page context.

Signed definitions​

Enable ES256 envelope verification with JWKS for production builds:

val config = TogglyConfig(
appKey = "your-app-key",
environment = "Production",
useSignedDefinitions = true,
verifySignatures = true,
maxSignatureAgeSeconds = 300L
)
  • Fetch path is always evaluated-signed/{appKey}/{environment}
  • verifySignatures runs SignedDefsVerify against {baseUri}/.well-known/jwks
  • useSignedDefinitions adds ETag / If-None-Match conditional refresh

Full behavior (cold-start re-verify, fail-closed vs soft-keep): Configuration → Signed definitions.

Identity Management​

Set user identity for targeted feature flags:

// Set identity after user login
Toggly.shared.setIdentity("user-123")

// Clear identity on logout
Toggly.shared.setIdentity(null)

// Get current identity
val currentIdentity = Toggly.shared.identity

With Refresh​

Setting identity automatically refreshes feature flags:

lifecycleScope.launch {
Toggly.shared.setIdentity("user-123")
// Flags are now refreshed with user-specific targeting
}

User Properties​

Provide additional context for targeting:

Toggly.shared.setIdentity(
identity = "user-123",
properties = mapOf(
"plan" to "premium",
"country" to "US",
"beta_tester" to "true"
)
)

Events​

Listen to SDK events for monitoring and debugging:

viewModelScope.launch {
Toggly.shared.events.collect { event ->
when (event) {
is TogglyEvent.Initialized -> {
Log.d("Toggly", "Initialized with ${event.flagCount} flags")
}
is TogglyEvent.Refreshed -> {
Log.d("Toggly", "Refreshed: ${event.flagCount} flags")
}
is TogglyEvent.Error -> {
Log.e("Toggly", "Error: ${event.error.message}")
// Handle error (e.g., show toast, retry)
}
is TogglyEvent.FeatureChanged -> {
Log.d("Toggly", "${event.featureKey}: ${event.oldValue} -> ${event.newValue}")
}
is TogglyEvent.IdentityChanged -> {
Log.d("Toggly", "Identity changed to: ${event.identity}")
}
is TogglyEvent.NetworkStatusChanged -> {
Log.d("Toggly", "Network: ${event.isConnected}")
}
}
}
}

Event Types​

EventDescription
InitializedSDK initialized successfully
RefreshedFeature flags refreshed from server
ErrorAn error occurred
FeatureChangedA feature flag value changed
IdentityChangedUser identity was set or cleared
NetworkStatusChangedNetwork connectivity changed

Manual Refresh​

Refresh feature flags on demand:

lifecycleScope.launch {
try {
Toggly.shared.refresh()
Log.d("Toggly", "Refresh successful")
} catch (e: Exception) {
Log.e("Toggly", "Refresh failed", e)
}
}

Pull-to-Refresh Pattern​

@Composable
fun FeatureFlagsScreen() {
var isRefreshing by remember { mutableStateOf(false) }

SwipeRefresh(
state = rememberSwipeRefreshState(isRefreshing),
onRefresh = {
isRefreshing = true
scope.launch {
Toggly.shared.refresh()
isRefreshing = false
}
}
) {
// Content
}
}

Feature Gates​

Evaluate multiple features together:

// All features must be enabled
val hasFullAccess = Toggly.shared.evaluateFeatureGate(
featureKeys = listOf("premium", "beta-access", "admin"),
requirement = FeatureRequirement.ALL
)

// Any feature must be enabled
val hasPartialAccess = Toggly.shared.evaluateFeatureGate(
featureKeys = listOf("premium", "trial"),
requirement = FeatureRequirement.ANY
)

In Compose​

@Composable
fun PremiumContent() {
FeatureGate(
featureKeys = listOf("premium", "beta-access"),
requirement = FeatureRequirement.ALL,
enabled = { PremiumFeatures() },
disabled = { UpgradePrompt() }
)
}

Debug Information​

Get diagnostic information about the SDK:

val debugInfo = Toggly.shared.getDebugInfo()

Log.d("Toggly", """
App Key: ${debugInfo.appKey}
Environment: ${debugInfo.environment}
Identity: ${debugInfo.identity}
Is Initialized: ${debugInfo.isInitialized}
Feature Count: ${debugInfo.featureFlagCount}
Last Refresh: ${debugInfo.lastRefreshTime}
Last Error: ${debugInfo.lastError}
""".trimIndent())

Debug UI Component​

@Composable
fun TogglyDebugPanel() {
val debugInfo by Toggly.shared.debugInfoFlow.collectAsState(
initial = Toggly.shared.getDebugInfo()
)

Column(modifier = Modifier.padding(16.dp)) {
Text("Toggly Debug Info", style = MaterialTheme.typography.h6)
Spacer(modifier = Modifier.height(8.dp))
Text("App Key: ${debugInfo.appKey}")
Text("Environment: ${debugInfo.environment}")
Text("Identity: ${debugInfo.identity ?: "Anonymous"}")
Text("Flags: ${debugInfo.featureFlagCount}")
Text("Last Refresh: ${debugInfo.lastRefreshTime}")

Button(onClick = {
scope.launch { Toggly.shared.refresh() }
}) {
Text("Force Refresh")
}
}
}

Feature Flag Overrides​

Override feature flags for testing or development:

// Set a local override
Toggly.shared.setOverride("new-feature", true)

// Remove an override
Toggly.shared.removeOverride("new-feature")

// Clear all overrides
Toggly.shared.clearOverrides()

// Check if override is set
val isOverridden = Toggly.shared.isOverridden("new-feature")

Debug Menu Integration​

@Composable
fun FeatureFlagDebugMenu() {
val flags by Toggly.shared.allFlagsFlow.collectAsState(initial = emptyMap())

LazyColumn {
items(flags.toList()) { (key, value) ->
Row(
modifier = Modifier.fillMaxWidth().padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(key)
Switch(
checked = value,
onCheckedChange = { newValue ->
Toggly.shared.setOverride(key, newValue)
}
)
}
}
}
}

Offline Support​

The SDK automatically handles offline scenarios:

// Check if using cached data
val isCached = Toggly.shared.isUsingCachedData

// Force use of cached data
Toggly.shared.enableOfflineMode(true)

// Return to online mode
Toggly.shared.enableOfflineMode(false)

Network Monitoring​

viewModelScope.launch {
Toggly.shared.networkStatusFlow.collect { isConnected ->
if (isConnected) {
// Network restored, refresh flags
Toggly.shared.refresh()
}
}
}

Lifecycle Integration​

Automatically refresh when app returns to foreground:

class MainActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
lifecycleScope.launch {
if (Toggly.shared.shouldRefresh()) {
Toggly.shared.refresh()
}
}
}
}

With ProcessLifecycleOwner​

class MyApplication : Application(), LifecycleEventObserver {
override fun onCreate() {
super.onCreate()
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
}

override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
when (event) {
Lifecycle.Event.ON_START -> {
CoroutineScope(Dispatchers.IO).launch {
Toggly.shared.refresh()
}
}
else -> {}
}
}
}

Testing​

Mock Client for Tests​

class FakeTogglyClient : TogglyClient {
private val flags = mutableMapOf<String, Boolean>()

fun setFlag(key: String, value: Boolean) {
flags[key] = value
}

override suspend fun isFeatureOn(featureKey: String): Boolean {
return flags[featureKey] ?: false
}

// Implement other methods...
}

In Unit Tests​

@Test
fun `test feature flag behavior`() = runTest {
val fakeClient = FakeTogglyClient()
fakeClient.setFlag("new-feature", true)

val viewModel = MyViewModel(fakeClient)

assertTrue(viewModel.isNewFeatureEnabled())
}

In Instrumentation Tests​

@get:Rule
val composeTestRule = createComposeRule()

@Test
fun testFeatureFlag() {
// Configure with test flags
Toggly.configure(
config = TogglyConfig(
appKey = "",
defaultFlags = mapOf("test-feature" to true)
),
storage = MemoryStorage()
)

composeTestRule.setContent {
MyComposable()
}

// Assert UI shows enabled state
composeTestRule.onNodeWithText("New Feature").assertIsDisplayed()
}

Custom Evaluators​

Register custom logic for feature evaluation:

Toggly.shared.registerEvaluator("custom-rule") { context ->
// Custom evaluation logic
val userId = context.identity ?: return@registerEvaluator false
userId.hashCode() % 2 == 0 // Enable for users with even hash
}

Metrics and Analytics​

Track feature flag usage:

// Enable analytics
Toggly.shared.enableAnalytics(true)

// Get usage stats
val stats = Toggly.shared.getUsageStats()
stats.forEach { (featureKey, count) ->
Log.d("Toggly", "$featureKey evaluated $count times")
}

Integration with Firebase Analytics​

viewModelScope.launch {
Toggly.shared.events.collect { event ->
when (event) {
is TogglyEvent.FeatureChanged -> {
firebaseAnalytics.logEvent("feature_flag_changed") {
param("feature_key", event.featureKey)
param("old_value", event.oldValue.toString())
param("new_value", event.newValue.toString())
}
}
else -> {}
}
}
}

Best Practices​

1. Initialize Early​

class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize in Application.onCreate for fastest startup
Toggly.configure(config, storage)
CoroutineScope(Dispatchers.IO).launch {
Toggly.shared.init()
}
}
}

2. Use Default Values​

val config = TogglyConfig(
appKey = "your-app-key",
defaultFlags = mapOf(
"critical-feature" to true // Safe default
)
)

3. Handle Errors Gracefully​

val isEnabled = try {
Toggly.shared.isFeatureOn("my-feature")
} catch (e: Exception) {
Log.e("Toggly", "Error checking feature", e)
false // Safe fallback
}

4. Clean Up Resources​

override fun onDestroy() {
super.onDestroy()
Toggly.shared.close()
}

Next Steps​