{"id":23828543,"url":"https://github.com/miha-x64/lychee","last_synced_at":"2025-10-25T02:43:15.977Z","repository":{"id":57743069,"uuid":"102399925","full_name":"Miha-x64/Lychee","owner":"Miha-x64","description":"The most complete and powerful data-binding library and persistence infra for Kotlin 1.5, Android \u0026 Splitties Views DSL, JavaFX \u0026 TornadoFX, JSON, JDBC \u0026 SQLite,  HTTP, SharedPreferences.","archived":false,"fork":false,"pushed_at":"2023-05-11T13:39:35.000Z","size":2729,"stargazers_count":119,"open_issues_count":11,"forks_count":10,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-01-02T13:17:46.182Z","etag":null,"topics":["data-binding","hacktoberfest","javafx","jdbc","json","kotlin","mapping","persistence","properties","reactive","sqlite","subjects"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Miha-x64.png","metadata":{"files":{"readme":"README.md","changelog":null,"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}},"created_at":"2017-09-04T20:30:21.000Z","updated_at":"2024-05-21T03:07:23.000Z","dependencies_parsed_at":"2024-01-18T23:04:48.742Z","dependency_job_id":"15c526ec-ff26-4835-986c-ba1958dfa633","html_url":"https://github.com/Miha-x64/Lychee","commit_stats":null,"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Miha-x64%2FLychee","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Miha-x64%2FLychee/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Miha-x64%2FLychee/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Miha-x64%2FLychee/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Miha-x64","download_url":"https://codeload.github.com/Miha-x64/Lychee/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":232199951,"owners_count":18487528,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["data-binding","hacktoberfest","javafx","jdbc","json","kotlin","mapping","persistence","properties","reactive","sqlite","subjects"],"created_at":"2025-01-02T13:17:49.749Z","updated_at":"2025-10-25T02:43:15.903Z","avatar_url":"https://github.com/Miha-x64.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/Miha-x64/Lychee.svg?branch=master)](https://travis-ci.org/Miha-x64/Lychee)\n![Extremely lightweight](https://img.shields.io/badge/🦋-Extremely%20Lightweight-7799cc.svg)\n[![Hits-of-Code](https://hitsofcode.com/github/Miha-x64/Lychee)](https://hitsofcode.com/view/github/Miha-x64/Lychee)\n[![Kotlin 1.5](https://img.shields.io/badge/kotlin-1.5-blue.svg)](http://kotlinlang.org)\n[![Awesome Kotlin](https://kotlin.link/awesome-kotlin.svg)](https://kotlin.link)\n[![Channel at Kotlin Slack](https://img.shields.io/static/v1?label=kotlinlang\u0026message=Lychee\u0026color=brightgreen\u0026logo=slack)](https://app.slack.com/client/T09229ZC6/CPVLZ7LBT)\n[![Telegram chat](https://img.shields.io/static/v1?label=chat\u0026message=Lychee\u0026color=brightgreen\u0026logo=telegram)](https://t.me/kotlin_lychee)\n\n\u003c!-- abandoned [![Codacy Badge](https://api.codacy.com/project/badge/Grade/89813e3ee28441b3937a76f09e906aef)](https://www.codacy.com/app/Miha-x64/Lychee?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=Miha-x64/Lychee\u0026amp;utm_campaign=Badge_Grade) --\u003e\n\u003c!-- abandoned [![codecov](https://codecov.io/gh/Miha-x64/Lychee/branch/master/graph/badge.svg)](https://codecov.io/gh/Miha-x64/Lychee) in module `:properties`, excluding inline functions --\u003e\n\n# Lychee (ex. reactive-properties)\n\nLychee is a library to rule all the data.\n\n### ToC\n* [Approach to declaring data](#approach-to-declaring-data)\n* [Properties](#properties)\n* [Other data-binding libraries](#other-data-binding-libraries)\n* [Properties sample](#properties-sample)\n* [Sample usage in GUI application](#sample-usage-in-gui-application)\n* [Persistence and Android](#persistence-and-android)\n* [SQL](#sql-experimental) \u003c!-- TODO other SQL libraries --\u003e\n* [HTTP](#http)\n* [FAQ](#faq)\n* [Adding to a project](#adding-to-a-project)\n\n### Approach to declaring data\n\nTypically, we declare data using classes:\n```kt\n/*data*/ class Player(\n    val name: String,\n    val surname: String,\n    var score: Int,\n)\n```\nBut there are some mistakes in the example above:\n* there aren't any convenient ways to manipulate the properties of arbitrary classes:\n  * reflection does not play well with ProGuard/R8 and Graal,\n  * kapt is slow and does not play well with separate compilation,\n  * both break encapsulation by exposing field names (or values of annotations) as serialization interface,\n  * none of them knows precisely how to serialize values, they just try to guess according to field types,\n  * there are no standard annotations:\n    every JSON library has its own annotations (Gson: `@SerializedName` and `@TypeAdapter`),\n    every ORM, ActiveRecord, or another database-related thing has its own, too,\n  * `TypeAdapter` concept is cursed:\n    every library tries to support all standard types\n    (how often do you need to store `AtomicIntegerArray`? Gson has built-in support for it, this is crazy!),\n    and tree shakers (a.k.a. dead code eliminators) cannot figure out which ones are actually used\n    (this requires deep understanding of reflection API and `Map\u003cType, TypeAdapter\u003e` machinery,\n    10+ years of ProGuard were not enough to get this deep);\n* along with interface (property names and types),\n  this also declares implementation details (backing fields).\n  Thus, you're getting only in-memory representation.\n  (To workaround the issue, Realm, for example,\n  extends your classes so getters\u0026setters are overridden while fields are unused,\n  and rewrites your bare field accesses, if any, to use getters\u0026setters.)\n  Theoretically, this can be fixed by extracting `interface`:\n    ```kt\n    interface Player {\n        val name: String\n        val surname: String\n        var score: Int\n     // fun copy()? can we also ask to implement equals()? no.\n    }\n    data class MemoryPlayer(override val …) : Player\n    class JsonPlayer(private val json: JsonObject) : Player {\n        override val name: String get() = json.getString(\"name\")\n        …\n    }\n    class SqlPlayer(private val connection: Connection) : Player {\n        override val name: String get() = connection.createStatement()…\n    }\n    ```\n\n   but implementations are 146% boilerplate;\n* no mutability control. `var score` is mutable but not observable;\n* `hashCode`, `equals`, and `toString` contain generated bytecode fully consisting of boilerplate;\n* data class `copy` not only consists of boilerplate\n  but also becomes binary incompatible after every primary constructor change;\n* data class `componentN`s are pure evil in 99% cases:\n  destructuring is good with positional things like `Pair` or `Triple` but not with named properties.\n\n`:persistence` module provides the solution. Interface is declared by inheriting `Schema`:\n```kt\nobject Player : Schema\u003cPlayer\u003e() {\n    val Name = \"name\" let string\n    val Surname = \"surname\" let string\n    val Score = \"score\".mut(i32, default = 0)\n}\n```\nHere, `Player`, `string`, and `i32` (not `int` because it's Java keyword) are all subtypes of `DataType`.\nThus, they declare how to store data both in-memory and on wire.\n`Name`, `Surname`, and `Score` are field definitions, two immutable and one mutable,\nbased on [typed key](https://matklad.github.io/2018/05/24/typed-key-pattern.html) pattern.\n\nImplementations are subtypes of `Struct\u003cSCHEMA\u003e`,\nso they implement storage machinery while staying decoupled\nfrom data schema:\n```kt\nval player: StructSnapshot\u003cPlayer\u003e = Player { p -\u003e\n    p[Name] = \"John\"\n    p[Surname] = \"Galt\"\n    // Score gets its default value.\n}\n```\n`StructSnapshot` is immutable (and very cheap: it is an Array, not a HashMap) implementation. It can only be read from:\n```kt\nassertEquals(0, player[Player.Score])\n```\nHere, `Player {}` is `SCHEMA.invoke(build: SCHEMA.() -\u003e Unit)` function which tries to mimic struct literal;\n`p` is `StructBuilder\u003cSCHEMA\u003e`–a fully mutable temporary object.\n`Struct`s implement `hashCode`, `equals`, `toString`, and `copy` of this kind: `player.copy { it[Score] = 9000 }`.\nIt creates new `StructBuilder` and passes it to the function you provide.\n(Similar thing is called `newBuilder` in OkHttp, and `buildUpon` in `android.net.Uri`.)\n\nThere's also a good practice to implement a constructor function\nwhich gives less chance of forgetting to specify required field values:\n```kt\nfun Player(name: String, surname: String) = Player { p -\u003e\n    p[Name] = name\n    p[Surname] = surname\n}\n```\n\n## Properties\n\nProperties (subjects, observables) inspired by [JavaFX](https://wiki.openjdk.java.net/display/OpenJFX/Main)\nand [Vue.js](https://vuejs.org/) MVVM-like approach are available in `:properties` module.\nA `Property` provides functionality similar to\n`BehaviorSubject` in RxJava, or `Property` in JavaFX,\nor `LiveData` in Android Arch.\n\n* Simple and easy-to-use\n* Lightweight: persistence + properties + android-bindings define around 1500 methods\n  including easy-to-shrink `inline fun`s and `value class`es\n* zero reflection \u003csmall\u003e([the only use of kotlin.reflect](https://github.com/Miha-x64/Lychee/blob/ccea2e165f0da5dbaedac2d3562c4a843614241f/properties/src/main/kotlin/net/aquadc/properties/operatorsInline.kt#L168-L179) is required if you delegate your Kotlin property to a Lychee `Property` and [eliminated by Kotlin 1.3.70+ compiler](https://youtrack.jetbrains.com/issue/KT-14513))\u003c/small\u003e\n* Extensible: not confined to Android, JavaFX or whatever (want MPP? File an issue with sample use-cases)\n* Single-threaded and concurrent (lock-free) implementations\n* Ready to use Android bindings like `tv.bindTextTo(prop)`, not `ld.observe(viewLifecycleOwner) { tv.text = it }`\n* Some bindings for JavaFX\n* Sweet with View DSLs like\n  [Splitties](https://github.com/LouisCAD/Splitties/) \n  and [TornadoFX](https://github.com/edvin/tornadofx)\n* Depends only on Kotlin-stdlib and\n  [Kotlin-MPP Collection utils](https://github.com/Miha-x64/Kotlin-MPP_Collection_utils) for overheadless `EnumSet`s\n* [Presentation](https://speakerdeck.com/gdg_rnd/mikhail-goriunov-advanced-kotlin-patterns-on-android-properties)\n  about properties: initial problem statement and some explanations\n\nWith `:persistence` + `:properties`, it's also possible to observe mutable fields:\n```kt\nval observablePlayer = ObservableStruct(player)\nval scoreProp: Property\u003cInt\u003e = observablePlayer prop Player.Score\nsomeTextView.bindTextTo(scoreProp.map(CharSequencez.ValueOf)) // bind to UI, for example\n\n// both mutate the same text in-memory int value and a text field:\nscoreProp.value = 10\nobservablePlayer[Player.Score] = 20\n```\n\n\n## Other data-binding libraries\nto explain why I've rolled my own:\n\n* [agrosner/KBinding](https://github.com/agrosner/KBinding) (MIT): similar to this,\n  Observable-based, Android-only, depends on kotlinx.coroutines\n\n* [BennyWang/KBinding](https://github.com/BennyWang/KBinding) (no license):\n   Android-only, uses annotation processing, depends on RXJava 1.3\n\n* [LewisRhine/AnkoDataBindingTest](https://github.com/LewisRhine/AnkoDataBindingTest) (no license):\n  proof of concept solution from [Data binding in Anko](https://medium.com/lewisrhine/data-binding-in-anko-77cd11408cf9)\n  article, Android-only, depends on Anko and AppCompat\n\n* [lightningkite/kotlin-anko-observable](https://github.com/lightningkite/kotlin-anko-observable) (no license):\n  Android-only,\n  supports easy creation of RecyclerView adapters along with data-binding,\n  based on [lightningkite/kotlin-anko](https://github.com/lightningkite/kotlin-anko) (depends on Anko and AppCompat)\n  and [lightningkite/kotlin-observable](https://github.com/lightningkite/kotlin-observable)\n  (`ObservableProperty\u003cT\u003e` and `ObservableList\u003cT\u003e`);\n  [UnknownJoe796/kotlin-components-starter](https://github.com/UnknownJoe796/kotlin-components-starter) (MIT)\n\n* [MarcinMoskala/KotlinAndroidViewBindings](https://github.com/MarcinMoskala/KotlinAndroidViewBindings) (Apache 2.0):\n  delegates properties of Views-by-id to to Kotlin properties\n\n## Properties sample\n\n```kt\nval prop: MutableProperty\u003cInt\u003e = propertyOf(1)\nval mapped: Property\u003cInt\u003e = prop.map { 10 * it }\nassertEquals(10, mapped.value)\n\nprop.value = 5\nassertEquals(50, mapped.value)\n\n\nval tru = propertyOf(true)\nval fals = !tru // operator overloading\nassertEquals(false, fals.value)\n```\n\n## Sample usage in GUI application\n\nAndroid layout ([Splitties Views DSL](https://github.com/LouisCAD/Splitties/tree/master/modules/views-dsl)):\n\n```kt\nsetContentView(verticalLayout {\n    padding = dip(16)\n\n    addView(editText {\n        id = 1 // let view save its state, focus, etc\n        hint = \"Email\"\n        bindTextBidirectionally(vm.emailProp)\n        bindErrorMessageTo(vm.emailValidProp.map { if (it) null else \"E-mail is invalid\" })\n    })\n\n    addView(editText {\n        id = 2\n        hint = \"Name\"\n        bindTextBidirectionally(vm.nameProp)\n    })\n\n    addView(editText {\n        id = 3\n        hint = \"Surname\"\n        bindTextBidirectionally(vm.surnameProp)\n    })\n\n    addView(button {\n        bindEnabledTo(vm.buttonEnabledProp)\n        bindTextTo(vm.buttonEnabledProp.map { if (it) \"Save changes\" else \"Nothing changed\" })\n        setWhenClicked(vm.buttonClickedProp)\n        // ^ set flag on action\n    })\n\n}.wrapInScrollView())\n```\n\nJavaFX layout (using JFoenix):\n\n```kt\nchildren.add(JFXTextField().apply {\n    promptText = \"Email\"\n    textProperty().bindBidirectionally(vm.emailProp)\n})\n\nchildren.add(Label().apply {\n    text = \"E-mail is invalid\"\n    bindVisibilityHardlyTo(!vm.emailValidProp)\n})\n\nchildren.add(JFXTextField().apply {\n    promptText = \"Name\"\n    textProperty().bindBidirectionally(vm.nameProp)\n})\n\nchildren.add(JFXTextField().apply {\n    promptText = \"Surname\"\n    textProperty().bindBidirectionally(vm.surnameProp)\n})\n\nchildren.add(JFXButton(\"Press me, hey, you!\").apply {\n    disableProperty().bindTo(!vm.buttonEnabledProp)\n    textProperty().bindTo(vm.buttonTextProp)\n    setOnAction { vm.buttonClickedProp.set() }\n})\n```\n\nCommon ViewModel:\n\n```kt\nclass MainVm(\n    // user is backed by arbitrary data source: in-memory, database, SharedPreferences, …\n    private val user: TransactionalPropertyStruct\u003cUser\u003e\n) : PersistableProperties {\n\n    // user input\n\n    // clone user into memory\n    private val editableUser = ObservableStruct(user, false)\n\n    // expose properties for View\n    val emailProp get() = editableUser prop User.Email\n    val nameProp get() = editableUser prop User.Name\n    val surnameProp get() = editableUser prop User.Surname\n\n    // handle actions\n\n    val buttonClickedProp = propertyOf(false).clearEachAnd {\n        // reset flag and perform action—patch user with values from memory\n        user.transaction { t -\u003e\n            t.setFrom(editableUser, User.Email + User.Name + User.Surname)\n        }\n    }\n\n    // preserve/restore state of this ViewModel (for Android)\n    override fun saveOrRestore(io: PropertyIo) {\n        /*\n        When saving state, property values are written to io which is PropertyOutput.\n        When restoring, property values are assigned from io which is PropertyInput.\n        \n        Infix function calls:\n        */\n        io x emailProp\n        io x nameProp\n        io x surnameProp\n    }\n\n    // some feedback for user actions\n\n    val emailValidProp = emailProp.map { it.contains(\"@\") }\n\n    // compare snapshots\n    private val usersDifferProp = user.snapshots().mapWith(editableUser.snapshots(), Objectz.NotEqual)\n\n    val buttonEnabledProp = usersDifferProp and emailValidProp\n\n}\n```\n\n## Persistence and Android\n\nThings available in `:android-bindings`:\n\n* [`SharedPreferenceProperty`](/android-bindings/src/main/kotlin/net/aquadc/persistence/android/pref/SharedPreferenceProperty.kt)\n implements `Property` interface, and stores data inside `SharedPreferences`;\n\n* [`SharedPreferencesStruct`](android-bindings/src/main/kotlin/net/aquadc/persistence/android/pref/SharedPreferencesStruct.kt)\n  is observable struct stored in `SharedPreferences`;\n\n```kt\n// this will copy data from player into the given SharedPreferences instance\nval storedPlayer = SharedPreferencesStruct(player, getSharedPreferences(…))\nval scoreProp = storedPlayer prop Player.Score\nval score = storedPlayer[Player.Score]\n// and this is different:\nstoredPlayer.transaction { p -\u003e\n    p[Score] = 100500\n}\n```\n\n* implementing [`PersistableProperties`](/properties/src/main/kotlin/net/aquadc/properties/persistence/memento/PersistableProperties.kt)\nhelps you to save or restore the state of a ViewModel to `ByteArray`/`Parcel` by implementing a single method,\nwithout declaring symmetrical, bolierplate, and error-prone `writeToParcel` and `createFromParcel` methods\nand without having Android dependencies:\n\n```kt\nclass SomeViewModel : PersistableProperties {\n    …\n    override fun saveOrRestore(io: PropertyIo) {    \n        io x prop1\n        io x prop2\n        io x prop3\n    }\n}\n```\nsee full [save \u0026 restore example](/samples/android-sample/src/main/kotlin/net/aquadc/propertiesSampleApp/MainActivity.kt).\n\n* JSON support built on top of `android.util.JsonReader/Writer`:\n```kt\n// reading\nval jsonPlayer = \"\"\"{\"name\":\"Hank\",\"surname\":\"Rearden\"}\"\"\"\n        .reader() // StringReader\n        .json() // JsonReader\n        .tokens() // TokenStream\n        .readAs(Player) // StructSnapshot\u003cPlayer\u003e\n\nval jsonPlayers = \"\"\"[ {\"name\":\"Hank\",\"surname\":\"Rearden\"}, ... ]\"\"\"\n        .reader().json().tokens().readListOf(Player)\n\n// writing\ntype.tokensFrom(value).writeTo(JsonWriter(…))\n```\n\n[`TokenStream`](/persistence/src/main/kotlin/net/aquadc/persistence/tokens/TokenStream.kt)\nabstraction is an iterator over tokens and it's helpful for\nchanging schema of provided data (instead of using “mappers”),\nsee [sample transform usage](/android-bindings/src/test/kotlin/promo.kt#L61-L69).\n\n## Android `RemoteViews`\n\n`RemoteViews` API differs from normal `View`s API.\nThus, `:android-bindings` module provides separate API for this. For example,\n```kt\nRemoteViews(packageName, R.layout.notification).bind(\n    android.R.id.text1 textTo vm.nameProp,\n    android.R.id.text2 textTo vm.emailProp,\n)\n```\nreturns you a `Property\u003cRemoteViews\u003e`, so you can just observe it\nand update notification on every change.\n\n## SQL (experimental)\n\n`:sql` module provides `Table`, a wrapper over `Schema`:\n```kt\n// trivial table. Primary key column is not mentioned within Schema\nval Players = tableOf(Player, \"players\", \"_id\", i64)\n```\nWith `Session` (implementations: Android-specific `SqliteSession`, ~~general-purpose `JdbcSession`~~ DON'T USE THIS SHIT TILL THE NEXT RELEASE), you're getting\n* SQL templates:\n```kt\nval selectNameEmailBySmth = Query(\n    \"SELECT a.name, b.email FROM anywhere a JOIN anything b WHERE smth = ?\",\n    /* argument */ string,\n    // return a list of positionally bound string-to-string tuples:\n    structs(projection(string, string), BindBy.Position)\n) // Session.(String) -\u003e List\u003cStruct\u003cTuple\u003cString, …, String, …\u003e\u003e\u003e\n\nval updateNameByEmail = Mutation(\n    \"UPDATE users SET name = ? WHERE email = ?\",\n    string, string,\n    execute()\n) // Transaction.(String, String) -\u003e Unit\n```\n\n(To be clear, the receivers are not exactly Session and Transaction. You can call a mutation just on a session, or query in a transaction.)\n\n* Triggers:\n```kt\nval listener = session.observe(\n    UserTable to TriggerEvent.INSERT,\n    UserTable to TriggerEvent.DELETE,\n) { report -\u003e\n    val userChanges = report.of(UserTable)\n    println(\"+\" + userChanges.inserted.size)\n    println(\"-\" + userChanges.removed.size)\n}\n…\nlistener.close() // unsubscribe\n```\n\n## HTTP\n\nHTTP is ~~unfortunately~~ the most popular application layer protocol.\nIts abilities are not restricted to passing binary or JSON bodies:\nthere are headers, query parameters, form fields, multipart, and more.\n\nWith `:http`, you can declare endpoints using some of `DataType`s from `:persistence`:\n```kt\nval user = GET(\"/user/{role}/\",\n    Header(\"X-Token\"), Path(\"role\"), Query(\"id\", uuid),\n    Response\u003cString\u003e())\n```\nThis gives you several features:\n* HTTP client templates: `val getUser = okHttpClient.template(baseUrl, user, deferred(::parseUser))` =\u003e\n  `(token: String, role: String, id: UUID) -\u003e Deferred\u003cString\u003e`.\n  Here you define `parseUser` yourself. Thus, you are free to handle responses as you want:\n  throw exception for non-2xx responses,\n  or return `Either\u003cHttpException, ResponseEntity\u003e`,\n  or ignore response code at all and just parse response body;  \n* server-side type-safe routing:\n  `undertowRoutingHandler.add(user, ::respond, ::respondBadRequest) { token, role, id -\u003e \"response\" }`;\n* link generation: if endpoint declaration uses GET method\n  and does not contain headers, it is possible to build URL:\n```kt\nGET(\"/user/{role}/\", Path(\"role\"), Query(\"id\", uuid))\n    .url(baseUrl, \"admin\", UUID.randomUUID())\n    // =\u003e //user/admin/?id=0b46b157-84b9-474c-83bb-76c2ddf58e75\n```\n\n**Hey, have you just reinvented Retrofit?**\n\nWell, yes, but actually no. Retrofit\n* works only on client-side,\n* requires method return types (Call, Observable, Deferred) to be tied to async framework,\n* promotes Service-style interfaces.\n\nLychee-HTTP, on the other side,\n* allows `Endpoint`s to be both invoked from client-side and implemented at server-side,\n* decouples async wrapper from return value,\n* httpClient.template(endpoint) returns a function, server-side endpoint handler is a funcion,\n  thus, no Services/Controllers.\n\n## FAQ\n\n#### What's the purpose of this library?\n\nThe main purpose is MVVM/DataBinding, especially in Android\nwhere preserving ViewModel state may be quirky.\nViewModel/ViewState can be declared as a set of mappings,\nwhere the values of some properties depend on some other ones.\n\n#### Why not use an existing solution?\n\n* `javafx.beans.property.Property`\n\n  It was the main source of inspiration. But the class hierarchy is too deep and wide,\n  looks like a complicated solution for a simple problem.\n  Has no support for multithreading. Looks like unsubscription won't take effect during notification.\n  \n* `android.util.Property`\n\n  A very trivial thing for animation. Has `ReflectiveProperty` subclass which is close to JavaFX concept\n  (every property is a `Property`) not observable and reflective (and thus sad).\n\n* `io.reactivex.BehaviorSubject`\n  \n  Has no read-only interface. You can either expose an `Observable` (without `get`) or a `BehaviorSubject` (with `get` and `set`).\n  Has no single-threaded version. Part of non-modular, poorly designed RxJava.\n\n* `LiveData`\n  \n  Confined to `Handler`/`Looper` which limits usage to Android only and complicates testing.\n  It's also an abstract class, thus customization is limited.\n\n* XML data-binding\n\n  Uses XML layouts (inflexible) and code generation (sucks in many ways, still breaks regularly).\n  Ties layouts to hard-coded Java classes thus killing XML reusability.\n\n#### Why version is 0.0.x?\n\n1.x versions mean stable and compatible API/ABI.\nLychee interface is not volatile, but is a subject to change, move, rename.\nThis means that it can be used in production apps (migrations are easy), but libraries should update as fast as Lychee does.\nIf your library does (or going to) depend on Lychee, file an issue:\nI will take into account which APIs do you use and maybe add a link to your library.\n\n`0.1.0` version is to be released after adding mutational and [linearization](https://github.com/Kotlin/kotlinx-lincheck) tests,\n`1.0.0` is planned after dropping workarounds for\n  [KT-24981: @JvmSynthetic for classes](https://youtrack.jetbrains.com/issue/KT-24981),\n  [KT-24067: type checking and casting of multi-arity function objects](https://youtrack.jetbrains.com/issue/KT-24067)\n  ).\n\n#### Where and how should I dispose subscriptions?\n\nWhen the property is not being observed, it not observes its source and thus not being retained by it.\nConsider the following code:\n\n```kt\nval someGlobalProp = propertyOf(100)\nval mappedProp = someGlobalProp.map { it * 10 }\n// mappedProp has no listeners and thus not observes someGlobalProp\n\nprintln(mappedProp.value) // value calculated on demand\n\nmappedProp.addChangeListener { ... }\n// mappedProp now listens for someGlobalProp changes\n// and not eligble for GC until someGlobalProp is not\n\nsomeGlobalProp.value = 1\n// mappedProp value calculated due to original value change\n// mappedProp's listener was notified\n```\n\nAll Android bindings are based on [bindViewTo](/android-bindings/src/main/kotlin/net/aquadc/properties/android/bindings/bind.kt#L25)\n which creates a [Binding](/android-bindings/src/main/kotlin/net/aquadc/properties/android/bindings/bind.kt#L64).\nIt is a [flyweight](https://en.wikipedia.org/wiki/Flyweight_pattern) observing\nView attached state, Activity started state, and Property changes.\nWhen view gets attached to window, `Binding` is getting subscribed\nto Activity lifecycle via [Lifecycle-Watcher](/android-bindings/src/main/kotlin/net/aquadc/properties/android/Lifecycle-Watcher.kt#L17);\nwhen Activity is started, `Binding` listens for data source.\nWhen Activity gets stopped or View gets detached,\nbinding unsubscribes and becomes eligible for garbage collection\nalong with the whole View hierarchy.\n\n#### How much black magic do you use under the hood?\n\nSome operator overloading, some value classes, several compilation error suppressions, tons of unchecked casts.\nNo reflection, zero annotation processing.\nIf you encounter any problems, they most likely will be related to type inference or Java interop.\n\n#### Is there anything similar to RxJava's Single?\n\nNope. Java since v. 1.8 contains `CompletableFuture` for async computations.\nIt also was backported to ~~Java 6.5~~ _Android_ a long time ago.\nNote that it is distributed under “GPL v. 2.0 with classpath exception”\nwhich is not as restrictive as GPL itself.\n\nYou can mutate concurrent properties from background threads (e.g. in the end of async computations),\ntriggering UI state change as needed and without any callbacks.\n\n#### ProGuard rules for Android?\n\n[Here you are.](/samples/android-sample/proguard-rules.pro#L30-L55)\n\n## Adding to a project\n\n[![Download](https://maven-badges.herokuapp.com/maven-central/su.lychee/properties/badge.svg?style=flat)](https://search.maven.org/artifact/su.lychee/properties/0.0.17/jar) Properties\n\n[![Download](https://maven-badges.herokuapp.com/maven-central/su.lychee/persistence/badge.svg?style=flat)](https://search.maven.org/artifact/su.lychee/persistence/0.0.17/jar) Persistence\n\n[![Download](https://maven-badges.herokuapp.com/maven-central/su.lychee/extended-persistence/badge.svg?style=flat)](https://search.maven.org/artifact/su.lychee/extended-persistence/0.0.17/jar) Extended Persistence\n\n[![Download](https://maven-badges.herokuapp.com/maven-central/su.lychee/android-bindings/badge.svg?style=flat)](https://search.maven.org/artifact/su.lychee/android-bindings/0.0.17/aar) Android Bindings\n\n[![Download](https://maven-badges.herokuapp.com/maven-central/su.lychee/http/badge.svg?style=flat)](https://search.maven.org/artifact/su.lychee/http/0.0.17/jar) HTTP\n\n[![Download](https://maven-badges.herokuapp.com/maven-central/su.lychee/sql/badge.svg?style=flat)](https://search.maven.org/artifact/su.lychee/sql/0.0.17/jar) SQL (experimental)\n\n[![Download](https://maven-badges.herokuapp.com/maven-central/su.lychee/android-json/badge.svg?style=flat)](https://search.maven.org/artifact/su.lychee/android-json/0.0.17/jar) Android JSON\n\n[![Download](https://maven-badges.herokuapp.com/maven-central/su.lychee/android-json-on-jvm/badge.svg?style=flat)](https://search.maven.org/artifact/su.lychee/android-json-on-jvm/0.0.17/jar) Android JSON on JVM\n\n```groovy\n// `allprojects` section of top-level build.gradle || root of module-level build.gradle\nrepositories {\n    ...\n    mavenCentral()\n    maven { url \"https://jitpack.io\" } // our dependency, Collection-utils, still lives there\n}\n\n// module-level build.gradle\ndependencies {\n    // I am a bit dumb, so with my first publication on Maven Central I ended up having empty \u003cdependencies\u003e for some of artifacts\n    implementation \"com.github.Miha-x64.Kotlin-MPP_Collection_utils:Collection-utils-jvm:1.0-alpha05\"\n  \n    def lychee = '0.0.17'\n//  val lychee = \"0.0.17\"\n    implementation(\"su.lychee:properties:$lychee\") // observables for both JVM and Android\n    implementation(\"su.lychee:persistence:$lychee\") // persistence for JVM and Android\n    implementation(\"su.lychee:extended-persistence:$lychee\") // partial structs, tuples, either, unsigned, primitive[], token transforms\n    implementation(\"su.lychee:android-bindings:$lychee\") // AAR for Android(x): View bindings, Parcel, SharedPreferences as Struct, Handler as Executor\n    implementation(\"su.lychee:android-json:$lychee\") // android.util.JsonReader as TokenStream\n    implementation(\"su.lychee:android-json-on-jvm:$lychee\") // implements android.util.JsonReader for server and desktop, use with android-json outside of Android \n    implementation(\"su.lychee:sql:$lychee\") // observable SQL and SQL templates\n    implementation(\"su.lychee:http:$lychee\") // RPC over HTTP: client-side HTTP templates, server-side routing, type-safe link generator\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmiha-x64%2Flychee","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmiha-x64%2Flychee","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmiha-x64%2Flychee/lists"}