3.3 KiB
core/config-toggles
Feature toggles (and the related excluded-blockchains toggles). Toggles gate
features by app version; the JSON config is the source of truth and the
FeatureToggles enum is generated from it at build time.
How it works
- Config:
src/main/assets/configs/feature_toggles_config.json— a JSON array of{ "name": <STRING>, "version": <STRING> }(ConfigToggle). - The convention plugin generates the
FeatureTogglesenum (one entry pername) at build time. Reference it asFeatureToggles.<NAME>. - Entry point:
FeatureTogglesManager.isFeatureEnabled(FeatureToggles.X).ProdFeatureTogglesManager(release): a toggle is enabled when the app version>=itsversion.DevFeatureTogglesManager(tester builds,BuildConfig.TESTER_MENU_ENABLED): runtime-toggleable via the Tester Menu.
versionsemantics:"undefined"(DISABLED_FEATURE_TOGGLE_VERSION) → OFF in prod; can only be flipped ON via the Tester Menu / dev builds. Use this while a feature is in development."X.Y"(e.g.5.40) → ON in prod from that app version onward (currentVersion >= localVersion, seeVersionAvailabilityContract).
Naming convention (ENFORCED by a test)
- A toggle
nameMUST match^(AND|TWI)_\d+(?:_[A-Z0-9]+)+$— start with the Jira ticket id (AND_<id>for Android tickets,TWI_<id>for idea tickets), then anUPPER_SNAKE_CASEsuffix. Example:AND_15901_STORIES_CONTAINER_ENABLED. - Enforced by
FeatureTogglesNamingConventionTest. Legacy toggles that predate the rule are whitelisted in itsEXCLUDED_TOGGLES_LIST— do not add new names there without an explicit reason. - The Kotlin interface property stays human-readable without the ticket id:
isStoriesContainerEnabled.
Per-feature toggles & how to add one
Each feature owns its toggles — feature code reads them through its own
interface, never FeatureTogglesManager directly:
-
api/:XxxFeatureTogglesinterface —val isYyyEnabled: Boolean. -
impl/:DefaultXxxFeatureToggles(featureTogglesManager)exposes each toggle as a getter-backed property, not a stored value — so it is re-evaluated on every read (required for runtime toggling via the Tester Menu):override val isYyyEnabled: Boolean get() = featureTogglesManager.isFeatureEnabled(FeatureToggles.AND_<id>_YYY)Never
val isYyyEnabled = featureTogglesManager.isFeatureEnabled(...)(evaluated once at construction). -
DI: a
@Provides @Singletonin the feature's Hilt module returning the interface.
To add a toggle:
- Add
{ "name": "AND_<id>_FOO_ENABLED", "version": "undefined" }to the config JSON (the enum is regenerated at build). - Add
val isFooEnabledto the feature'sXxxFeatureTogglesand map it inDefaultXxxFeatureToggles(create the interface/impl/DI provider if the feature has none yet). - Gate code on
xxxFeatureToggles.isFooEnabled.
Removing (cleanup)
When a toggle ships at 100%, set its version to the release and run the
cleanup-feature-toggles skill — it removes the JSON entry, the interface/impl
members, inlines true, and drops dead branches. Mark code that must be deleted
together with a toggle using @RemoveWithToggle("AND_<id>_FOO_ENABLED")
(com.tangem.utils.annotations.RemoveWithToggle); the cleanup skill picks it up.