FeatureToggle
Feature toggle library for Android.
Overview
FeatureToggle
library allows to configure features of Android application in runtime using feature flags. Common usecases:
- Trunk-Based Developement when developers can release application with not production ready code in main branch and hide this code behind a feature flag.
- Safe release with new implementation of critical part of application. If critical problem found in new implementation, developers can switch to old implementation using feature flag.
- A/B testing when feature flags used to switch between multiple feature implementations.
When using FeatureToggle
library each dynamic feature in Android application must been represent as separate class or interface with multiple implementations. Each dynamic feature has Feature Flag
with unique key and FeatureFactory
.
Feature Flag
is Kotlin class that contains one or more fields that describe feature config.
Feature Flag
s can be loaded from multiple FeatureFlagDataSource
s. FeatureFlagDataSource
have priority, that used to decide from which FeatureFlagDataSource
apply specific Feature Flag
. Feature Flag
s stored in Json and in runtime represented as Koltin objects.
FeatureFactory
is responsible to create feature object using provided Feature Flag
object to make a decision how to create feature object.
FeatureToggle
library automatically generates registries of current application feature flags and factories using annotation processors.
Quick Start
- Add feature manager and compiler:
implementation("com.qiwi.featuretoggle:featuretoggle-feature-manager:0.1.0")
kapt("com.qiwi.featuretoggle:featuretoggle-compiler:0.1.0")
- Add converter that will be used to convert feature flags from Json into Kotlin objects. Two converters are available: Jackson and Gson:
implementation("com.qiwi.featuretoggle:featuretoggle-converter-jackson:0.1.0")
//or
implementation("com.qiwi.featuretoggle:featuretoggle-converter-gson:0.1.0")
- For each feature add feature flag with unique key and factory:
class SampleFeature {
//code
}
@FeatureFlag("feature_key")
class SampleFeatureFlag(
//fields
)
@Factory
class SampleFeatureFeatureFactory : FeatureFactory<SampleFeature, SampleFeatureFlag>() {
override fun createFeature(flag: SampleFeatureFlag): SampleFeature {
//construct feature using flag
}
override fun createDefault(): AndroidInfoFeature {
//construct default feature implementation
}
}
- Create instance of
FeatureManager
usingFeatureManager.Builder
. Provide converter, necessary data sources and generated registries. Also it is recommended to fetch feature flags immediately after creation instance ofFeatureManager
:
val featureManager = FeatureManager.Builder(context)
.converter(JacksonFeatureFlagConverter()) //or GsonFeatureFlagConverter()
.logger(DebugLogger()) //optional logger
.addDataSource(AssetsDataSource("feature_flags.json", context)) //also available additional data sources: FirebaseDataSource, AgConnectDataSource, RemoteDataSource
.flagRegistry(FeatureFlagRegistryGenerated()) //set generated flag registry
.factoryRegistry(FeatureFactoryRegistryGenerated()) //set generated factory registry
.build()
featureManager.fetchFlags()
- Provide instance of
FeatureManager
using your favourite DI framework or useFeatureToggle
singleton:
implementation("com.qiwi.featuretoggle:featuretoggle-feature-manager-singleton:0.1.0")
FeatureToggle.setFeatureManager(...)
FeatureToggle.featureManager().getFeature(...)
- Get feature from
FeatureManager
:
val feature = featureManager.getFeature<SampleFeature>()
It is recommended to wait with timeout for feature flags loading for example on splash screen:
class SplashScreenActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
lifecycleScope.launchWhenCreated {
runCatching {
withTimeout(TIMEOUT_MILLIS) {
FeatureToggle.featureManager().awaitFullLoading()
}
}
openMainActivity()
}
}
...
}
Assets DataSource
AssetsDataSource
loads feature flags from Json file in assets
folder. It is used to include default feature flag values into apk or app bundle. Default AssetsDataSource
priority is 1
.
Cache
Feature flags that loaded from remote data sources will be cached and used on next fetch. You can set priority of cached feature flags in FeatureManager.Builder
:
FeatureManager.Builder(context)
...
.cachedFlagsPriority(2)
Default cached flags priority is 2
.
Remote Config DataSource
FeatureToggle
supports Firebase Remote Config and AppGallery Connect Remote Configuration.
For each feature flag add remote config value with its feature flag key. Remote config value must be stored as Json string. Sample:
{
"versionName": "12",
"apiLevel": 31
}
Default remote config data sources priority is 4
.
FirebaseDataSource
:
Usage with - If you haven't already, add Firebase to your Android project.
- If you need to use Google Analytics with Remote Config, add analytics dependency:
implementation("com.google.firebase:firebase-analytics:${version}")
- Add
FirebaseDataSource
:
implementation("com.qiwi.featuretoggle:featuretoggle-datasource-firebase:0.1.0")
FeatureManager.Builder(context)
...
.addDataSource(FirebaseDataSource())
- Add feature flags in Engage > Remote Config section in the Firebase console. Sample:
For more details about Firebase Remote Config see official docs.
AgConnectDataSource
:
Usage with - If you haven't already, integrate the AppGallery Connect SDK.
- If you need to use HUAWEI Analytics with remote configuration, add analytics dependency:
implementation("com.huawei.hms:hianalytics:${version}")
- Add
AgConnectDataSource
:
implementation("com.qiwi.featuretoggle:featuretoggle-datasource-agconnect:0.1.0")
FeatureManager.Builder(context)
...
.addDataSource(AgConnectDataSource())
- Add feature flags in Grow > Remote Configuration section in AppGallery Connect. Sample:
For more details about AppGallery Connect Remote Configuration see official docs.
Remote DataSource
FeatureToggle
also have RemoteDataSource
that can load feature flags from Json REST API using OkHttp library. Response Json must be in the following format:
[
{
"feature": "android_info",
"versionName": "12",
"apiLevel": 31
}
]
Usage:
implementation("com.qiwi.featuretoggle:featuretoggle-datasource-remote:0.1.0")
FeatureManager.Builder(context)
...
.addDataSource(RemoteDataSource("url"))
Default RemoteDataSource
priority is 3
.
Debug DataSource
If there is need to update feature flags manually (for debug purposes), use DebugDataSource
:
implementation("com.qiwi.featuretoggle:featuretoggle-datasource-debug:0.1.0")
val debugDataSource = DebugDataSource()
FeatureManager.Builder(context)
...
.addDataSource(debugDataSource)
...
debugDataSource.updateFeatureFlagsFromJsonString(...)
Default DebugDataSource
priority is 100
.
Custom DataSource
It is possible to extend FeatureToggle
with custom data source:
class CustomDataSource : FeatureFlagDataSource {
override val sourceType: FeatureFlagsSourceType ...
override val key: String ...
override val priority: Int ...
override fun getFlags(
registry: FeatureFlagRegistry,
converter: FeatureFlagConverter,
logger: Logger
): Flow<Map<String, Any>> = flow {
...
}
}
Testing
If you need to use FeatureManager
in unit tests, use FakeFeatureManager
. It does not load flags from data sources. Instead, it uses mocked feature flags. Usage:
testImplementation("com.qiwi.featuretoggle:featuretoggle-feature-manager-test:0.1.0")
val fakeFeatureManager = FakeFeatureManager.create(FeatureFlagRegistryGenerated(), FeatureFactoryRegistryGenerated())
...
fakeFeatureManager.overrideFlag(...)
R8/Proguard
FeatureToggle
modules have bundled proguard rules for its classes. However, for each feature flag class you need to add proguard rule:
-keep class com.example.SampleFeatureFlag { *; }
License
MIT License
Copyright (c) 2021 QIWI
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.