Skip to main content

Android Views

Integrate Toggly feature flags with traditional Android Views using LiveData, ViewModel, and View extensions.

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 Views module to your build.gradle.kts:

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

FeatureFlagViewModel​

The FeatureFlagViewModel provides LiveData-based access to feature flags:

import io.toggly.views.FeatureFlagViewModel

class MyActivity : AppCompatActivity() {
private val viewModel: FeatureFlagViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

// Initialize if not already done
viewModel.initialize()

// Observe a single feature flag
viewModel.featureFlagLiveData("new-dashboard").observe(this) { isEnabled ->
newDashboardView.visibility = if (isEnabled) View.VISIBLE else View.GONE
}

// Observe initialization state
viewModel.isInitialized.observe(this) { initialized ->
if (initialized) {
// Feature flags are ready
loadContent()
}
}
}
}

Feature Gates​

Evaluate multiple feature flags together:

// All features must be enabled
viewModel.featureGateLiveData(
featureKeys = listOf("premium-tier", "beta-access"),
requirement = FeatureRequirement.ALL
).observe(this) { hasAccess ->
premiumSection.visibility = if (hasAccess) View.VISIBLE else View.GONE
}

// Any feature must be enabled
viewModel.featureGateLiveData(
featureKeys = listOf("experiment-a", "experiment-b"),
requirement = FeatureRequirement.ANY
).observe(this) { inExperiment ->
experimentBanner.visibility = if (inExperiment) View.VISIBLE else View.GONE
}

Negation​

Invert the gate evaluation:

viewModel.featureGateLiveData(
featureKeys = listOf("maintenance-mode"),
requirement = FeatureRequirement.ALL,
negate = true // Show when maintenance-mode is OFF
).observe(this) { showMainContent ->
mainContent.visibility = if (showMainContent) View.VISIBLE else View.GONE
}

Identity Management​

// Set user identity for targeting
viewModel.setIdentity("user-123")

// Clear identity
viewModel.setIdentity(null)

// Get current identity
val identity = viewModel.currentIdentity

Actions​

// Refresh feature flags from server
viewModel.refresh()

// Clear cached feature flags
viewModel.clearCache()

// Get debug information
val debugInfo = viewModel.getDebugInfo()

View Extensions​

Bind Views directly to feature flags using extension functions:

bindToFeatureFlag​

Bind a View's visibility to a feature flag:

import io.toggly.views.bindToFeatureFlag

// Show when enabled, hide when disabled
newFeatureButton.bindToFeatureFlag(
featureKey = "new-feature",
lifecycleOwner = this
)

// Custom visibility states
betaSection.bindToFeatureFlag(
featureKey = "beta-access",
lifecycleOwner = this,
visibleWhenEnabled = View.VISIBLE,
visibleWhenDisabled = View.INVISIBLE // or View.GONE
)

bindEnabledToFeatureFlag​

Bind a View's enabled state to a feature flag:

import io.toggly.views.bindEnabledToFeatureFlag

// Enable/disable based on feature flag
submitButton.bindEnabledToFeatureFlag(
featureKey = "submit-enabled",
lifecycleOwner = this
)

bindAlphaToFeatureFlag​

Bind a View's alpha (opacity) to a feature flag:

import io.toggly.views.bindAlphaToFeatureFlag

// Fade out when disabled
experimentalSection.bindAlphaToFeatureFlag(
featureKey = "experimental",
lifecycleOwner = this,
enabledAlpha = 1.0f,
disabledAlpha = 0.3f
)

bindToFeatureGate​

Bind visibility to multiple feature flags:

import io.toggly.views.bindToFeatureGate

adminPanel.bindToFeatureGate(
featureKeys = listOf("admin-access", "premium-tier"),
lifecycleOwner = this,
requirement = FeatureRequirement.ALL
)

showWhenFeature / bind with negate​

Show a view when a feature is on, or use negate = true for the off path:

import io.toggly.views.showWhenFeature
import io.toggly.views.bindToFeatureFlag

// Show only when enabled
newFeatureView.showWhenFeature("new-feature", this)

// Show only when disabled
legacyFeatureView.bindToFeatureFlag(
featureKey = "new-feature",
lifecycleOwner = this,
negate = true
)

isFeatureOff remains available on the ViewModel as a helper for imperative checks.

toggleViews​

Toggle between two views based on a feature flag:

import io.toggly.views.toggleViews

toggleViews(
featureKey = "new-checkout",
lifecycleOwner = this,
enabledView = newCheckoutView,
disabledView = legacyCheckoutView
)

Fragment Usage​

Use the same patterns in Fragments:

class MyFragment : Fragment() {
private val viewModel: FeatureFlagViewModel by activityViewModels()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

// Observe feature flags
viewModel.featureFlagLiveData("my-feature").observe(viewLifecycleOwner) { isEnabled ->
featureSection.visibility = if (isEnabled) View.VISIBLE else View.GONE
}

// Bind views
binding.newFeature.bindToFeatureFlag(
featureKey = "new-feature",
lifecycleOwner = viewLifecycleOwner
)
}
}

Flow-Based Usage​

Access Flow-based APIs for more flexibility:

class MyFragment : Fragment() {
private val viewModel: FeatureFlagViewModel by activityViewModels()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

// Collect feature flag flow
viewLifecycleOwner.lifecycleScope.launch {
viewModel.featureFlagFlow("my-feature").collect { isEnabled ->
updateUI(isEnabled)
}
}

// Collect feature gate flow
viewLifecycleOwner.lifecycleScope.launch {
viewModel.featureGateFlow(
listOf("feature-a", "feature-b"),
FeatureRequirement.ALL
).collect { hasAccess ->
updateAccessUI(hasAccess)
}
}
}
}

Suspend Functions​

Use suspend functions for one-time checks:

viewLifecycleOwner.lifecycleScope.launch {
val isEnabled = viewModel.isFeatureOn("my-feature")
val isDisabled = viewModel.isFeatureOff("my-feature")

// Make decisions based on current values
if (isEnabled) {
showNewFeature()
}
}

RecyclerView Adapter​

Use feature flags in RecyclerView adapters:

class MyAdapter(
private val items: List<Item>,
private val togglyService: TogglyService
) : RecyclerView.Adapter<MyViewHolder>() {

override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val item = items[position]

// Bind feature flags
holder.itemView.showWhenFeature(
"show-item-details",
holder.itemView.findViewTreeLifecycleOwner()!!
)
}
}

Entity context​

Pass the entity on each view check. Do not use setIdentity for page entities. See Entity & page context. Clients do not register schemas.

val on = Toggly.shared.isFeatureOn("OrderBadge", order, "Order")

Best Practices​

1. Use ViewModel for Shared State​

// Share ViewModel across Activity and Fragments
class MainActivity : AppCompatActivity() {
private val viewModel: FeatureFlagViewModel by viewModels()
}

class MyFragment : Fragment() {
private val viewModel: FeatureFlagViewModel by activityViewModels()
}

2. Cancel Jobs on Lifecycle End​

View extension functions return Job for cancellation:

private val jobs = mutableListOf<Job>()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

jobs += myView.bindToFeatureFlag("feature", viewLifecycleOwner)
}

override fun onDestroyView() {
super.onDestroyView()
jobs.forEach { it.cancel() }
jobs.clear()
}

3. Handle Configuration Changes​

The ViewModel survives configuration changes:

viewModel.featureFlagsLiveData.observe(this) { flags ->
// This observer is re-attached after rotation
updateAllFeatures(flags)
}

Next Steps​