misikora / laboratory Goto Github PK
View Code? Open in Web Editor NEWFeature flags for multi-module Kotlin Android projects
Home Page: https://mehow.io/laboratory/
License: Apache License 2.0
Feature flags for multi-module Kotlin Android projects
Home Page: https://mehow.io/laboratory/
License: Apache License 2.0
Configuration cache is an incubating Gradle feature that, well, caches configuration.
Running a task with configuration caching (Gradle 7.4.2) reports the following problems:
> ./gradlew --configuration-cache --configuration-cache-problems=warn :app:assembleDebug
...
> Configure project :app
2 problems were found storing the configuration cache.
- Task `:app:generateFeatureFactory` of type `io.mehow.laboratory.gradle.FeatureFactoryTask`: cannot serialize object of type 'org.gradle.api.internal.project.DefaultProject', a subtype of 'org.gradle.api.Project', as these are not supported with the configuration cache.
See https://docs.gradle.org/7.4.2/userguide/configuration_cache.html#config_cache:requirements:disallowed_types
- Task `:app:generateFeatureFlags` of type `io.mehow.laboratory.gradle.FeatureFlagsTask`: cannot serialize object of type 'org.gradle.api.internal.project.DefaultProject', a subtype of 'org.gradle.api.Project', as these are not supported with the configuration cache.
See https://docs.gradle.org/7.4.2/userguide/configuration_cache.html#config_cache:requirements:disallowed_types
...
The documentation specifies requirements for configuration cache to work.
Markdown format should be sufficient.
interface OptionFactory {
fun optionOf(featureName: String, optionName: String): Feature<*>?
}
laboratory {
optionFactory {
packageName = "io.mehow.sample"
isPublic = true
useFqcn = true
projectFilter { project -> false }
}
}
Due to introduction of DefaultOptionFactory
a default option can be easily changed at runtime. Right now, observing a feature flag option will always use a default option that was available during observe()
call instead of using most recent value.
Simply moving val defaultOption = getDefaultOption(feature)
to the map()
call is a good start but it does not solve an issue where a feature flag is observed and a default option changes in the factory. This would require some callback from the factory that informs Laboratory
of default option changes so it can propagate them.
It might be also a pointless thing to implement since it seems like a very rare use case that will needlessly complicate the API.
Error deprecation level feature flags should be generated in a way that allows for this deprecation.
@Deprecated("message", level = ERROR)
private enum class DeprecatedError : Feature<@Suppress("DEPRECATION_ERROR") DeprecatedError> {
Option,
;
override val defaultOption get() = Option
}
After upgrading kotlin in project to version 1.7.20, when trying to assemble module with feature flags, we get:
Cause 1: java.lang.IllegalArgumentException: Did not find Kotlin source set
at io.mehow.laboratory.gradle.SourceSetContributionKt.contributeToKotlin(SourceSetContribution.kt:61)
at io.mehow.laboratory.gradle.SourceSetContributionKt.contributeToSourceSets(SourceSetContribution.kt:23)
at io.mehow.laboratory.gradle.LaboratoryPlugin.addSourceSets(LaboratoryPlugin.kt:154)
at io.mehow.laboratory.gradle.LaboratoryPlugin.registerFeaturesTask$lambda-11(LaboratoryPlugin.kt:74)
at org.gradle.configuration.internal.DefaultUserCodeApplicationContext$CurrentApplication$1.execute(DefaultUserCodeApplicationContext.java:123)
Module does not have kotlin/java code, only build.gradle with feature flags declaration:
apply plugin: 'kotlin'
apply plugin: 'io.mehow.laboratory'
laboratory {
packageName = "eu.some.package"
featureFactory {
packageName = "eu.some.package"
isPublic = true
}
feature("SomeFlag") {
description = "Some description"
withDefaultOption("Disabled")
withOption("Enabled")
withDefaultSource("Remote")
}
}
Fading content in and out is ugly. Use some AVD animations. Most likely with Kyrie for controllable animation progress.
laboratory {
AuthType {
withValue("None")
withDefaultValue("Fingerprint")
}
}
This is in anticipation of multiplatform support and because Java support is not a target of this library anyway.
Right now it's a little bit messy. Maybe use GH pages?
Hi @MiSikora
Hope you are doing well.
I've encountered a Runtime crash in Laboratory where we have two application modules in same project.
How to Reproduce?
For me, Crashlog looks like below.
java.lang.ClassNotFoundException: com.module.two.laboratory.sdk.Feature1 at java.lang.Class.classForName(Native Method) at java.lang.Class.forName(Class.java:454) at java.lang.Class.forName(Class.java:379) at com.module.one.laboratory.sdk.GeneratedFeatureFactory.create(GeneratedFeatureFactory.kt:15) at io.mehow.laboratory.inspector.InspectorViewModel$$special$$inlined$mapValues$lambda$1$1.invokeSuspend(InspectorViewModel.kt:54) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
What we have figured out so far is the laboratory is expecting all the feature flags in both of the modules.
i.e. If module one have 2 features and Module two have another 2 features.
Then Laboratory is expecting 4 features in total so far, No matter what the module is being compiled.
Since it's being accessed with Reflection Class.forName
so module compiles fine but crashes as soon you open Laboratory.
Laboratory Version: 0.12.1
Android Version: Any
Let me know if you need anything else.
Cheers :)
๐ https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter
Blocked by: alexjlockwood/kyrie#29, Kotlin/dokka#1730, Kotlin/kotlinx.html#173, stable AGP 7.0
Currently it is "blocked" by KT-14743 as making the whole API inline
is not something I want to dive into.
isDefaultValue
in favour of defaultValue
property.Class<Feature<T>>
for a description, a source, and a default value.value
nomenclature to option
.Feature
to FeatureFlag
?Right now features displayed to the user are not observed in a reactive way.
This can cause an issue if one feature is used to control the configuration of others as there will be no live updates for the users and they'll need to exit and go back to refresh the state. Flow
API added in 0.2.0
should be used instead of the regular suspend
function.
Users should be able to search by feature flag name, options and sources.
LaboratoryActivity
should expose an option to reset local values of feature flags and source values.
When having multiple flags related to one feature (eg. we have a feature that we can enable with one flag and because we are also developing two extension to this feature we have another two flags etc.) it would be nice if there would be a possibility to group flags.
Eg. by adding groupId = ""
in flag declaration:
SomeFeature {
withOption("Enabled")
withDefaultOption("Disabled")
withGroupId("FeatureSet1")
}
And based on that we could introduce some new way of representing flags in groups:
Related to issue: #82
Not really sure about this right now.
object SourceOptionFactory : DefaultOptionFactory {
override fun <T : Feature<out T>> create(feature: T): Feature<*>? = feature::class.java
.takeIf { it.canonicalName?.split('.')?.lastOrNull() == "Source" }
?.options
?.firstOrNull { it.name == "Local" }
}
Assuming that feature flags matched in takeIf()
are source feature flags, then they are not respected by Laboratory
because the factory does not interact with SourcedFeatureStorage
.
Fixing this bug will most likely require breaking changes to SourcedFeatureFactory
.
Also, not really sure if this should be supported. Maybe the whole concept of DefaultOptionFactory
should be removed before 1.0.0
.
This will remove a need for a local maven repository dependency. One thing that is problematic are shared versions from buildSrc
. It might be better to migrate to Gradle to Groovy. Also buildSrc
is kind of bad due to clean builds. An alternative worth considering is a separate Gradle plugin with lib versions that is also a composite build.
laboratory {
optionFactory()
dependency(project(":module-a"))
}
Using generated OptionFactory
will fail without including them in the module's dependencies block. Perhaps all projects included in laboratory
should be added as implementation
when OptionFactory
is auto-generated? Or make it configurable as an opt-in?
When having multiple (20 or more flags) it gets harder to find option that we are interested in.
Would be nice if we could set custom layout for experiment item, or if there would be some predefined compact layout that we could enable in LaboratoryActivity configuration.
Related to issue: #81
Dokka should provide GFM output that can be used by MkDocs to deploy it to the website.
Blocked by #93
Documentation should provide information about testing changes to the website locally.
This will allow to generate a specialised Laboratory
.
interface Laboratory {
// Whole public API
}
interface FirebaseLaboratory : Laboratory {
suspend fun <T : Feature<*>> setFirebaseFeature(feature: T)
@JvmDefault
@BlockingIoCall
fun <T : Feature<*>> setFirebaseFeatureBlocking(feature: T) = runBlocking { setFirebaseFeature(feature) }
suspend fun <T : Feature<*>> setFirebaseFeatures(vararg features: T)
@JvmDefault
@BlockingIoCall
fun <T : Feature<*>> setFirebaseFeaturesBlocking(vararg features: T) = runBlocking { setFirebaseFeatures(*features) }
}
interface AwsLaboratory : Laboratory {
suspend fun <T : Feature<*>> setAwsFeature(feature: T)
@JvmDefault
@BlockingIoCall
fun <T : Feature<*>> setAwsFeatureBlocking(feature: T) = runBlocking { setAwsFeature(feature) }
suspend fun <T : Feature<*>> setAwsFeatures(vararg features: T)
@JvmDefault
@BlockingIoCall
fun <T : Feature<*>> setAwsFeaturesBlocking(vararg features: T) = runBlocking { setAwsFeatures(*features) }
}
class SourcedLaboratory : Laboratory, FirebaseLaboratory, AwsLaboratory {
// Implementation
}
Feature flags should have descriptions that can be displayed in LaboratoryActivity
.
It would be useful to enable generation of feature flags per variant. API could look like this.
laboratory {
feature("SomeFeature") {
withValue("Enabled")
withDefaultValue("Disabled")
androidVariantFilter { variant -> variant.name == "debug" }
}
}
It would generate enum class SomeFeature
only in debug
builds.
This needs a fix in Kotlin https://youtrack.jetbrains.com/issue/KT-34480.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.