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} verifySignaturesrunsSignedDefsVerifyagainst{baseUri}/.well-known/jwksuseSignedDefinitionsadds ETag /If-None-Matchconditional 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
| Event | Description |
|---|---|
Initialized | SDK initialized successfully |
Refreshed | Feature flags refreshed from server |
Error | An error occurred |
FeatureChanged | A feature flag value changed |
IdentityChanged | User identity was set or cleared |
NetworkStatusChanged | Network 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
- Check the API Reference for complete method documentation
- Explore Storage Options for persistence strategies
- Review Configuration options