Giter Site home page Giter Site logo

misikora / laboratory Goto Github PK

View Code? Open in Web Editor NEW
76.0 3.0 3.0 3.09 MB

Feature flags for multi-module Kotlin Android projects

Home Page: https://mehow.io/laboratory/

License: Apache License 2.0

Kotlin 98.92% Shell 0.93% CSS 0.06% HTML 0.09%
feature-flags android kotlin gradle-plugin jetpack-android jetpack-datastore android-development ab-testing code-generation laboratory

laboratory's People

Contributors

alexkrupa avatar boguszpawlowski avatar dependabot[bot] avatar misikora avatar vudzkostek avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

laboratory's Issues

Feature flags cannot be deprecated with error level

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
}

Use composite build for library

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.

Redesign Feature interface

  • Remove isDefaultValue in favour of defaultValue property.
  • Add extensions to Class<Feature<T>> for a description, a source, and a default value.
  • Rename value nomenclature to option.
  • Maybe rename Feature to FeatureFlag?

Generate option factory

interface OptionFactory {
  fun optionOf(featureName: String, optionName: String): Feature<*>?
}
laboratory {
  optionFactory {
    packageName = "io.mehow.sample"
    isPublic = true
    useFqcn = true
    projectFilter { project -> false }
  }
}

Runtime crash with multi Application module project.

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?

  1. You must have more than one application modules in same codebase.
  2. All modules must use laboratory gradle plugin (io.mehow.laboratory)
  3. Use feature flags on all modules.
  4. Run any of the application module and try opening Laboratory.
    There will be a runtime crash follows by ClassNotFoundException

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 :)

Add laboratory dependencies as runtime dependencies

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?

Overrides of sources in `DefaultOptionFactory` are not respected

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.

Improve search animation

Fading content in and out is ugly. Use some AVD animations. Most likely with Kyrie for controllable animation progress.

Generate features per Android variant

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.

Observe changes to a default option

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.

public fun <T : Feature<T>> observe(feature: Class<T>): Flow<T> {
val options = feature.options
val defaultOption = getDefaultOption(feature)
return storage.observeFeatureName(feature).map { featureName ->
val expectedName = featureName ?: defaultOption.name
options.firstOrNull { it.name == expectedName } ?: defaultOption
}
}

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.

Add parent-child relationship to features

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:

  • expandable list - regular items (without group) could be represented as switches, groups could be represented as expandable list items
  • child screen - regular items (without group) could be represented as switches, groups could be items without switch that would open additional screen with child flags.

Related to issue: #82

Can't generate feature flags with Kotlin 1.7.20

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")
    }
}

Observe features in LaboratoryActivity

Right now features displayed to the user are not observed in a reactive way.

https://github.com/MiSikora/Laboratory/blob/60a40713ce1e55ecbff0216b45d2b5f793b46057/library/inspector/src/main/java/io/mehow/laboratory/inspector/Presenter.kt#L13-L18

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.

Make Laboratory an interface

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
}

Support Gradle configuration cache

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.