{"id":20492547,"url":"https://github.com/redmadrobot/konfeature","last_synced_at":"2026-03-02T13:13:16.574Z","repository":{"id":250149837,"uuid":"824084114","full_name":"RedMadRobot/Konfeature","owner":"RedMadRobot","description":"Kotlin library for working with feature remote configuration","archived":false,"fork":false,"pushed_at":"2026-02-18T15:21:34.000Z","size":164,"stargazers_count":27,"open_issues_count":6,"forks_count":2,"subscribers_count":6,"default_branch":"main","last_synced_at":"2026-02-18T19:21:13.444Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RedMadRobot.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-07-04T10:28:32.000Z","updated_at":"2026-02-18T10:36:52.000Z","dependencies_parsed_at":"2024-07-31T19:47:16.730Z","dependency_job_id":null,"html_url":"https://github.com/RedMadRobot/Konfeature","commit_stats":null,"previous_names":["redmadrobot/konfeature"],"tags_count":1,"template":false,"template_full_name":"RedMadRobot/android-library-template","purl":"pkg:github/RedMadRobot/Konfeature","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RedMadRobot%2FKonfeature","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RedMadRobot%2FKonfeature/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RedMadRobot%2FKonfeature/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RedMadRobot%2FKonfeature/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RedMadRobot","download_url":"https://codeload.github.com/RedMadRobot/Konfeature/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RedMadRobot%2FKonfeature/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30003756,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-02T12:19:43.414Z","status":"ssl_error","status_checked_at":"2026-03-02T12:19:02.215Z","response_time":60,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-15T17:29:35.122Z","updated_at":"2026-03-02T13:13:16.568Z","avatar_url":"https://github.com/RedMadRobot.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Konfeature\n\n\u003cimg width=\"1560\" height=\"877\" alt=\"75fc26ed9b4a25dc2010f846dae2d792\" src=\"https://github.com/user-attachments/assets/218c27e6-0d06-4c25-bbaa-796343d4e7d5\" /\u003e\n\n[![Version](https://img.shields.io/maven-central/v/com.redmadrobot.konfeature/konfeature?style=flat-square)][mavenCentral]\n[![Build Status](https://img.shields.io/github/actions/workflow/status/RedMadRobot/konfeature/main.yml?branch=main\u0026style=flat-square)][ci]\n[![License](https://img.shields.io/github/license/RedMadRobot/Konfeature?style=flat-square)][license]\n[![Android](https://img.shields.io/badge/Android-3DDC84?style=flat-square\u0026logo=android\u0026logoColor=white)](#)\n[![iOS](https://img.shields.io/badge/iOS-000000?style=flat-square\u0026logo=apple\u0026logoColor=white)](#)\n[![JVM](https://img.shields.io/badge/JVM-007396?style=flat-square\u0026logo=java\u0026logoColor=white)](#)\n\n**Konfeature** is a powerful **Kotlin Multiplatform** library for managing remote configuration in your applications. It provides a clean, declarative API for working with feature flags and configuration elements across Android, iOS, and JVM platforms.\n\nWorking with remote configuration has become a standard part of the development process for almost any application. Depending on the complexity of the application, several requirements for such functionality may arise, including:\n- convenient syntax for declaring configuration elements\n- the ability to separate configuration into different files for different features\n- the ability to make the configuration local-only during active feature development\n- support for multiple data sources for remote config\n- the ability to view a list of all configurations and modify their values for debugging purposes\n- logging the value and its source when accessing the configuration, as well as logging non-critical errors\n\nWe have made every effort to meet all these requirements in the development of Konfeature.\n\n---\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n- [Supported Platforms](#supported-platforms)\n- [Installation](#installation)\n- [Usage](#usage)\n  - [FeatureConfig](#featureconfig)\n  - [FeatureSource](#featuresource)\n  - [SourceSelectionStrategy](#sourceselectionstrategy)\n  - [Interceptor](#interceptor)\n  - [Logger](#logger)\n  - [Spec](#spec)\n  - [Ordering](#ordering)\n- [Contributing](#contributing)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n## Supported Platforms\n\nKonfeature is a **Kotlin Multiplatform** library with support for:\n\n| Platform | Status | Targets |\n|----------|--------|---------|\n| **Android** | ✅ Fully Supported | JVM (via Kotlin/JVM) |\n| **iOS** | ✅ Fully Supported | arm64, x86_64, simulator arm64 |\n| **JVM** | ✅ Fully Supported | Java/Kotlin applications |\n\n## Installation\n\n### Add Maven Central Repository\n\n```groovy\nrepositories {\n    mavenCentral()\n}\n```\n\n### Add Dependency\n\n**For Gradle (Kotlin Multiplatform Project):**\n\n```kotlin\nkotlin {\n    sourceSets {\n        commonMain.dependencies {\n            implementation(\"com.redmadrobot.konfeature:konfeature:\u003cversion\u003e\")\n        }\n    }\n}\n```\n\n**For Gradle (Single Platform):**\n\n```groovy\ndependencies {\n    implementation(\"com.redmadrobot.konfeature:konfeature:\u003cversion\u003e\")\n}\n```\n\n## Usage\n\n### FeatureConfig\n\nDefines a set of configuration elements, where each element is defined using a delegate.\nThere are two types of delegates:\n- `by toggle(...)` - used for elements of type `Boolean`\n- `by value(...)` - used for elements of any other type\n\n```kotlin\nclass ProfileFeatureConfig : FeatureConfig(\n    name = \"profile_feature_config\",\n    description = \"Config of features for profile usage\"\n) {\n    val isProfileFeatureEnabled: Boolean by toggle(\n        key = \"profile_feature\",\n        description = \"show profile entry point for user\",\n        defaultValue = false,\n    )\n\n    val profileFeatureTitle: String by value(\n        key = \"profile_feature_title\",\n        description = \"title of profile entry point button\",\n        defaultValue = \"Feature number nine\",\n        sourceSelectionStrategy = SourceSelectionStrategy.Any\n    )\n\n    val profileButtonAppearDuration: Long by value(\n        key = \"profile_button_appear_duration\",\n        description = \"duration of profile button appearing in ms\",\n        defaultValue = 200,\n        sourceSelectionStrategy = SourceSelectionStrategy.Any\n    )\n}\n```\n\nThe configuration requires specifying:\n- `name` - the name of the configuration\n- `description` - a detailed description of the configuration\n\nEach configuration element requires specifying:\n- `key` - used to retrieve the value of the element from a `Source`\n- `description` - a detailed description of the element\n- `defaultValue` - used if the value cannot be found in a `Source`\n- `sourceSelectionStrategy` - the strategy for selecting a `Source` using [SourceSelectionStrategy](#sourceselectionstrategy)\n\nAfter that, you need to register the configuration in `Konfeature`:\n\n```kotlin\nval profileFeatureConfig: FeatureConfig = ProfileFeatureConfig()\n\nval konfeatureInstance = konfeature {\n    register(profileFeatureConfig) \n}\n```\n\n\u003eSimilarly, you can add multiple configurations, for example, for each module, when organizing multi-modularity by features.\n\n### FeatureSource\n\nAn abstraction over the value source for configuration elements.\n\n```kotlin\npublic interface FeatureSource {\n\n    public val name: String\n\n    public fun get(key: String): Any?\n}\n```\n- `name` - source name\n- `get(key: String)` - logic for getting values by `key`  \n\nExample implementation based on `FirebaseRemoteConfig`:\n\n```kotlin\nclass FirebaseFeatureSource(\n    private val remoteConfig: FirebaseRemoteConfig\n) : FeatureSource {\n    \n    override val name: String = \"FirebaseRemoteConfig\"\n\n    override fun get(key: String): Any? {\n        return remoteConfig\n            .getValue(key)\n            .takeIf { source == FirebaseRemoteConfig.VALUE_SOURCE_REMOTE }\n            ?.let { value: FirebaseRemoteConfigValue -\u003e\n                value.getOrNull { asBoolean() }\n                    ?: value.getOrNull { asString() }\n                    ?: value.getOrNull { asLong() }\n                    ?: value.getOrNull { asDouble() }\n            }\n    }\n        \n    private fun FirebaseRemoteConfigValue.getOrNull(\n        getter: FirebaseRemoteConfigValue.() -\u003e Any?\n    ): Any? {        \n        return try {\n            getter()    \n        } catch (error: IllegalArgumentException) {\n            null\n        }        \n    }\n}\n```\nAfter that, you need to add the `Source` in `Konfeature`:\n\n```kotlin\nval profileFeatureConfig: FeatureConfig = ProfileFeatureConfig()\nval source: FeatureSource = FirebaseFeatureSource(remoteConfig)\n\nval konfeatureInstance = konfeature {\n    addSource(source)\n    register(profileFeatureConfig) \n}\n```\n\n\u003eSimilarly, you can add multiple sources, for example, Huawei AppGallery, RuStore, or your own backend.\n\n### SourceSelectionStrategy\n\nYou can configure the retrieval of an element's value from the source more flexibly by using the `sourceSelectionStrategy` parameter:\n\n```kotlin\nval profileFeatureTitle: String by value(\n    key = \"profile_feature_title\",\n    description = \"title of profile entry point button\",\n    defaultValue = \"Feature number nine\",\n    sourceSelectionStrategy = SourceSelectionStrategy.Any\n)\n```\n\nWhere `sourceSelectionStrategy` filters the available data sources.\n\n```kotlin\npublic fun interface SourceSelectionStrategy {\n\n    public fun select(names: Set\u003cString\u003e): Set\u003cString\u003e\n\n    public companion object {\n        public val None: SourceSelectionStrategy = SourceSelectionStrategy { emptySet() }\n        public val Any: SourceSelectionStrategy = SourceSelectionStrategy { it }\n\n        public fun anyOf(vararg sources: String): SourceSelectionStrategy = SourceSelectionStrategy { sources.toSet() }\n    }\n}\n```\n\nThe `select(...)` method receives a list of available `Source` names and returns a list of sources from which the configuration element can retrieve a value.\n\nFor most scenarios, predefined implementations will be sufficient:\n- `SourceSelectionStrategy.None` - prohibits taking values from any source, i.e., the value specified in `defaultValue` will always be used\n- `SourceSelectionStrategy.Any` - allows taking values from any source\n- `SourceSelectionStrategy.anyOf(\"Source 1\", ... ,\"Source N\")` - allows taking values from the specified list of sources\n\n\u003e [!IMPORTANT]\n\u003e By default, `SourceSelectionStrategy.None` is used!\n\n### Interceptor\n\nAllows intercepting and overriding the value of the element.\n\n```kotlin\npublic interface Interceptor {\n\n    public val name: String\n\n    public fun intercept(valueSource: FeatureValueSource, key: String, value: Any): Any?\n}\n```\n\n- `name` - the name of the interceptor\n- `intercept(valueSource: FeatureValueSource, key: String, value: Any): Any?` - called when accessing the element with `key` and `value` from `valueSource(Source(\u003cname\u003e), Interceptor(\u003cname\u003e), Default)`, and returns its new value or `null` if it doesn't change\n\nExample of implementation based on `DebugPanelInterceptor`:\n\n```kotlin\nclass DebugPanelInterceptor : Interceptor {\n\n    private val values = mutableMapOf\u003cString, Any\u003e()\n\n    override val name: String = \"DebugPanelInterceptor\"\n\n    override fun intercept(valueSource: FeatureValueSource, key: String, value: Any): Any? {\n        return values[key]\n    }\n\n    fun setFeatureValue(key: String, value: Any) {\n        values[key] = value\n    }\n\n    fun removeFeatureValue(key: String) {\n        values.remove(key)\n    }\n}\n```\n\nAfter that, you need to add the `Interceptor` in `Konfeature`:\n\n```kotlin\nval profileFeatureConfig: FeatureConfig = ProfileFeatureConfig()\nval source: FeatureSource = FirebaseFeatureSource(remoteConfig)\nval debugPanelInterceptor: Interceptor = DebugPanelInterceptor()\n\nval konfeatureInstance = konfeature {\n    addSource(source)\n    register(profileFeatureConfig)\n    addInterceptor(debugPanelInterceptor)\n}\n```\n\n\u003eSimilarly, you can add multiple interceptors.\n\n### Logger\n\n```kotlin\npublic interface Logger {\n\n    public fun log(severity: Severity, message: String)\n\n    public enum class Severity {\n        WARNING, INFO\n    }\n}\n```\n\nThe following events are logged:\n\n- key, value, and its source when requested\n\u003eGet value 'true' by key 'profile_feature' from 'Source(name=FirebaseRemoteConfig)'\n- `Source` or `Interceptor` returns an unexpected type for `key`\n\u003eUnexpected value type for 'profile_button_appear_duration': expected type is 'kotlin.Long', but value from 'Source(name=FirebaseRemoteConfig)' is 'true' with type 'kotlin.Boolean'\n\nExample of implementation based on `Timber`:\n\n```kotlin\nclass TimberLogger: Logger {\n    \n    override fun log(severity: Severity, message: String) {\n        if (severity == INFO) {\n            Timber.tag(TAG).i(message)    \n        } else if (severity == WARNING) {\n            Timber.tag(TAG).w(message)\n        }\n    }\n    \n    companion object {\n        private const val TAG = \"Konfeature\"\n    }\n}\n```\n\nAfter that, you need to add the `Logger` in `Konfeature`:\n\n```kotlin\nval profileFeatureConfig: FeatureConfig = ProfileFeatureConfig()\nval source: FeatureSource = FirebaseFeatureSource(remoteConfig)\nval debugPanelInterceptor: Interceptor = DebugPanelInterceptor()\nval logger: Logger = TimberLogger()\n\nval konfeatureInstance = konfeature {\n    addSource(source)\n    register(profileFeatureConfig)\n    addInterceptor(debugPanelInterceptor)\n    setLogger(logger)\n}\n```\n\n### Spec\n\nKonfeature contains information about all registered `FeatureConfig` in the form of `spec`:\n\n```kotlin\npublic interface Konfeature {\n\n    public val spec: List\u003cFeatureConfigSpec\u003e\n\n    public fun \u003cT : Any\u003e getValue(spec: FeatureValueSpec\u003cT\u003e): FeatureValue\u003cT\u003e\n}\n```\n\nThis allows you to obtain information about added configurations as well as the current value of each element:\n\n```kotlin\nval konfeatureInstance = konfeature {...}\n\nval featureConfigSpec = konfeatureInstance.spec[0]\nval featureSpec = featureConfigSpec.values[0]\nval featureValue = konfeatureInstance.getValue(featureSpec)\n```\n\u003e  This can be useful for use in the DebugPanel\n\n## Ordering\nThe value of the configuration element is determined in the following order:\n\n- `defaultValue` and `Default` source are assigned.\n- Using `sourceSelectionStrategy`, a list of `Sources` from which a value can be requested is determined.\n- Search the list of `Sources` in the order they were added to `Konfeature`, **stopping at the first occurrence** of the element by `key`.\n  Upon successful search, the value from `Source` is assigned with `Source(name=SourceName)` source.\n- Search the list of `Interceptors` in the order they were added to `Konfeature`.\n  If `Interceptor` returns a value other than `null`, this value is assigned with `Interceptor(name=InterceptorName)` source.\n\n## Contributing\n\nMerge requests are welcome.  \nFor major changes, please open an issue first to discuss what you would like to change.\n\n[mavenCentral]: https://central.sonatype.com/artifact/com.redmadrobot.konfeature/konfeature\n[ci]: https://github.com/RedMadRobot/Konfeature/actions?query=branch%3Amain\n[license]: ./LICENSE\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fredmadrobot%2Fkonfeature","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fredmadrobot%2Fkonfeature","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fredmadrobot%2Fkonfeature/lists"}