A type-safe cascading configuration library for Kotlin/Java/Android, supporting most configuration formats

Overview

Konf

Java 8+ Maven metadata URL JitPack Build Status codecov codebeat badge Awesome Kotlin Badge

A type-safe cascading configuration library for Kotlin/Java/Android, supporting most configuration formats.

Features

  • Type-safe. Get/set value in config with type-safe APIs.
  • Thread-safe. All APIs for config is thread-safe.
  • Batteries included. Support sources from JSON, XML, YAML, HOCON, TOML, properties, map, command line and system environment out of box.
  • Cascading. Config can fork from another config by adding a new layer on it. Each layer of config can be updated independently. This feature is powerful enough to support complicated situation such as configs with different values share common fallback config, which is automatically updated when configuration file changes.
  • Self-documenting. Document config item with type, default value and description when declaring.
  • Extensible. Easy to customize new sources for config or expose items in config.

Contents

Prerequisites

  • JDK 8 or higher
  • tested on Android SDK 23 or higher

Use in your projects

This library has been published to Maven Central and JitPack.

Konf is modular, you can use different modules for different sources:

  • konf-core: for built-in sources (JSON, properties, map, command line and system environment)
  • konf-hocon: for built-in + HOCON sources
  • konf-toml: for built-in + TOML sources
  • konf-xml: for built-in + XML sources
  • konf-yaml: for built-in + YAML sources
  • konf-git: for built-in + Git sources
  • konf: for all sources mentioned above
  • konf-js: for built-in + JavaScript (use GraalVM JavaScript) sources

Maven

<dependency>
  <groupId>com.uchuhimo</groupId>
  <artifactId>konf</artifactId>
  <version>1.1.2</version>
</dependency>

Gradle

compile 'com.uchuhimo:konf:1.1.2'

Gradle Kotlin DSL

compile(group = "com.uchuhimo", name = "konf", version = "1.1.2")

Maven (master snapshot)

Add JitPack repository to <repositories> section:

<repository>
    <id>jitpack.io</id>
    <url>https://jitpack.io</url>
</repository>

Add dependencies:

<dependency>
    <groupId>com.github.uchuhimo</groupId>
    <artifactId>konf</artifactId>
    <version>master-SNAPSHOT</version>
</dependency>

Gradle (master snapshot)

Add JitPack repository:

repositories {
    maven { url 'https://jitpack.io' }
}

Add dependencies:

compile 'com.github.uchuhimo.konf:konf:master-SNAPSHOT'

Gradle Kotlin DSL (master snapshot)

Add JitPack repository:

repositories {
    maven(url = "https://jitpack.io")
}

Add dependencies:

compile(group = "com.github.uchuhimo.konf", name = "konf", version = "master-SNAPSHOT")

Quick start

  1. Define items in config spec:

    object ServerSpec : ConfigSpec() {
        val host by optional("0.0.0.0")
        val tcpPort by required<Int>()
    }
  2. Construct config with items in config spec and values from multiple sources:

    val config = Config { addSpec(ServerSpec) }
            .from.yaml.file("server.yml")
            .from.json.resource("server.json")
            .from.env()
            .from.systemProperties()

    or:

    val config = Config { addSpec(ServerSpec) }.withSource(
        Source.from.yaml.file("server.yml") +
        Source.from.json.resource("server.json") +
        Source.from.env() +
        Source.from.systemProperties()
    )

    This config contains all items defined in ServerSpec, and load values from 4 different sources. Values in resource file server.json will override those in file server.yml, values from system environment will override those in server.json, and values from system properties will override those from system environment.

    If you want to watch file server.yml and reload values when file content is changed, you can use watchFile instead of file:

    val config = Config { addSpec(ServerSpec) }
            .from.yaml.watchFile("server.yml")
            .from.json.resource("server.json")
            .from.env()
            .from.systemProperties()
  3. Define values in source. You can define in any of these sources:

    • in server.yml:
      server:
          host: 0.0.0.0
          tcp_port: 8080
    • in server.json:
      {
          "server": {
              "host": "0.0.0.0",
              "tcp_port": 8080
          }
      }
    • in system environment:
      SERVER_HOST=0.0.0.0
      SERVER_TCPPORT=8080
    • in command line for system properties:
      -Dserver.host=0.0.0.0 -Dserver.tcp_port=8080
  4. Retrieve values from config with type-safe APIs:

    data class Server(val host: String, val tcpPort: Int) {
        fun start() {}
    }
    
    val server = Server(config[ServerSpec.host], config[ServerSpec.tcpPort])
    server.start()
  5. Retrieve values from multiple sources without using config spec:

    val server = Config()
            .from.yaml.file("server.yml")
            .from.json.resource("server.json")
            .from.env()
            .from.systemProperties()
            .at("server")
            .toValue<Server>()
    server.start()

Define items

Config items is declared in config spec, added to config by Config#addSpec. All items in same config spec have same prefix. Define a config spec with prefix local.server:

object ServerSpec : ConfigSpec("server") {
}

If the config spec is binding with single class, you can declare config spec as companion object of the class:

class Server {
    companion object : ConfigSpec("server") {
        val host by optional("0.0.0.0")
        val tcpPort by required<Int>()
    }
}

The config spec prefix can be automatically inferred from the class name, leading to further simplification like:

object ServerSpec : ConfigSpec() {
}

or

class Server {
    companion object : ConfigSpec() {
    }
}

Here are some examples showing the inference convention: Uppercase to uppercase, lowercase to lowercase, SuffixSpec to suffix, TCPService to tcpService.

The config spec can also be nested. For example, the path of Service.Backend.Login.user in the following example will be inferred as "service.backend.login.user":

object Service : ConfigSpec() {
    object Backend : ConfigSpec() {
        object Login : ConfigSpec() {
            val user by optional("admin")
        }
    }
}

There are three kinds of item:

  • Required item. Required item doesn't have default value, thus must be set with value before retrieved in config. Define a required item with description:
    val tcpPort by required<Int>(description = "port of server")
    Or omit the description:
    val tcpPort by required<Int>()
  • Optional item. Optional item has default value, thus can be safely retrieved before setting. Define an optional item:
    val host by optional("0.0.0.0", description = "host IP of server")
    Description can be omitted.
  • Lazy item. Lazy item also has default value, however, the default value is not a constant, it is evaluated from thunk every time when retrieved. Define a lazy item:
    val nextPort by lazy { config -> config[tcpPort] + 1 }

You can also define config spec in Java, with a more verbose API (compared to Kotlin version in "quick start"):

public class ServerSpec {
  public static final ConfigSpec spec = new ConfigSpec("server");

  public static final OptionalItem<String> host =
      new OptionalItem<String>(spec, "host", "0.0.0.0") {};

  public static final RequiredItem<Integer> tcpPort = new RequiredItem<Integer>(spec, "tcpPort") {};
}

Notice that the {} part in item declaration is necessary to avoid type erasure of item's type information.

Use config

Create config

Create an empty new config:

val config = Config()

Or an new config with some initial actions:

val config = Config { addSpec(Server) }

Add config spec

Add multiple config specs into config:

config.addSpec(Server)
config.addSpec(Client)

Retrieve value from config

Retrieve associated value with item (type-safe API):

val host = config[Server.host]

Retrieve associated value with item name (unsafe API):

val host = config.get<String>("server.host")

or:

val host = config<String>("server.host")

Cast config to value

Cast config to a value with the target type:

val server = config.toValue<Server>()

Check whether an item exists in config or not

Check whether an item exists in config or not:

config.contains(Server.host)
// or
Server.host in config

Check whether an item name exists in config or not:

config.contains("server.host")
// or
"server.host" in config

Check whether all values of required items exist in config or not:

config.containsRequired()

Throw exception if some required items in config don't have values:

config.validateRequired()

Modify value in config

Associate item with value (type-safe API):

config[Server.tcpPort] = 80

Find item with specified name, and associate it with value (unsafe API):

config["server.tcpPort"] = 80

Discard associated value of item:

config.unset(Server.tcpPort)

Discard associated value of item with specified name:

config.unset("server.tcpPort")

Associate item with lazy thunk (type-safe API):

config.lazySet(Server.tcpPort) { it[basePort] + 1 }

Find item with specified name, and associate it with lazy thunk (unsafe API):

config.lazySet("server.tcpPort") { it[basePort] + 1 }

Subscribe the update event

Subscribe the update event of an item:

val handler = Server.host.onSet { value -> println("the host has changed to $value") }

Subscribe the update event before every set operation:

val handler = Server.host.beforeSet { config, value -> println("the host will change to $value") }

or

val handler = config.beforeSet { item, value -> println("${item.name} will change to $value") }

Subscribe the update event after every set operation:

val handler = Server.host.afterSet { config, value -> println("the host has changed to $value") }

or

val handler = config.afterSet { item, value -> println("${item.name} has changed to $value") }

Cancel the subscription:

handler.cancel()

Export value in config as property

Export a read-write property from value in config:

var port by config.property(Server.tcpPort)
port = 9090
check(port == 9090)

Export a read-only property from value in config:

val port by config.property(Server.tcpPort)
check(port == 9090)

Fork from another config

val config = Config { addSpec(Server) }
config[Server.tcpPort] = 1000
// fork from parent config
val childConfig = config.withLayer("child")
// child config inherit values from parent config
check(childConfig[Server.tcpPort] == 1000)
// modifications in parent config affect values in child config
config[Server.tcpPort] = 2000
check(config[Server.tcpPort] == 2000)
check(childConfig[Server.tcpPort] == 2000)
// modifications in child config don't affect values in parent config
childConfig[Server.tcpPort] = 3000
check(config[Server.tcpPort] == 2000)
check(childConfig[Server.tcpPort] == 3000)

Load values from source

Use from to load values from source doesn't affect values in config, it will return a new child config by loading all values into new layer in child config:

val config = Config { addSpec(Server) }
// values in source is loaded into new layer in child config
val childConfig = config.from.env()
check(childConfig.parent === config)

All out-of-box supported sources are declared in DefaultLoaders, shown below (the corresponding config spec for these samples is ConfigForLoad):

Type Usage Provider Sample
HOCON config.from.hocon HoconProvider source.conf
JSON config.from.json JsonProvider source.json
properties config.from.properties PropertiesProvider source.properties
TOML config.from.toml TomlProvider source.toml
XML config.from.xml XmlProvider source.xml
YAML config.from.yaml YamlProvider source.yaml
JavaScript config.from.js JsProvider source.js
hierarchical map config.from.map.hierarchical - MapSourceLoadSpec
map in key-value format config.from.map.kv - KVSourceSpec
map in flat format config.from.map.flat - FlatSourceLoadSpec
system environment config.from.env() EnvProvider -
system properties config.from.systemProperties() PropertiesProvider -

These sources can also be manually created using their provider, and then loaded into config by config.withSource(source).

All from APIs also have their standalone version that return sources without loading them into the config, shown below:

Type Usage
HOCON Source.from.hocon
JSON Source.from.json
properties Source.from.properties
TOML Source.from.toml
XML Source.from.xml
YAML Source.from.yaml
JavaScript Source.from.js
hierarchical map Source.from.map.hierarchical
map in key-value format Source.from.map.kv
map in flat format Source.from.map.flat
system environment Source.from.env()
system properties Source.from.systemProperties()

Format of system properties source is same with that of properties source. System environment source follows the same mapping convention with properties source, but with the following name convention:

  • All letters in name are in uppercase
  • . in name is replaced with _

HOCON/JSON/properties/TOML/XML/YAML/JavaScript source can be loaded from a variety of input format. Use properties source as example:

  • From file: config.from.properties.file("/path/to/file")
  • From watched file: config.from.properties.watchFile("/path/to/file", 100, TimeUnit.MILLISECONDS)
    • You can re-trigger the setup process every time the updated file is loaded by watchFile("/path/to/file") { config, source -> setup(config) }
  • From string: config.from.properties.string("server.port = 8080")
  • From URL: config.from.properties.url("http://localhost:8080/source.properties")
  • From watched URL: config.from.properties.watchUrl("http://localhost:8080/source.properties", 1, TimeUnit.MINUTES)
    • You can re-trigger the setup process every time the URL is loaded by watchUrl("http://localhost:8080/source.properties") { config, source -> setup(config) }
  • From Git repository: config.from.properties.git("https://github.com/uchuhimo/konf.git", "/path/to/source.properties", branch = "dev")
  • From watched Git repository: config.from.properties.watchGit("https://github.com/uchuhimo/konf.git", "/path/to/source.properties", period = 1, unit = TimeUnit.MINUTES)
    • You can re-trigger the setup process every time the Git file is loaded by watchGit("https://github.com/uchuhimo/konf.git", "/path/to/source.properties") { config, source -> setup(config) }
  • From resource: config.from.properties.resource("source.properties")
  • From reader: config.from.properties.reader(reader)
  • From input stream: config.from.properties.inputStream(inputStream)
  • From byte array: config.from.properties.bytes(bytes)
  • From portion of byte array: config.from.properties.bytes(bytes, 1, 12)

If source is from file, file extension can be auto detected. Thus, you can use config.from.file("/path/to/source.json") instead of config.from.json.file("/path/to/source.json"), or use config.from.watchFile("/path/to/source.json") instead of config.from.json.watchFile("/path/to/source.json"). Source from URL also support auto-detected extension (use config.from.url or config.from.watchUrl). The following file extensions can be supported:

Type Extension
HOCON conf
JSON json
Properties properties
TOML toml
XML xml
YAML yml, yaml
JavaScript js

You can also implement Source to customize your new source, which can be loaded into config by config.withSource(source).

Subscribe the update event for load operation

Subscribe the update event before every load operation:

val handler = config.beforeLoad { source -> println("$source will be loaded") }

You can re-trigger the setup process by subscribing the update event after every load operation:

val handler = config.afterLoad { source -> setup(config) }

Cancel the subscription:

handler.cancel()

Strict parsing when loading

By default, Konf extracts desired paths from sources and ignores other unknown paths in sources. If you want Konf to throws exception when unknown paths are found, you can enable FAIL_ON_UNKNOWN_PATH feature:

config.enable(Feature.FAIL_ON_UNKNOWN_PATH)
    .from.properties.file("server.properties")
    .from.json.resource("server.json")

Then config will validate paths from both the properties file and the JSON resource. Furthermore, If you want to validate the properties file only, you can use:

config.from.enable(Feature.FAIL_ON_UNKNOWN_PATH).properties.file("/path/to/file")
    .from.json.resource("server.json")

Path substitution

Path substitution is a feature that path references in source will be substituted by their values.

Path substitution rules are shown below:

  • Only quoted string value will be substituted. It means that Konf's path substitutions will not conflict with HOCON's substitutions.
  • The definition of a path variable is ${path}, e.g., ${java.version}.
  • The path variable is resolved in the context of the current source.
  • If the string value only contains the path variable, it will be replaced by the whole sub-tree in the path; otherwise, it will be replaced by the string value in the path.
  • Use ${path:-default} to provide a default value when the path is unresolved, e.g., ${java.version:-8}.
  • Use $${path} to escape the path variable, e.g., $${java.version} will be resolved to ${java.version} instead of the value in java.version.
  • Path substitution works in a recursive way, so nested path variables like ${jre-${java.specification.version}} are allowed.
  • Konf also supports all key prefix of StringSubstitutor's default interpolator.

By default, Konf will perform path substitution for every source (except system environment source) when loading them into the config. You can disable this behaviour by using config.disable(Feature.SUBSTITUTE_SOURCE_BEFORE_LOADED) for the config or source.disabled(Feature.SUBSTITUTE_SOURCE_BEFORE_LOADED) for a single source.

By default, Konf will throw exception when some path variables are unresolved. You can use source.substituted(false) manually to ignore these unresolved variables.

To resolve path variables refer to other sources, you can merge these sources before loading them into the config. For example, if we have two sources source1.json and source2.properties, source1.json is:

{ 
  "base" : { "user" : "konf" , "password" : "passwd" }
}

source2.properties is:

connection.jdbc=mysql://${base.user}:${base.password}@server:port

use:

config.withSource(
    Source.from.file("source1.json") +
    Source.from.file("source2.properties")
)

We can resolve mysql://${base.user}:${base.password}@server:port as mysql://konf:passwd@server:port.

Prefix/Merge operations for source/config/config spec

All of source/config/config spec support add prefix operation, remove prefix operation and merge operation as shown below:

Type Add Prefix Remove Prefix Merge
Source source.withPrefix(prefix) or Prefix(prefix) + source or config.from.prefixed(prefix).file(file) source[prefix] or config.from.scoped(prefix).file(file) fallback + facade or facade.withFallback(fallback)
Config config.withPrefix(prefix) or Prefix(prefix) + config config.at(prefix) fallback + facade or facade.withFallback(fallback)
Spec spec.withPrefix(prefix) or Prefix(prefix) + spec spec[prefix] fallback + facade or facade.withFallback(fallback)

Export/Reload values in config

Export all values in config as a tree:

val tree = config.toTree()

Export all values in config to map in key-value format:

val map = config.toMap()

Export all values in config to hierarchical map:

val map = config.toHierarchicalMap()

Export all values in config to map in flat format:

val map = config.toFlatMap()

Export all values in config to JSON:

val file = createTempFile(suffix = ".json")
config.toJson.toFile(file)

Reload values from JSON:

val newConfig = Config {
    addSpec(Server)
}.from.json.file(file)
check(config == newConfig)

Config can be saved to a variety of output format in HOCON/JSON/properties/TOML/XML/YAML/JavaScript. Use JSON as example:

  • To file: config.toJson.toFile("/path/to/file")
  • To string: config.toJson.toText()
  • To writer: config.toJson.toWriter(writer)
  • To output stream: config.toJson.toOutputStream(outputStream)
  • To byte array: config.toJson.toBytes()

You can also implement Writer to customize your new writer (see JsonWriter for how to integrate your writer with config).

Supported item types

Supported item types include:

  • All primitive types
  • All primitive array types
  • BigInteger
  • BigDecimal
  • String
  • Date and Time
    • java.util.Date
    • OffsetTime
    • OffsetDateTime
    • ZonedDateTime
    • LocalDate
    • LocalTime
    • LocalDateTime
    • Year
    • YearMonth
    • Instant
    • Duration
  • SizeInBytes
  • Enum
  • Array
  • Collection
    • List
    • Set
    • SortedSet
    • Map
    • SortedMap
  • Kotlin Built-in classes
    • Pair
    • Triple
    • IntRange
    • CharRange
    • LongRange
  • Data classes
  • POJOs supported by Jackson core modules

Konf supports size in bytes format described in HOCON document with class SizeInBytes.

Konf supports both ISO-8601 duration format and HOCON duration format for Duration.

Konf uses Jackson to support Kotlin Built-in classes, Data classes and POJOs. You can use config.mapper to access ObjectMapper instance used by config, and configure it to support more types from third-party Jackson modules. Default modules registered by Konf include:

Optional features

There are some optional features that you can enable/disable in the config scope or the source scope by Config#enable(Feature)/Config#disable(Feature) or Source#enabled(Feature)/Source#disable(Feature). You can use Config#isEnabled() or Source#isEnabled() to check whether a feature is enabled.

These features include:

  • FAIL_ON_UNKNOWN_PATH: feature that determines what happens when unknown paths appear in the source. If enabled, an exception is thrown when loading from the source to indicate it contains unknown paths. This feature is disabled by default.
  • LOAD_KEYS_CASE_INSENSITIVELY: feature that determines whether loading keys from sources case-insensitively. This feature is disabled by default except for system environment.
  • LOAD_KEYS_AS_LITTLE_CAMEL_CASE: feature that determines whether loading keys from sources as little camel case. This feature is enabled by default.
  • OPTIONAL_SOURCE_BY_DEFAULT: feature that determines whether sources are optional by default. This feature is disabled by default.
  • SUBSTITUTE_SOURCE_BEFORE_LOADED: feature that determines whether sources should be substituted before loaded into config. This feature is enabled by default.

Build from source

Build library with Gradle using the following command:

./gradlew clean assemble

Test library with Gradle using the following command:

./gradlew clean test

Since Gradle has excellent incremental build support, you can usually omit executing the clean task.

Install library in a local Maven repository for consumption in other projects via the following command:

./gradlew clean install

Breaking Changes

v0.19.0

Since all sources are substituted before loaded into config by default, all path variables will be substituted now. You can use config.disable(Feature.SUBSTITUTE_SOURCE_BEFORE_LOADED) to disable this change.

v0.17.0

After migrated to tree-based source APIs, many deprecated APIs are removed, including:

  • Source: all isXXX and toXXX APIs
  • Config: layer, addSource and withSourceFrom

v0.15

After modularized Konf, hocon/toml/xml/yaml/git/watchGit in DefaultLoaders become extension properties/functions and should be imported explicitly. For example, you should import com.uchuhimo.konf.source.hocon before using config.from.hocon; in Java, config.from().hocon is unavailable, please use config.from().source(HoconProvider.INSTANCE) instead.

If you use JitPack, you should use com.github.uchuhimo.konf:konf:<version> instead of com.github.uchuhimo:konf:<version> now.

v0.10

APIs in ConfigSpec have been updated to support item name's auto-detection, please migrate to new APIs. Here are some examples:

  • val host = optional("host", "0.0.0.0") to val host by optional("0.0.0.0")
  • val port = required<Int>("port") to val port by required<Int>()
  • val nextPort = lazy("nextPort") { config -> config[port] + 1 } to val nextPort by lazy { config -> config[port] + 1 }

License

© uchuhimo, 2017-2019. Licensed under an Apache 2.0 license.

Comments
  • How to write new values to config file?

    How to write new values to config file?

    Is there any way to save the changed values to the same file from which the values were loaded, so when the program is run a second time then the new values are loaded?

    opened by SackCastellon 11
  • Way to Register Change Listener?

    Way to Register Change Listener?

    Is there a way to call a callback when a value changes? We can setup watchFile to look for changes which is really nifty, but is there any way to be alerted when a property has changed, or even just be alerted when the file is changed?

    Since the values in a property file are often used for longer lived classes we often need to take action when a property has changed. Specifically in my case, our DB usernames and passwords rotate, and i need to update my connection pool whenever this happens.

    opened by MrThreepwood 10
  • [YAML] Path resolution failing if a key is numeric

    [YAML] Path resolution failing if a key is numeric

    I apologise if this is intended and documented somewhere, I couldn't find it mentioned anywhere. Consider this yaml config file:

    tree:
      1:
        myVal: true
    

    This will fail to resolve properly (i.e. config.at("tree.1.myVal").toValue<Boolean>())). However, if that 1 is renamed to a1, everything works properly.

    enhancement 
    opened by dkambersky 8
  • Modularize project to minimize unnecessary transitive dependencies

    Modularize project to minimize unnecessary transitive dependencies

    I currently think the amount of supported formats and dependencies is excessive for most projects. I think we should move the JSON, XML, YAML, HOCON, and TOML providers into separate modules, and let users opt-in to use these different providers. Furthermore I also think it would be a good idea to move the functionality for loading from git repositories into a different module. The primary motivation for this is to reduced amount of dependencies on the core library.

    What I propose is to have 7 different modules in total;

    • konf-core
    • konf-json
    • konf-xml
    • konf-yaml
    • konf-hocon
    • konf-toml
    • konf-jgit

    I've forked the project and started making progress on this in a separate branch. I intend the API to stay mostly unchanged, however it's necessary to remove some fields and replace them with extension properties (DefaultLoaders specifically). The README and documentation must be updated to reflect these few changes, and add that you must manually add providers as a dependency, and register them to the Provider companion object.

    enhancement 
    opened by feature 8
  • Concurrent access from multiple processes

    Concurrent access from multiple processes

    I'm trying to create a configuration system that can be read or modified from multiple processes. It appears this may be possible by locking the files during writes. Can you give me any guidance on this?

    My plan was to do something like:

    val config = Config { addSpec(SomeSpec) }.from.hocon.watchFile("preferences.conf")
    doSomeStuff()
    // first, lock file
    preferencesFile.inputStream().channel.lock().use {
        // then apply the new update
        updates(config)
    
        // Then save the changes to preference
        config.toHocon.toFile(preferencesFile)
    }
    

    Does that look reasonable? If one client is writing the update, and a second client tries, would it apply the its update without reading the update from the first client and so clobber the first update? I think it should be something like:

    // first, lock file
    preferencesFile.inputStream().channel.lock().use {
        // refresh config from file here, but I have no idea how to do that
    
        // then apply the new update
        updates(config)
    
        // Then save the changes to preference
        config.toHocon.toFile(preferencesFile)
    }
    
    opened by sproctor 7
  • No support for having no prefix?

    No support for having no prefix?

    Hello,

    Say I have the following set of existing environment variables within my infrastructure:

    DATABASE=foo.example.com
    DATABASE_USER=john
    DATABASE_PASS=doe
    

    I want to, without modifying them, be able to resolve these variables from both YAML file and environment variables, with latter having precedence.

    It seems that such naming is discouraged by konf.

    I tried the following:

    object Root : ConfigSpec("") {
        val DATABASE by required<String>()
        val DATABASE_USER by required<String>()
        val DATABASE_PASS by required<String>()
    }
    
    Config {
        addSpec(Root)
    }
            .from.yaml.resource("server.yml")
            .from.env()
            .from.systemProperties()
    

    With YAML file having similar structure to the above:

    DATABASE: foo.example.com
    DATABASE_USER: john
    DATABASE_PASS: doe
    

    Sadly, the moment DATABASE_PASS or similar variable appears in the env, I get the following exception:

    Exception in thread "main" com.uchuhimo.konf.source.LoadException: fail to load DATABASE
    	at com.uchuhimo.konf.source.SourceKt.loadItem(Source.kt:519)
    	at com.uchuhimo.konf.source.SourceKt$load$1.invoke(Source.kt:532)
    	at com.uchuhimo.konf.source.SourceKt$load$1.invoke(Source.kt)
    	at com.uchuhimo.konf.BaseConfig.lock(BaseConfig.kt:65)
    	at com.uchuhimo.konf.source.SourceKt.load(Source.kt:530)
    	at com.uchuhimo.konf.BaseConfig.withSource(BaseConfig.kt:585)
    	at com.uchuhimo.konf.source.DefaultLoaders.env(DefaultLoaders.kt:118)
    	at example.server.ServerSpecKt.getApplicationConfig(ServerSpec.kt:41)
    	at example.StarterAppKt.initializeApplicationContext(StarterApp.kt:43)
    	at example.StarterAppKt.main(StarterApp.kt:68)
    	at example.StarterAppKt.main(StarterApp.kt)
    Caused by: com.uchuhimo.konf.source.ObjectMappingException: unable to map source com.uchuhimo.konf.ContainerNode@33c2bd in [type: system-environment] to value of type String
    	at com.uchuhimo.konf.source.SourceKt.toValue(Source.kt:820)
    	at com.uchuhimo.konf.source.SourceKt.loadItem(Source.kt:512)
    	... 10 more
    Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
     at [Source: UNKNOWN; line: -1, column: -1]
    	at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
    	at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1343)
    	at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1139)
    	at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1093)
    	at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:63)
    	at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:10)
    	at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:3985)
    	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2343)
    	at com.uchuhimo.konf.source.SourceKt.toValue(Source.kt:815)
    	... 11 more
    

    Is there a mode to rely on simple naming without inferred nesting?

    enhancement help wanted 
    opened by golx 7
  • Required fields not required?

    Required fields not required?

    I expect this to throw an exception when executed because I don't have either value defined in my Environment:

    object DatabaseSpec : ConfigSpec(){
        val connectionString by required<String>()
        val driver by required<String>()
    }
    
    val config =    Config{
                        addSpec(DatabaseSpec)
                    }
                        .from.env()
    

    But it does not throw an exception. Am I misunderstanding what "required" means?

    opened by emacdona 6
  • Nested collection of objects

    Nested collection of objects

    Newbie to Kotlin and Konf here. Is there an easier way to achieve mapping the following yaml to the Spec below:

    Or does the solution involve using the jackson mapper with config.mapper? Thank you !

    datasets:
      hive:
        - key: transactions
          uri: /user/somepath
          format: parquet
          database: transations_daily
          table: transx
    
        - key: second_transactions
          uri: /seconduser/somepath
          format: avro
          database: transations_monthly
          table: avro_table
      file:
        - key: users
          uri: s3://filestore
          format: parquet
          mode: overwrite
    
    object DataSetsSpec: ConfigSpec("datasets"){
        val fileDataSets by optional<List<FileDS>>(default = emptyList())
        val hive by optional<List<HiveDS>>(default = emptyList())
    }
    
    object FileDS: ConfigSpec ("file"){
        val key by required<String>(description = "Name of the dataset")
        val uri by required<String>(description = "Target path of the file with the protocol qualifier")
        val format by optional<String>("parquet", "File format", "Format in which the file must be writte")
        val mode by optional<String>("append", "Save Mode", "Mode in which the file would be written - (overwrite, append)")
    }
    
    object HiveDS: ConfigSpec ("hive"){
        val key by required<String>()
        val uri by required<String>()
        val database by required<String>()
        val table by required<String>()
        val format by optional<String>("parquet", "File format", "Format in which the file must be writte")
        val mode by optional<String>("append", "Save Mode", "Mode in which the file would be written - (overwrite, append)")
    }
    
    opened by arunma 6
  • Nested object properties

    Nested object properties

    Hey @uchuhimo thanks again for your great work here. It would be great to be able to specify required and optional nested object properties.

    At the moment, this is technically possible, but a bit awkward, requiring to use a Map<String, Any>, and then using that to create a Config, perhaps with one or more Specs.

    Ideally, the following should work:

    val outer = object : ConfigSpec() {
        
         val inner = object : ConfigSpec("address") {
                
                val host by required<String>()
                val port by required<Int>()
         }
         
         val settings by required<Config>().withSchema(inner)
    }
    
    val configuration = Config.invoke { addSpec(outer) }.from.map.kv(mapOf("settings.address.host" to "127.0.0.1", "settings.address.port" to 8080))
    
    val settings = configuration[outer.settings]
    val host = settings[outer.inner.host]
    

    Not sure if there's a better way of doing the above, perhaps by using multiple Specs.

    enhancement 
    opened by sollecitom 6
  • RFE: Load Config into child prefix.  Or reuse ConfigSpec at new prefix

    RFE: Load Config into child prefix. Or reuse ConfigSpec at new prefix

    I am trying to accomplish the following:

    In one source (or ideally in the same source, but for simplictiy, 1 source )

    base.properties:

    auth.user=username
    auth.password=password
    

    In another source I would like to reuse 'auth' as a default for other values.

    service.properties

    service1.url=http://myservice
    service2.url=http://myservice
    service2.auth.user=user1
    service2.auth.password=user2
    

    In Application

    
    data class Auth(
       val user="",
       val password=""
    )
    
    
    object ServiceSpec :  ConfigSpec() { 
           val url by required<String>()
           val auth by required<Auth>()
    }
    

    I would like to instantiate something like

    
    class Service( config: Config ) {
        val url by config.property( ServiceSpec.url )
        val auth by config.property( ServiceSpec.auth )
    }
    

    Then in 'pseudo code'

    
    val authConfig  = Config { addSpec( ? ) }.withSourceFrom.file("base.properties")
    val ServiceConfig = Config  { addSpec( ServiceSpec( ? )  } ?? 
    val services = Config( ServiceConfig{ ? } ).withSourceFrom("service.properties")
    
    val service1 = Service( ServiceConfig("service1" ).withValuesFrom(services) ?? .withDefaultsFrom( authConfig ) )
    val service2 = Service( ServiceConfig("service2") .withValuesFrom(services)  ?? .withDefaultsFrom(authConfig)) 
    
    

    Couple unresolved issues

    1. How to reuse a the same type of ConfigSpec at different prefix's ? ("service1" and "service1" are the same type at different prefix's
    2. How to inject defaults from a unrelated config -- into different prefix's

    For while I thought the FlatSource( prefix="xxx" ) might work, but it does not. The prefix's are all absolute. Unless I am missing something you cannot derive specs from other specs or load sources or values into any but the root prefix.

    This is a very similar but orthagonal feature that layers provide. Layers let you have defaults on the same prefix but different named layers.

    Is there currently a way, or is it conceviable to implement something like Config.loadSourceAt("prefix.nested").from.file("file") and/or ConfigSpec( parent: ConfigSpec , atPrefix: String )

    Essentially be able to define ConfigSpecs as reusable types that may occur at different places in a config hierarchy, AND be able to load Sources from the same or different locations into different prefix'ed roots.

    enhancement undocumented 
    opened by DALDEI 6
  • Extend LOAD_KEYS_CASE_INSENSITIVELY to support something like snake_case

    Extend LOAD_KEYS_CASE_INSENSITIVELY to support something like snake_case

    Please think about following example code.

    object ExampleSpec : ConfigSpec("") {
        private val exampleKey by required<String>()
        
        fun loadExampleKey(): String {
            val config = Config { addSpec(ExampleSpec) }
            	.enable(Feature.LOAD_KEYS_CASE_INSENSITIVELY)
                .withSource(
                	Source.from.json.file(File("config.json"))
                )
    		
            return config[exampleKey]
        }
    }
    

    With the code above, We can load:

    {
      "exampleKey": "value"
    }
    

    But we cannot load with error: Exception in thread "main" java.util.concurrent.ExecutionException: com.uchuhimo.konf.UnsetValueException: item indentStyle is unset

      "example_key": "value"
    

    It's very nice if both CamelCase and snake_case are supported, especially when we load config from multiple file type such as json and yaml.

    enhancement 0.23.0 
    opened by yukukotani 5
  • Should contains find partial path matches?

    Should contains find partial path matches?

    Sample Properties file

    prefix1.prefixA.name.keyA.key1 = value
    prefix1.prefixA.name.keyA.key2 = value
    prefix1.prefixA.name.keyB.key1 = value
    prefix1.prefixA.name.keyB.key2 = value
    prefix1.prefixB.name.keyA.key1 = value
    prefix1.prefixB.name.keyA.key2 = value
    prefix1.prefixB.name.keyB.key1 = value
    prefix1.prefixB.name.keyB.key2 = value
    

    If I use this such as via

    val conf = Config().from.properties.resource("config/application.defaults.properties")
    

    Should I be able to test if a given sub-path exists?

    conf.contains("prefix1.prefixB.name")
    

    As of right now, this always returns false for me.

    Per the Properties, children of the Path exist. I use the Spec as follows and successfully so, despite the contains returning false

    object SomeSpec: ConfigSpec("") {
      val keyA1 by optional("NOT_SET", "keyA.key1")
      val keyA2 by optional("NOT_SET", "keyA.key1")
      val keyB1 by optional("NOT_SET", "keyB.key2")
      val keyB2 by optional("NOT_SET", "keyB.key2")
    }
    
    val prefixb = conf.withLayer("child.prefixB").also {
      it.addSpec(SomeSpec.withPrefix("prefix1.prefixB.name"))
    }
    
    println("keyB2: ${prefixb[SomeSpex.keyB2]}")
    
    opened by ppslim 0
  • Using .at(path).toValue errors if any config files are missing the entry

    Using .at(path).toValue errors if any config files are missing the entry

    Test case here: https://github.com/dtanner/konf-question/blob/get_at_question/src/test/kotlin/question/ConfigTest.kt#L32

    I'd like to avoid using ConfigSpec classes, and instead use the toValue() function to instantiate configuration directly into target classes. For the case where I want to grab a single property, this works fine if the config files I'm loading from all contain all the property I'm looking for, but if any of them don't, it errors. e.g. relevant snippets from the linked test case:

    config1.conf:

    server {
      host = "0.0.0.0"
    }
    

    config2-missing-host.conf:

    server {
      # if i uncomment the host here, things work as expected.
      # host = "1.1.1.1"
    }
    

    test:

    package question
    
    import com.uchuhimo.konf.Config
    import com.uchuhimo.konf.source.hocon
    import com.uchuhimo.konf.toValue
    import io.kotest.matchers.shouldBe
    import org.junit.jupiter.api.Test
    
    class ConfigTest {
    
        data class ServerConfig(val host: String)
    
        @Test
        fun `foo hocon`() {
            val config = Config()
                .from.hocon.resource("config1.conf")
                .from.hocon.resource("config2-missing-host.conf", optional = true)
    
            val serverConfig = config.at("server").toValue<ServerConfig>()
            serverConfig.host shouldBe "0.0.0.0"
    
            /*
            The following line throws:
    
            item root is unset
            com.uchuhimo.konf.UnsetValueException: item root is unset
            at app//com.uchuhimo.konf.BaseConfig.getOrNull(BaseConfig.kt:187)
            at app//com.uchuhimo.konf.BaseConfig.getOrNull$default(BaseConfig.kt:179)
            at app//com.uchuhimo.konf.BaseConfig.get(BaseConfig.kt:159)
            at app//com.uchuhimo.konf.BaseConfig$property$1.getValue(BaseConfig.kt:527)
             */
            val host = config.at("server.host").toValue<String>()
            host shouldBe "0.0.0.0"
        }
    
    }
    

    Is there a different/better way to get at individual properties and objects without using Spec files? Or is this reasonable to work as expected? Thanks.

    opened by dtanner 1
  • Update dependencies

    Update dependencies

    Currently, konf uses kotlin 1.4.32. The latest kotlin version is 1.6.10. Could this be updated for that?

    Some other libs may be also out of date, but I cannot check right now as I'm on mobile.

    I'll PR changes for dependency updates, if it has not been done by the time I get home

    opened by solonovamax 0
  • Create custom provider?

    Create custom provider?

    Hey there!

    Is there any way to follow to create a custom provider? Lets say creating a database provider for my use case. If there's is there any guide or tutorial for that?

    Thanks in advance

    stale 
    opened by misterquestions 1
  • add support for Maps with enum keys

    add support for Maps with enum keys

    It'd be great to allow something like this:

    object MyConfig : ConfigSpec() {
        val foo by required<Map<MyEnum, String>>()
    }
    
    enum class MyEnum {
        FOO,
        BAR,
    }
    
    myConfig:
      FOO: hey
      BAR: ho
    
    stale 
    opened by Leopard2A5 1
  • Null values are improperly serialized

    Null values are improperly serialized

    Data classes with null property values are serialized with those values as a string literal with a value of "null" instead a null literal. Jackson then attempts to de-serialize the string literal to the type of the property.

    Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `CustomChildType` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('null')
    
    stale 
    opened by sproctor 4
Releases(v1.1.2)
  • v1.1.2(Apr 8, 2021)

  • v1.1.1(Apr 8, 2021)

    Major Features and Improvements

    • feature: support listener for watched file/URL
    • feature: support listener for watched Git file
    • feature: support beforeSet/afterSet for config
    • feature: support beforeSet/afterSet for item
    • feature: support beforeLoad/afterLoad for config

    Breaking Changes

    • remove action parameter for Git source

    Deprecated

    • build: remove bintray support
    Source code(tar.gz)
    Source code(zip)
  • v1.0.0(Jan 24, 2021)

  • v0.23.0(Oct 12, 2020)

    Major Features and Improvements

    • feature: support to load keys as little camel case by default
    • feature: support empty YAML/JSON files
    • feature: support to interpolate Byte/Short/Int/Long/BigInteger/Char
    • feature: support to merge sources with different features
    • feature: support to aggregate sources from different layers
    • fix: support for non-map based StdSerializer
    • fix: discover all official providers automatically

    Breaking Changes

    • All keys in sources are loaded as little camel case by default, e.g., tcpPort and tcp_port in the source are treated as the same key. You can use config.disable(Feature.LOAD_KEYS_AS_LITTLE_CAMEL_CASE) to disable this change.

    Deprecated

    • fromXXX APIs on providers are removed use the new shorter APIs instead. E.g., you can use file instead of fromFile.
    Source code(tar.gz)
    Source code(zip)
  • v0.22.0(Dec 22, 2019)

    • feature: support flatten env source (#41)

      If you have the following environment variables:

      DATABASE=foo.example.com
      DATABASE_USER=john
      DATABASE_PASS=doe
      

      You can load them without any name conversion by using config.from.env(nested=false):

      object Root : ConfigSpec("") {
          val DATABASE by required<String>()
          val DATABASE_USER by required<String>()
          val DATABASE_PASS by required<String>()
      }
      
      Config {
          addSpec(Root)
      }.from.env(nested=false)
      
    • fix: support numeric keys in source

      Now you can use numeric keys in source (use YAML as example):

      tree:
        1:
          myVal: true
      
    Source code(tar.gz)
    Source code(zip)
  • v0.21.0(Nov 25, 2019)

    A serious configuration error in Gradle has been fixed in this version. This bug will cause compilation error when using konf without simultaneously declaring dependency to konf-core since v0.15. Please upgrade to this version if you are using v0.15~v0.20.0.

    Source code(tar.gz)
    Source code(zip)
  • v0.20.0(Sep 15, 2019)

    • feature: support to cast config to value with config.toValue<T>()
    • fix: watchFile throws exception when using relative path
    • fix: parsing of environment variables is stricter now
    Source code(tar.gz)
    Source code(zip)
  • v0.19.0(Sep 9, 2019)

    Major Features and Improvements

    • feature: support default providers API
    • feature: support substituted source
    • feature: all sources are substituted before loaded into config by default

    Breaking Changes

    • Since all sources are substituted before loaded into config by default, all path variables will be substituted now. You can use config.disable(Feature.SUBSTITUTE_SOURCE_BEFORE_LOADED) to disable this change.

    Deprecated

    • fromXXX APIs on providers are deprecated now, use the new shorter APIs instead. E.g., you can use file instead of fromFile.
    Source code(tar.gz)
    Source code(zip)
  • v0.18.0(Sep 7, 2019)

  • v0.17.1(Sep 5, 2019)

    • fix: Loader#watchFile doesn't work on macOS
    • build: update dependencies
    • build: support Github Actions
    • build: disable Aliyun Maven repository by default
    Source code(tar.gz)
    Source code(zip)
  • v0.17.0(Sep 3, 2019)

    Major Features and Improvements

    • feature: migrate to tree-based source APIs
    • feature: support merge operation for sources
    • feature: support withFallback API for config spec
    • fix: fix formatting bug in toml4j's writer when map is in nested list
    • fix: filter invalid variables in environment
    • refactor: unify info and context in Source API

    Breaking Changes

    After migrated to tree-based source APIs, many deprecated APIs are removed, including:

    • Source: all isXXX and toXXX APIs
    • Config: layer, addSource and withSourceFrom
    Source code(tar.gz)
    Source code(zip)
  • v0.16.0(Aug 26, 2019)

  • v0.15.1(Aug 23, 2019)

  • v0.15(Aug 20, 2019)

    Major Features and Improvements

    Konf is modularized, now you can use different modules for different sources without unnecessary dependencies.

    Breaking Changes

    After modularized Konf, hocon/toml/xml/yaml/git/watchGit in DefaultLoaders become extension properties/functions and should be imported explicitly. For example, you should import com.uchuhimo.konf.source.hocon before using config.from.hocon; in Java, config.from().hocon is unavailable, please use config.from().source(HoconProvider.INSTANCE) instead.

    Source code(tar.gz)
    Source code(zip)
  • v0.14.1(Aug 13, 2019)

    Major Features and Improvements

    • feature: support optional sources
    • feature: support empty list and single element list in flat source
    • feature: add config.containsRequired() (return true/false) and config.validateRequired() (throw exception instead) to check whether all values of required items exist in config or not
    • feature: leading or trailing whitespaces are allowed in item name / path
    • feature: support onSet for Item
    • fix: support HOCON substitutions
    • fix: default values in facade config now have lower priority than the values in fallback config
    Source code(tar.gz)
    Source code(zip)
  • v0.13(Jan 29, 2019)

    Major Features and Improvements

    • feature: add support for nested config spec
    • feature: infer config spec prefix automatically
    • fix: load keys from the system environment case-insensitively
    • feature: remove experimental corountine APIs
    Source code(tar.gz)
    Source code(zip)
  • v0.12(Jan 29, 2019)

    Major Features and Improvements

    • feature: add mapped API for DefaultLoaders
    • feature: If FAIL_ON_UNKNOWN_PATH enabled, an exception is thrown when loading from the source to indicate it contains unknown paths
    Source code(tar.gz)
    Source code(zip)
  • v0.11(Jul 22, 2018)

    Major Features and Improvements

    • feature: support user-defined extension in DefaultLoaders
    • feature: support prefix operations for Source/Config/ConfigSpec
    • feature: support ad-hoc config item
    • feature: support to cast config to a specified class
    • feature: support merge operations for Source/Config/ConfigSpec
    • feature: support to load source from git
    • feature: support nullable type in Config/Source/Writer
    • gradle: update dependencies
      • Kotlin: 1.2.30 -> 1.2.51
    Source code(tar.gz)
    Source code(zip)
  • v0.10(Mar 4, 2018)

    Major Features and Improvements

    • feature: support to automatically detect item name in ConfigSpec
    • feature: support auto-detected URL format
    • feature: support auto-detected watched URL format
    • feature: support to parse comma separated values in the flat source
    • feature: support export config as the comma-separated format for flat source writers
    • gradle: update dependencies
      • Kotlin: 1.2.21 -> 1.2.30

    Breaking Changes

    APIs in ConfigSpec have been updated to support item name's auto-detection, please migrate to new APIs. Here are some examples:

    • val host = optional("host", "0.0.0.0") to val host by optional("0.0.0.0")
    • val port = required<Int>("port") to val port by required<Int>()
    • val nextPort = lazy("nextPort") { config -> config[port] + 1 } to val nextPort by lazy { config -> config[port] + 1 }

    Thanks to our Contributors

    Thanks to our contributors whose commits are featured in this release:

    @DALDEI

    Source code(tar.gz)
    Source code(zip)
  • v0.9(Feb 14, 2018)

    • feature: support to watch file/URL
    • feature: support auto-detected watched file format
    • doc: add documents for watchFile/watchUrl API
    • feature: initial support for Java 9
    • gradle: update dependencies
    Source code(tar.gz)
    Source code(zip)
  • v0.8(Feb 13, 2018)

    • feature: support Writer API
    • feature: support HOCON/JSON/properties/TOML/XML/YAML writer
    • doc: add documents for Writer API
    • gradle: update dependencies
      • Kotlin: 1.2 -> 1.2.21
    Source code(tar.gz)
    Source code(zip)
  • v0.7(Dec 8, 2017)

    • feature: support to export/reload config
    • feature: support equals/hashCode/toString in Config
    • gradle: update dependencies
      • Kotlin: 1.1.51 -> 1.2
    • doc: add document for config export/persistence
    • refactor: refactor/enrich examples
    Source code(tar.gz)
    Source code(zip)
  • v0.6(Dec 8, 2017)

  • v0.5(Aug 9, 2017)

    • fix: enable tests shadowed by group with same name
    • fix: inline unsupported to cover code of Source
    • feature: add examples
    • doc: add document for most classes
    • doc: add usage doc in README
    • refactor: rename load operation in config
    • refactor: extract interface ItemContainer
    • refactor: simplify JSR310 deserializers's implementation
    Source code(tar.gz)
    Source code(zip)
  • v0.4(Aug 5, 2017)

    • feature: support contains/get operation in Source
    • fix: generate overload constructor for Item
    • test: code coverage over 90%
    • gradle: update dependencies
      • com.uchuhimo:kotlinx-bimap: 0.3.1 -> 1.0
      • com.typesafe:config: 1.2.1 -> 1.3.1
      • com.fasterxml.jackson.core:jackson-databind: 2.8.9 -> 2.9.0
    • doc: support dokka
    • doc: add README
    • travis: support Codecov
    Source code(tar.gz)
    Source code(zip)
  • v0.3(Jul 12, 2017)

    • eliminate compilation warning
    • update Kotlin from 1.1.3 to 1.1.3-2
    • replace subject of loader/provider test from HOCON to properties
    • support build scan
    • support install to local maven
    • update uchuhimo/kotlinx-bimap from v0.2 to v0.3.1
    Source code(tar.gz)
    Source code(zip)
  • v0.2(Jul 11, 2017)

  • v0.1(Jul 10, 2017)

Owner
null
An application to convert strings to diffirent formats

Localized An application to convert strings to diffirent formats This app will help developers to convert strings from application in one platform to

null 16 Feb 12, 2021
CSV and FixedLength Formats for kotlinx-serialization

Module kotlinx-serialization-csv Serialize and deserialize ordered CSV and Fixed Length Format Files with kotlinx-serialization. Source code Docs Inst

Philip Wedemann 12 Dec 16, 2022
LiveData 数据倒灌:别问,问就是不可预期 - Perfect alternative to SingleLiveEvent, supporting multiple observers.

前言 大家好,我是《Jetpack MVVM Best Practice》作者 KunMinX。 今天提到的 “数据倒灌” 一词,缘于我为了方便理解和记忆 “页面在 ‘二进宫’ 时收到旧数据推送” 的情况,而在 2019 年 自创并在网上传播的 关于此类现象的概括。 它主要发生在:通过 Shared

KunMinX 924 Jan 5, 2023
Modern Calendar View Supporting Both Hijri and Gregorian Calendars but in highly dynamic way

KCalendar-View Modern calendar view supporting both Hijri and Gregorian calendar

Ahmed Ibrahim 8 Oct 29, 2022
The most complete and powerful data-binding library and persistence infra for Kotlin 1.3, Android & Splitties Views DSL, JavaFX & TornadoFX, JSON, JDBC & SQLite, SharedPreferences.

Lychee (ex. reactive-properties) Lychee is a library to rule all the data. ToC Approach to declaring data Properties Other data-binding libraries Prop

Mike 112 Dec 9, 2022
It is far easier to design a class to be thread-safe than to retrofit it for thread safety later

"It is far easier to design a class to be thread-safe than to retrofit it for thread safety later." (Brian Goetz - Java concurrency: Publisher: Addiso

Nguyễn Trường Thịnh 3 Oct 26, 2022
A Lightweight PDF Viewer Android library which only occupies around 125kb while most of the Pdf viewer occupies up to 16MB space.

Pdf Viewer For Android A Simple PDF Viewer library which only occupies around 125kb while most of the Pdf viewer occupies upto 16MB space. How to inte

Rajat 362 Dec 29, 2022
Most used extension methods for Kotlin

Extensify Most used extension methods for Kotlin Download Step 1. Add the JitPack repository to your build file allprojects { repositories {

Mobven 36 Aug 25, 2022
The most essential libraries for Kotlin Multiplatform development

Essenty The most essential libraries for Kotlin Multiplatform development. Supported targets: android jvm js (IR and LEGACY) iosArm64, iosX64 watchosA

Arkadii Ivanov 218 Jan 3, 2023
This lib implements the most common CoroutineScopes used in Android apps.

AndroidCoroutineScopes Starting from 0.26.0 release we should define a scope for new coroutines (docs). To avoid this boilerplate code I've created th

Adriel Café 15 Oct 3, 2022
An android lib that provides most commonly used utility function

Awesome-Utility An android lib that provides most commonly used utility function. It can help Android developers with supporting common utility functi

Ameer Hamza 2 Apr 5, 2022
A pluggable sealed API result type for modeling Retrofit responses.

A pluggable sealed API result type for modeling Retrofit responses.

Slack 645 Dec 19, 2022
Basic app to use different type of observables StateFlow, Flow, SharedFlow, LiveData, State, Channel...

stateflow-flow-sharedflow-livedata Basic app to use different type of observables StateFlow, Flow, SharedFlow, LiveData, State, Channel... StateFlow,

Raheem 5 Dec 21, 2022
Curations and configuration files for the OSS Review Toolkit.

ORT Config This repository contains configuration files for the OSS Review Toolkit. Content Curations The curations directory contains package curatio

OSS Review Toolkit 9 Dec 8, 2022
Android Spinner Dialog Library supported on both Java and Kotlin, Use for single or multi selection of choice

SpinnerDialog Android Spinner Dialog Library, Use for single or multi selection of choice Android UI Download To include SpinnerDialog in your project

Hamza Khan 55 Sep 15, 2022
Sample Code for fake Kotlin library written in Java

Jatlin このリポジトリは ブログ記事 のためのサンプルコードです。詳細は記事をご覧ください。 プロジェクト構成 :java-lib にKotlinに偽装したJavaファイルが含まれます。 :kotlin-lib は :java-lib をビルドしたJARファイルをKotlinから読み込んで実行

Takaki Hoshikawa 3 Dec 10, 2021
A library for building Java only Zygisk/Riru modules.

A library for building Java only Zygisk/Riru modules.

Kr328 19 Dec 17, 2022
Cross-platform framework for building truly native mobile apps with Java or Kotlin. Write Once Run Anywhere support for iOS, Android, Desktop & Web.

Codename One - Cross Platform Native Apps with Java or Kotlin Codename One is a mobile first cross platform environment for Java and Kotlin developers

Codename One 1.4k Jan 9, 2023
StaticLog - super lightweight static logging for Kotlin, Java and Android

StaticLog StaticLog is a super lightweight logging library implemented in pure Kotlin (https://kotlinlang.org). It is designed to be used in Kotlin, J

Julian Pfeifer 28 Oct 3, 2022