Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.0.0-beta2 #1

Merged
merged 18 commits into from May 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
2253573
[bindings] simplified holder/componet usage by not requiring specifyi…
agrosner Apr 23, 2017
a9f75ef
[bindings] fix binding registering and example from changes.
agrosner Apr 23, 2017
9478687
[bindings] make constructors internal to emphasize use of binding met…
agrosner Apr 23, 2017
b9b2f75
[readme] updated documentation for api changes.
agrosner Apr 23, 2017
d4893ed
[binding] made binding register methods into interface. Support nulla…
agrosner Apr 24, 2017
d5d507c
[bindings] support null viewmodel. support null observable field conv…
agrosner Apr 24, 2017
91b2a66
[binding] support generic, toplevel binding changes. support nullable…
agrosner Apr 24, 2017
61a4bb0
[onewaytosource] add viewmodel as parameter to the onewaytosource met…
Apr 24, 2017
6e891aa
[twowaybinding] add viewmodel as second parameter to inverse setter s…
Apr 24, 2017
1bf4c1f
[project] create separate artifact for anko and remove initial depend…
Apr 24, 2017
4b87051
[readme] fix compile statement.
Apr 24, 2017
d9616d1
[readme] update for nullable example.
Apr 24, 2017
b197fc0
[binding] binding component non null.
Apr 24, 2017
6b4b8f4
[binding] make the binding componennt a binding register type so we c…
Apr 24, 2017
519cd8b
[binding] expose isbound property for consumers.
Apr 24, 2017
c338c2f
[version] update kotlin version. fix packaging.
May 12, 2017
ecd636f
[binding] add reverse operator for booleans.
May 12, 2017
c573937
[binding] add comments in the main interface.
agrosner May 13, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
116 changes: 70 additions & 46 deletions README.md
@@ -1,47 +1,56 @@
# KBinding

KBinding is a library for [Anko](https://github.com/Kotlin/anko) to enable databinding in a fluent, easy to understand syntax.
KBinding is a Kotlin databinding library best used with [Anko](https://github.com/Kotlin/anko) to enable databinding in a fluent, easy to understand syntax.

```kotlin

val holder = BindingHolder(viewModel)

// one way binding on Observable fields
holder.oneWay(bindSelf(viewModel.name).toText(textView))
holder.bindSelf { viewModel.name }.toText(textView)

// one way binding on non observables
holder.oneWay(ViewModel::name,
bind({ viewModel.name })
holder.bind(ViewModel::name) { it.name }
.on { if (it == "") View.GONE : View.VISIBLE } // convert to another type
.toViewVisibility(someView))
.toViewVisibility(someView)

// two way binding on observable that synchronizes text and value changes.
holder.twoWay(bindSelf(viewModel.name)
holder.bindSelf { it.name }
.toText(textView)
.twoWay()
.toFieldFromText())
.toFieldFromText()

// two way binding that synchronizes compoundbutton / checkbox changes
holder.twoWay(bindSelf(viewModel.selected)
holder.bindSelf { it.selected }
.toOnCheckedChange(checkbox)
.twoWay()
.toFieldFromCompound())
.toFieldFromCompound()

// binds input changes from the view to the name property.
holder.oneWayToSource(bind(textView)
holder.bind(textView)
.onSelf()
.to(viewModel.name))
.to { it.name }

// binds input changes from the view to the name property (non observable).
holder.oneWayToSource(bind(textView)
holder.bind(textView)
.onSelf()
.to { input, view -> viewModel.name = input})
.to { input, view -> it.name = input}

holder.viewModel = viewModel // set the ViewModel (no restriction and could be a `Presenter`)
holder.bindAll() // binds all bindings, also will execute all of them once.
// if bound will reevaluate the bindings

// support nullable `ViewModel`
// if normal binding, a default value for field is used and the expression is not evaluated.
// in this case it's executed always
holder.bindNullable(ViewModel::name) { it?.name }
.onSelf()
.toText(textView)

holder.unbindAll() // when done, unbind

val binding = holder.bindSelf(textView).toObservable { it.name }
binding.unbind() // can turn off binding as needed

```

## Including in your project
Expand All @@ -57,18 +66,22 @@ allProjects {
```

```gradle

compile 'com.github.agrosner.kbinding:kbinding:1.0.0-beta2' // version of KBinding

// to use with Anko, separate artifact.
compile 'org.jetbrains.anko:anko-sdk15:0.9.1' // current version of anko used
compile 'com.github.agrosner:KBinding:1.0.0-beta1' // version of KBinding
compile 'com.github.agrosner.KBinding:kbinding-anko:1.0.0-beta2'
```

## Getting Started

KBinding works best with [Anko](https://github.com/Kotlin/anko), but can be used by other consumers.

First, we need a ViewModel or object to send data to our bindings.
First, we need a ViewModel or object to send data to our observableBindings.

By default normal properties, when their value changes, will not propagate those changes
to our bindings. So we have a couple of options. First we must extend `BaseObservable`.
to our observableBindings. So we have a couple of options. First we must extend `BaseObservable`.

```kotlin

Expand All @@ -77,7 +90,7 @@ class UserViewModel(var name: String = "") : BaseObservable()
```

The base class of `BaseObservable` by default handles propagating changes of the ViewModel
and fields to the bindings when notified. In order to notify changes to the parent `ViewModel`,
and fields to the observableBindings when notified. In order to notify changes to the parent `ViewModel`,
we have three options:

1. Override a fields `setter` and notify changes to our `BaseObservable`:
Expand Down Expand Up @@ -108,7 +121,8 @@ When binding, option (1) requires us to explicitly notify the parent on change o
Option (1) and (2) also requires us to specify the field in the binding:
```kotlin

oneWay(UserViewModel::name, bindSelf { viewModel.name }.toText(this))
holder.bindSelf(UserViewModel::name) { it.name }
.toText(this)

```

Expand All @@ -117,21 +131,23 @@ to the `KProperty`:

```kotlin

oneWay(bindSelf(viewModel.name).toText(this))
holder.bindSelf { it.name }.toText(this)

```

We use `it` in the binding, not the direct reference to `viewModel` or top-level object in the view because if that `ViewModel` changes, the bindings will reference a stale object!

### Create the UI

Have our components that we use in `Anko` extend `BindingComponent<Activity, ViewModel>` for convenience collection and disposal of the bindings:
Have our components that we use in `Anko` extend `BindingComponent<Activity, ViewModel>` for convenience collection and disposal of the observableBindings:

```kotlin
class MainActivityLayout(mainActivityViewModel: MainActivityViewModel)
: BindingComponent<MainActivity, MainActivityViewModel>(mainActivityViewModel) {
```

Instead of overridding `createView()`, we override `createViewWithBindings()`. This is
so we internally can bind all created bindings after view is created.
so we internally can bind all created observableBindings after view is created.

Then to bind views:

Expand All @@ -140,21 +156,20 @@ Then to bind views:
override fun createViewWithBindings(ui: AnkoContext<MainActivity>): View {
return with(ui) {
textView {
twoWay(UserViewModel::name,
bindSelf { viewModel.name }
bindSelf(UserViewModel::name) { it.name }
.toText(this)
.twoWay()
.toFieldFromText())
.toFieldFromText()
}
}
}

```

The `BindingComponent` is backed by the `BindingHolder`, which collects and manages
the bindings.
the observableBindings.

If we do not unbind the `BindingHolder`, it will lead to memory leaks of all of the bindings. You need to explicitly call `unbind()` when calling the `BindingHolder` directly, or `destroyView()` if using the `BindingComponent`:
If we do not unbind the `BindingHolder`, it will lead to memory leaks of all of the observableBindings. You need to explicitly call `unbind()` when calling the `BindingHolder` directly, or `destroyView()` if using the `BindingComponent`:

```kotlin
bindingHolder.unbind()
Expand All @@ -172,16 +187,16 @@ override fun onDestroy() {

```

# Supported bindings
# Supported observableBindings

Currently we support three kinds of bindings:
Currently we support three kinds of observableBindings:
1. `oneWay` -> handle changes from `ViewModel` to `View`
2. `twoWay` -> handles changes in both directions between `ViewModel` <-> `View`
3. `oneWayToSource` -> handles changes from `View` to `ViewModel`

## One Way Bindings

`oneWay` bindings handle changes from a `Observable` or functional expression on a specific `View`.
`oneWay` observableBindings handle changes from a `Observable` or functional expression on a specific `View`.

The changes from an `ObservableField` come directly from the instance, while changes
from an expression need explicit wiring to determine for which property it came from.
Expand All @@ -197,9 +212,9 @@ The expression syntax is required to register to changes on a specific `KPropert
```kotlin

textView {
oneWay(MyViewModelClass::someField,
bindSelf { viewModel.someField }
.toText(this))
bindSelf(MyViewModelClass::someField)
{ viewModel.someField }
.toText(this)
}

```
Expand Down Expand Up @@ -228,9 +243,9 @@ Take, in an e-commerce app we want to display the number of items in the cart. T
```kotlin

textView {
oneWay(bind(viewModel.count)
bind { viewModel.count }
.on { string(R.string.someFormattedString, plural(R.plural.somePlural, it)) } // helper methods for `View.context`
.toText(this))
.toText(this)
}

```
Expand All @@ -241,11 +256,11 @@ To specify this example on a custom text setter:
```kotlin

textView {
oneWay(bind(viewModel.count)
bind { viewModel.count }
.on { string(R.string.someFormattedString, plural(R.plural.somePlural, it)) } // helper methods for `View.context`
.toView(this, { value ->
text = value
}))
})
}

```
Expand All @@ -268,9 +283,18 @@ For example:
```kotlin

textView {
oneWayToSource(bind(this)
bind(this)
.onSelf() // String updates from `text`
.to(viewModel.count))
.toObservable { it.name }

// or custom update method
bindSelf(this).to { vm, input, view ->
// viewmodel might be null
vm?.let { vm ->
// assign data to the viewmodel
vm.name = input
}
}
}

```
Expand Down Expand Up @@ -303,15 +327,15 @@ Then pass it into the call to `bind`:

```kotlin
textView {
oneWayToSource(bind(this, MyOnTextChangedRegister())
bind(this, MyOnTextChangedRegister())
.onSelf() // String updates from `text`
.to(viewModel.count))
.to { viewModel.count }
}
```

## Two Way Bindings

`twoWay` bindings are slightly more complex and complicated. It specifies that an expression or `ObservableField` and `View`'s data are synchronized. We only allow one such binding per `KProperty` or `ObservableField` to prevent a cycle of updates occurring in the UI.
`twoWay` observableBindings are slightly more complex and complicated. It specifies that an expression or `ObservableField` and `View`'s data are synchronized. We only allow one such binding per `KProperty` or `ObservableField` to prevent a cycle of updates occurring in the UI.

We start off the binding the same way as a `oneWay` (`twoWay` extends off of `oneWay`) and then specify we want it `twoWay` and complete the reverse assignment. Any default, out-of-the-box `oneWay` binding on a `View` will only update `View` when the value is different than the current value. This prevents update cycles that could occur in a `twoWay`.

Expand All @@ -324,8 +348,8 @@ To register a `twoWay` binding on an `ObservableField` that relates to a user in

```kotlin
editText {
twoWay(bindSelf(viewModel.address).toText(this)
.twoWay().toFieldFromText())
bindSelf { viewModel.address }.toText(this)
.twoWay().toFieldFromText()
}
```

Expand All @@ -337,10 +361,10 @@ To use expressions without conveniences:

```kotlin
editText {
twoWay(MyViewModelClass::address, bindSelf { viewModel.address }.toText(this)
bindSelf(MyViewModelClass::address) { viewModel.address }.toText(this)
.twoWay().toInput(OnTextChangedRegister()) { viewValue ->
viewModel.address.value = it ?: viewModel.address.defaultValue // if null, set non-null default if we'd like.
})
}
}
```

Expand Down
3 changes: 2 additions & 1 deletion app/build.gradle
Expand Up @@ -31,7 +31,8 @@ dependencies {
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support:design:25.3.1'

compile project(':okbinding')
compile project(':kbinding')
compile project(':kbinding-anko')

testCompile 'junit:junit:4.12'
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
Expand Down
@@ -1,4 +1,4 @@
package com.andrewgrosner.okbinding;
package com.andrewgrosner.kbinding;

import android.content.Context;
import android.support.test.InstrumentationRegistry;
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/AndroidManifest.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.andrewgrosner.okbinding.sample"
<manifest package="com.andrewgrosner.kbinding.sample"
xmlns:android="http://schemas.android.com/apk/res/android">

<application
Expand All @@ -9,7 +9,7 @@
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:name="com.andrewgrosner.kbinding.sample.MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
Expand Down
@@ -1,4 +1,4 @@
package com.andrewgrosner.okbinding.sample
package com.andrewgrosner.kbinding.sample

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
Expand Down