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
- Learn about Jetpack Compose for modern UI
- Explore Storage Options for persistence
- Check Advanced Usage for more patterns