Lychee - The most complete and powerful data-binding library and persistence infra for Kotlin 1.3, Android & Anko/Splitties, JavaFX & TornadoFX. - Kotlin Resources

Build Status Lock-free Extremely lightweight Hits-of-Code Channel at Kotlin Slack Telegram chat

Adding to a project

Download Properties

Download Persistence

Download Extended Persistence

Download Android Bindings

// 'allprojects' section of top-level build.gradle || root of module-level build.gradle
repositories {
    ...
    maven { url 'https://dl.bintray.com/miha-x64/maven' }
}

// module-level build.gradle
dependencies {
    implementation 'net.aquadc.properties:properties:0.0.11' // observables for both JVM and Android
    implementation 'net.aquadc.properties:persistence:0.0.11' // persistence for JVM and Android
    implementation 'net.aquadc.properties:extended-persistence:0.0.11' // partial structs, tuples, either, unsigned, primitive[], token transforms
    implementation 'net.aquadc.properties:android-bindings:0.0.11' // AAR for Android(x): View bindings, Parcel, JsonReader as TokenStream, SharedPreferences as Struct, Handler as Executor
}

Lychee (ex. reactive-properties)

Properties (subjects) inspired by JavaFX and Vue.js MVVM-like approach. A Property provides functionality similar to BehaviorSubject in RxJava, or Property in JavaFX, or LiveData in Android Arch.

  • Simple and easy-to-use
  • Lightweight: persistence + properties + android-bindings define <1500 methods, many of them are inline funs
  • Extensible: not confined to Android, JavaFX or whatever
  • Single-threaded and concurrent (lock-free) properties are supported
  • Ready to use MVVM/data-binding for Android and JavaFX
  • Sweet with View DSLs like Splitties and TornadoFX
  • Depends only on Kotlin-stdlib and Kotlin-MPP Collection utils for overheadless EnumSets
  • Presentation about properties: initial problem statement and some explanations

Alternatives

Properties sample

val prop: MutableProperty<Int> = propertyOf(1)
val mapped: Property<Int> = prop.map { 10 * it }
assertEquals(10, mapped.value)

prop.value = 5
assertEquals(50, mapped.value)


val tru = propertyOf(true)
val fals = !tru // operator overloading
assertEquals(false, fals.value)

Sample usage in GUI application

Anko layout for Android:

verticalLayout {
    padding = dip(16)

    editText {
        id = 1 // let view save its state, focus, etc
        hint = "Email"
        bindTextBidirectionally(vm.emailProp)
        bindErrorMessageTo(vm.emailValidProp.map { if (it) null else "E-mail is invalid" })
    }

    editText {
        id = 2
        hint = "Name"
        bindTextBidirectionally(vm.nameProp)
    }

    editText {
        id = 3
        hint = "Surname"
        bindTextBidirectionally(vm.surnameProp)
    }

    button {
        bindEnabledTo(vm.buttonEnabledProp)
        bindTextTo(vm.buttonTextProp)
        setWhenClicked(vm.buttonClickedProp)
        // ^ set flag on action
    }

}

JavaFx layout (using JFoenix):

children.add(JFXTextField().apply {
    promptText = "Email"
    textProperty().bindBidirectionally(vm.emailProp)
})

children.add(Label().apply {
    text = "E-mail is invalid"
    bindVisibilityHardlyTo(!vm.emailValidProp)
})

children.add(JFXTextField().apply {
    promptText = "Name"
    textProperty().bindBidirectionally(vm.nameProp)
})

children.add(JFXTextField().apply {
    promptText = "Surname"
    textProperty().bindBidirectionally(vm.surnameProp)
})

children.add(JFXButton("Press me, hey, you!").apply {
    disableProperty().bindTo(!vm.buttonEnabledProp)
    textProperty().bindTo(vm.buttonTextProp)
    setOnAction { vm.buttonClickedProp.set() }
})

Common ViewModel:

class MainVm(
        // 'user' is backed by whatever data source — in-memory, database, SharedPreferences, …
        private val user: TransactionalPropertyStruct<User>
) : PersistableProperties {

    // user input

    // clone 'user' into memory
    private val editableUser = ObservableStruct(user, false)

    // expose properties for View
    val emailProp get() = editableUser prop User.Email
    val nameProp get() = editableUser prop User.Name
    val surnameProp get() = editableUser prop User.Surname

    // handle actions

    val buttonClickedProp = propertyOf(false).clearEachAnd {
        // reset flag and perform action — patch 'user' with values from memory
        user.transaction { t ->
            t.setFrom(editableUser, User.Email + User.Name + User.Surname)
        }
    }

    // preserve/restore state of this ViewModel (for Android)
    override fun saveOrRestore(io: PropertyIo) {
        /*
        When saving state, property values are written to io which is PropertyOutput.
        When restoring, property values are assigned from io which is PropertyInput.
        
        Infix function calls:
        */
        io x emailProp
        io x nameProp
        io x surnameProp
    }

    // some feedback for user actions

    val emailValidProp = emailProp.map { it.contains("@") }

    // compare snapshots
    private val usersDifferProp = user.snapshots().mapWith(editableUser.snapshots(), Objectz.NotEqual)

    val buttonEnabledProp = usersDifferProp and emailValidProp

}

Persistence

The simplest intersection of persistence and databinding is SharedPreferenceProperty (Android): it implements Property interface, and stores data inside SharedPreferences.

Another thing is PersistableProperties: this interface allows you to save or restore the state of a ViewModel using ByteArray via a single method without declaring symmetrical, bolierplate and error-prone writeToParcel and createFromParcel methods.

override fun saveOrRestore(io: PropertyIo) {
    
    io x prop1
    io x prop2
    io x prop3
}

But the most interesting and reusable things are all around Struct, Schema, and DataType.

Schema is a definition of a data bag with named, ordered, typed fields.

object Player : Schema<Player>() {
    val Name = "name" let string
    val Surname = "surname" let string
    val Score = "score".mut(int, default = 0)
}

let function creates an immutable field definition, mut declares a mutable one. Name, Surname and Score are field definitions, similarly to typed key pattern.

Struct is an instance carrying some data according to a certain Schema.

val player: StructSnapshot<Player> = Player.build { p ->
    p[Name] = "John"
    p[Surname] = "Galt"
}

field values can be accessed with indexing operator:

assertEquals(0, player[Player.Score]) // Score is equal to the default value which was set in Schema

The builder-function is recommended for setting all required fields:

fun Player(name: String, surname: String) = Player.build { p ->
    p[Name] = name
    p[Surname] = surname
}

Okay, the player instance is fully immutable, but we want a mutable observable one:

val observablePlayer = ObservableStruct(player)
val scoreProp: Property<Int> = observablePlayer prop Player.Score
someTextView.bindTextTo(scoreProp.map(CharSequencez.ValueOf))

// both mutate the same text in-memory int value and a text field:
scoreProp.value = 10
observablePlayer[Player.Score] = 20

SharedPreferencesStruct (Android) has very similar interface, but can be mutated only inside a transaction:

// this will copy data from player into the given SharedPreferences instance
val storedPlayer = SharedPreferencesStruct(player, getSharedPreferences(…))
val scoreProp = storedPlayer prop Player.Score
val score = storedPlayer[Player.Score]
// and this is different:
storedPlayer.transaction { p ->
    p[Score] = 100500
}

There is JSON support built on top of android.util.JsonReader/Writer:

// reading
val jsonPlayer = """{"name":"Hank","surname":"Rearden"}"""
        .reader() // StringReader
        .json() // JsonReader
        .tokens() // TokenStream
        .readAs(Player) // StructSnapshot<Player>

val jsonPlayers = """[ {"name":"Hank","surname":"Rearden"}, ... ]"""
        .reader().json().tokens().readListOf(Player)

// writing
type.tokensFrom(value).writeTo(JsonWriter(…))

TokenStream abstraction is helpful for changing schema of provided data (instead of using ‘mappers’), see sample transform usage.

Also, SQLite support is currently being developed.

ProGuard rules for Android

(for :persistence, :properties and :android-bindings)

# libs with compileOnly scope
-dontwarn android.support.annotation.**
-dontwarn android.support.v7.widget.**
-dontwarn android.support.design.widget.**
-dontwart androidx.**
-dontwarn okio.**

# required by EnumSet
-keepclassmembers enum * {
  public static **[] values();
}

# bindings to Java(FX)
-dontwarn net.aquadc.properties.fx.JavaFxApplicationThreadExecutorFactory
-assumenosideeffects class net.aquadc.properties.executor.PlatformExecutors {
    private void findFxFactory(java.util.ArrayList); # bindings to JavaFX
    private void findFjFactory(java.util.ArrayList); # If you're not going to addChangeListener() on ForkJoin threads
}

# debug-only assertions for enforcing type-safety
-assumenosideeffects class net.aquadc.persistence.type.SimpleNoOp {
    private void sanityCheck(java.lang.Object);
}
-assumenosideeffects class net.aquadc.persistence.extended.ArrayNoOp {
    private void sanityCheck(java.lang.Object);
}

# https://sourceforge.net/p/proguard/bugs/660/
-keepclassmembernames class net.aquadc.properties.internal.** {
  volatile <fields>;
}

FAQ

What’s the purpose of this library?

The main purpose is MVVM/DataBinding, especially in Android where preserving ViewModel state may be quirky. ViewModel/ViewState can be declared as a set of mappings, where the values of some properties depend on some other ones.

Why not use an existing solution?

  • javafx.beans.property.Property

    It was the main source of inspiration. But the class hierarchy is too deep and wide, looks like a complicated solution for a simple problem. Has no support for multithreading. Looks like unsubscription won’t take effect during notification.

  • android.util.Property

    A very trivial thing for animation. Has ReflectiveProperty subclass which is close to JavaFX concept (every property is a Property) not observable and reflective (and thus sad).

  • io.reactivex.BehaviorSubject

    Has no read-only interface. You can either expose an Observable (without get) or a BehaviorSubject (with get and set). Has no single-threaded version. Part of non-modular, poorly designed RxJava.

  • LiveData

    Confined to Handler/Looper which limits usage to Android only and complicates testing. It’s also an abstract class, thus customization is limited.

  • XML data-binding

    Uses XML layouts (inflexible) and code generation (sucks in many ways, still breaks regularly). Ties layouts to hard-coded Java classes thus killing XML reusability.

Why version is 0.0.x?

1.x versions mean stable and compatible API/ABI. Lychee interface is not volatile, but is a subject to change, move, rename. This means that it can be used in production apps (migrations are easy), but not in libraries.

0.1.0 version is to be released after adding mutational and linearization tests, 1.0.0 is planned after dropping workarounds for KT-24981: @JvmSynthetic for classes, KT-24067: type checking and casting of multi-arity function objects, and when inline classes come more stable (e. g. KT-31431 JVM backend failure on inline functions of inline classes, KT-33224: @JvmName for function with inline class parameter ).

Where and how should I dispose subscriptions?

When the property is not being observed, it not observes its source and thus not being retained by it. Consider the following code:

val someGlobalProp = propertyOf(100)
val mappedProp = someGlobalProp.map { it * 10 }
// mappedProp has no listeners and thus not observes someGlobalProp

println(mappedProp.value) // value calculated on demand

mappedProp.addChangeListener { ... }
// mappedProp now listens for someGlobalProp changes
// and not eligble for GC until someGlobalProp is not

someGlobalProp.value = 1
// mappedProp value calculated due to original value change
// mappedProp's listener was notified

All Android bindings are based on bindViewTo which creates a Binding. It is a flyweight implemening View.OnAttachStateChangeListener, ChangeListener and (Boolean) -> Unit. When view gets attached to window, Binding is getting subscribed to Activity lifecycle via Lifecycle-Watcher; when Activity is started, Binding listens for data source. When Activity gets stopped or View gets detached, binding unsubscribes and becomes eligible for garbage collection along with the whole view hierarchy.

Is there anything similar to RxJava’s Single?

Nope. Java since v. 1.8 contains CompletableFuture for async computations. It also was backported to Java 6.5 Android a long time ago. Note that it is distributed under ‘GPL v. 2.0 with classpath exception’ which is not as restrictive as GPL itself.

You can mutate concurrent properties from background threads (e. g. in the end of async computations), triggering UI state change as needed and without any callbacks.

compile "net.aquadc.properties:properties:0.0.11"

Related Libraries

materialdrawerkt

A Kotlin DSL wrapper around the mikepenz/MaterialDrawer library.

Last updated 3 mins ago

anvil

Minimal UI library for Android inspired by React

Last updated 3 mins ago

RichUtilsKt

Advancement Utils for Android Developer written in Kotlin

Last updated 3 mins ago

mapme

The Android maps adapter

Last updated 3 mins ago

bubble

Screen orientation detector for android

Last updated 3 mins ago