Skip to main content

Jetpack Compose

Integrate Toggly feature flags seamlessly with Jetpack Compose using composables and state.

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.

Installation​

Use version 1.8.0 or a newer compatible release from Maven Central for the Android telemetry and session APIs. Keep Core and the selected companion modules on the same version.

Add the Compose module to your build.gradle.kts:

dependencies {
implementation("io.toggly:toggly-android-core:1.8.0")
implementation("io.toggly:toggly-compose:1.8.0")
}

Setup​

Wrap your app content with TogglyProvider:

import io.toggly.compose.TogglyProvider

@Composable
fun App() {
TogglyProvider {
// Your app content
MainScreen()
}
}

Or provide a custom service:

TogglyProvider(service = myTogglyService) {
MainScreen()
}

rememberFeatureFlag​

The rememberFeatureFlag composable returns a reactive state that updates when the feature flag changes:

import io.toggly.compose.rememberFeatureFlag

@Composable
fun MyScreen() {
val isNewDashboard by rememberFeatureFlag("new-dashboard")

if (isNewDashboard) {
NewDashboardScreen()
} else {
LegacyDashboardScreen()
}
}

Default Values​

Provide a default value while loading:

val isEnabled by rememberFeatureFlag("my-feature", defaultValue = false)

Feature Composable​

The Feature composable conditionally renders content based on feature flag state. Set negate = true to render when the feature is off:

import io.toggly.compose.Feature

@Composable
fun WelcomeSection() {
// Show when enabled
Feature("welcome-banner") {
WelcomeBanner()
}

// On and off paths
Feature("new-checkout") {
NewCheckoutFlow()
}
Feature("new-checkout", negate = true) {
LegacyCheckoutFlow()
}
}

Loading State​

Handle the loading state explicitly:

Feature(
featureKey = "beta-feature",
loading = { CircularProgressIndicator() }
) {
BetaContent()
}
Feature(
featureKey = "beta-feature",
negate = true,
loading = { CircularProgressIndicator() }
) {
StableContent()
}

FeatureGate Composable​

The FeatureGate composable evaluates multiple feature flags together:

import io.toggly.compose.FeatureGate
import io.toggly.core.models.FeatureRequirement

@Composable
fun AdminSection() {
// ALL requirement - all features must be enabled
FeatureGate(
featureKeys = listOf("admin-access", "premium-tier"),
requirement = FeatureRequirement.ALL
) {
AdminPanel()
}

// ANY requirement - at least one feature must be enabled
FeatureGate(
featureKeys = listOf("feature-a", "feature-b", "feature-c"),
requirement = FeatureRequirement.ANY
) {
ExperimentalSection()
}
}

Negation​

Negate the gate evaluation:

FeatureGate(
featureKeys = listOf("maintenance-mode"),
requirement = FeatureRequirement.ALL,
negate = true // Show when maintenance-mode is OFF
) {
MainContent()
}

FeatureSwitch Composable​

The FeatureSwitch composable provides variant-style on/off slots:

import io.toggly.compose.FeatureSwitch

@Composable
fun PaymentSection() {
FeatureSwitch(
featureKey = "new-payment-flow",
enabled = { NewPaymentFlow() },
disabled = { LegacyPaymentFlow() }
)
}

Prefer two Feature(..., negate = true) calls when you want the same pattern as Next.js / .NET. Use FeatureSwitch when a single switch with default slots is clearer.

useToggly Hook​

Access the Toggly service directly for more control:

import io.toggly.compose.useToggly

@Composable
fun SettingsScreen() {
val toggly = useToggly()

LaunchedEffect(Unit) {
// Refresh feature flags
toggly.refresh()
}

// Access current identity
val identity = toggly.currentIdentity

// Get debug info
val debugInfo = toggly.getDebugInfo()

Column {
Text("Identity: ${identity ?: "Anonymous"}")
Text("Flags: ${debugInfo.featureFlagCount}")

Button(onClick = {
toggly.setIdentity("new-user-123")
}) {
Text("Change Identity")
}
}
}

Collecting Feature Flags​

Collect all feature flags as state:

import io.toggly.compose.useTogglyService

@Composable
fun DebugScreen() {
val service = useTogglyService()
val flags by service.featureFlags.collectAsState()

LazyColumn {
items(flags.entries.toList()) { (key, value) ->
Text("$key: $value")
}
}
}

Animation Support​

Animate feature flag transitions:

@Composable
fun AnimatedFeature() {
val isNewDesign by rememberFeatureFlag("new-design")

AnimatedContent(
targetState = isNewDesign,
transitionSpec = {
fadeIn() togetherWith fadeOut()
}
) { enabled ->
if (enabled) {
NewDesignScreen()
} else {
OldDesignScreen()
}
}
}

Entity context​

Feature("OrderBadge", context = order, contextKind = "Order") {
Badge()
}

Entity is per composable. setIdentity is user. Clients do not register schemas. See Entity & page context.

Best Practices​

1. Place Providers at Root Level​

@Composable
fun App() {
TogglyProvider {
MaterialTheme {
// Your app content
}
}
}

2. Use Meaningful Default Values​

// Default to false for new features
val isBetaFeature by rememberFeatureFlag("beta-feature", defaultValue = false)

// Default to true for core functionality
val isSearchEnabled by rememberFeatureFlag("search", defaultValue = true)

3. Handle Loading States​

@Composable
fun CriticalFeature() {
val loadStatus by Toggly.shared.loadStatus.collectAsState()

when (loadStatus) {
TogglyLoadStatus.IDLE,
TogglyLoadStatus.LOADING -> LoadingScreen()
TogglyLoadStatus.LOADED,
TogglyLoadStatus.ERROR -> {
// Use feature flags
val isEnabled by rememberFeatureFlag("critical-feature")
// ...
}
}
}

4. Test with Preview​

@Preview
@Composable
fun PreviewFeatureOn() {
// Create a mock provider for previews
val mockService = MockTogglyService(
flags = mapOf("my-feature" to true)
)

TogglyProvider(service = mockService) {
MyScreen()
}
}

Next Steps​