Skip to main content

Jetpack Compose

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

Installation​

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

dependencies {
implementation("io.toggly:toggly-android-core:1.0.0")
implementation("io.toggly:toggly-compose:1.0.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​