Setter is abstract type. There are its subclasses for RxJava Flowable and Kotlin Coroutines Flow.
Setter assigns with Android View by extension method
fun VIEW.addSetter(FLOWABLE_TYPE<DATA>, VIEW.(DATA) -> Unit)
So we can add any realization for any data and any view. For example
fun <T : CharSequence> TextView.setText(text: Flowable<T>) =
addSetter(text) { this.text = it }
Under the hood there is ObserveListener object associated with each view. This object store flowables
cache for view and subscribe on data changes when view onViewAttachedToWindow
and unsubscribe when view onViewDetachedFromWindow
.
There are many default setters already defined in library (and many will be added in next releases)
You can also easyly add your own setter with addSetter
method. For example if you're using Glide
for image loading setter will looks like this
fun ImageView.setImageUrl(image : Flowable<String>) =
addSetter(image) { url ->
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(this)
}
and then use it in your DSL layout (JetBrains Anko in example, but addSetter
is extension for
generic type T : View, so you can use it anywhere, even in ButterKnifed xml layouts)
frameLayout {
imageView {
setImageUrl(vm.user.map { it.avatarUrl })
}.lparams(matchParent, wrapContent)
}
There is also support for Two-Way Data Binding
View emits changes into FlowableProcessor (like Subject for Observable, but for Flowable).
Field and Item abstractions are deprecated and will be removed in release 1.0.0
It is representing by Fields and Items.
Field is observable property, that emit Optional element when element set to field. Allows 'null'.
Item is observable property, that emit T element when element set to field. Allows 'non-null' only.
for both, rx and coroutines provided base types
- Rx(Coroutine)Field
- Rx(Coroutine)Item
- Rx(Coroutine)CharSequence
- Rx(Coroutine)Boolean
- Rx(Coroutine)Long
- Rx(Coroutine)Int
- Rx(Coroutine)Double
- Rx(Coroutine)Float
RecyclerView
part deserves own block. Just recall typical RecyclerView
usage:
- Creating layout with
RecyclerView
- Creating layouts for ALL
ViewHolders
(I saw up to 9 different holders in one Recycler, are you?) - Creating Adapter, which handle
ViewHolders
logic - Creating
ViewHolders
- Writing
ViewHolders
manipulating logic inonBind
method
This library reduce described complexity, but bit harder for first dive. So look carefully :)
You just need:
- Create layout with
RecyclerView
- Also create layouts for ALL
ViewHolders
- Create binding between items types (classes) and
ViewHolders
layouts. - Nothing. It is all
In example below I used JetBrains Anko and AnkoCoroutinesLayout
from this library. In this class I needs to override only View creation method.
But you can extend own RxLayout
or CoroutinesLayout
or even Layout
.
Layout
is not depend on any library and can be any and adapted to any DSL library and any data flow. It describes entity, that get item changes and provide method for creating View:
abstract fun createView(parent: ViewGroup): View
RxLayout
and CoroutinesLayout
are layouts that adapted for data flow concrete types. They both override on own manner:
fun set(item: T)
And both provide:
- (observable) property
item
with type Rx(Coroutine)Field described above - property
itemChanges
that emits only non-null items fun changeItem(itemChange: T?.() -> Unit)
lets you to change item with Recycler will be notified about item change automatically
Example can be found Here. One screen app, that receive currency rates every second, and represent it according to entered value. On top position of RecyclerView
is base currency with editable field for it value, below fields with another currencies and values, based on base value, entered in header ViewHolder (rate * entered base value). Clicked rate becomes base rate.
Extractions here:
Describing Recycler in DSL:
recyclerView {
// Just remove BlinkAnimation on items change
(itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false
// Same as usualy - providing LayoutManager
layoutManager = LinearLayoutManager(context)
// Pay attention for line below, it will be described later
setItems(vm.data, revolutBindings(vm))
// Hide view based on observable boolean loading
hideUntilLoaded(vm.loading)
}
Then layouts. Layouts must extends Layout
class, and looks like this (left only important part, removed static data):
// Rates requests every second, that both of layouts will get holding item changes
// This class extends Layout which generic type is 'String'. Pay attention
class CurrencyHeaderItem(private val vm : RevolutViewModel, viewGroup: ViewGroup) : AnkoCoroutinesLayout<String>(viewGroup) {
override fun createView(ui: AnkoContext<AnkoCoroutinesLayout<String>>): View =
ui.frameLayout {
imageView {
// custom setter for image view, if item've change - image also have change
setImageUrl(itemChanges.map { toImageUrl(it) })
}
textView {
// AnkoCoroutinesLayout generic type is String, then 'itemChanges' property is observable String
setText(itemChanges)
}
textView {
// We can also do any modiications with data
setText(itemChanges.flatMapConcat { flow { emit(codeToValue(it)) } })
}
editText {
// Seting text from ViewModel
setText(vm.currentValue)
// After text changes bind it to back to field
afterTextChanges(vm.currentValue)
}
}
}
Second layout different, looks like very similar to this, but i wouldn't bring full listing. Just pay attention for generic type:
// Generic type is CurrencyRate differs from String in previous layout
// so 'itemChanges' property is observable CurrencyRate
class CurrencySimpleItem(private val vm : RevolutViewModel, viewGroup: ViewGroup) : AnkoCoroutinesLayout<CurrencyRate>(viewGroup)
It is all. One second... No! :) Just a little part, binding. Do you remember calling method 'setItems' on RecyclerView? Look to it:
setItems(vm.data, revolutBindings(vm))
vm.data
is observable list of items, but we need bind it to recycler:
fun revolutBindings(vm : RevolutViewModel) =
ItemBindings
.of(String::class.java) { CurrencyHeaderItem(vm, it) }
.addBinding(CurrencyRate::class) { CurrencySimpleItem(vm, it) }
Now it is completely all. You can run example and look how recycler represents data and works without providing adapter, or view holder.
Full signature of setItems
fun looks like this (Coroutines here, but also there is Rx port in library)
fun <T : Any> RecyclerView.setItems(
// Observable data
items: Flow<List<T>>,
// Bindings
itemBindings: ItemBindings,
// Diff callback factory. You can use custom or default provided in library.
// Default check if item is implements Diffable interface or compare by 'equals' calling
// So you can implement Diffable or use data classes from Kotlin
diffCallbackFactory: ((old: List<T>, new: List<T>) -> DiffCallback<T>) = diffCallback()
) = addSetter(items) { setItems(it, itemBindings, diffCallbackFactory) }
inline fun <T : Any, reified A : StandaloneRecyclerAdapter<T>> RecyclerView.setItems(
items: Flow<List<T>>,
itemBindings: ItemBindings,
// Here you can provide Custom Adapter extends StandaloneRecyclerAdapter, if you purpose more
// complex, than just adapter.
crossinline adapterFactory: (List<T>) -> A? = { null },
noinline diffCallbackFactory: ((old: List<T>, new: List<T>) -> DiffCallback<T>) = diffCallback()
) = addSetter(items) { setItems( it, itemBindings, adapterFactory, diffCallbackFactory) }
implementation 'io.github.iamthevoid.reactivebinding:core:1.0.0-beta12'
implementation 'io.github.iamthevoid.reactivebinding:rx2-core:1.0.0-beta12'
Optional:
- Recycler View
implementation 'io.github.iamthevoid.reactivebinding:core-recycler:1.0.0-beta12'
implementation 'io.github.iamthevoid.reactivebinding:rx2-recycler:1.0.0-beta12'
- Swipe Refresh Layout
implementation 'io.github.iamthevoid.reactivebinding:core-swiperefreshlayout:1.0.0-beta12'
implementation 'io.github.iamthevoid.reactivebinding:rx2-swiperefreshlayout:1.0.0-beta12'
- Material
implementation 'io.github.iamthevoid.reactivebinding:core-design:1.0.0-beta12'
implementation 'io.github.iamthevoid.reactivebinding:rx2-design:1.0.0-beta12'
implementation 'io.github.iamthevoid.reactivebinding:core:1.0.0-beta12'
implementation 'io.github.iamthevoid.reactivebinding:coroutines-core:1.0.0-beta12'
Optional:
- Recycler View
implementation 'io.github.iamthevoid.reactivebinding:core-recycler:1.0.0-beta12'
implementation 'io.github.iamthevoid.reactivebinding:coroutines-recycler:1.0.0-beta12'
- Swipe Refresh Layout
implementation 'io.github.iamthevoid.reactivebinding:core-swiperefreshlayout:1.0.0-beta12'
implementation 'io.github.iamthevoid.reactivebinding:coroutines-swiperefreshlayout:1.0.0-beta12'
- Material
implementation 'io.github.iamthevoid.reactivebinding:core-design:1.0.0-beta12'
implementation 'io.github.iamthevoid.reactivebinding:coroutines-design:1.0.0-beta12'