Last active
May 21, 2017 10:39
-
-
Save charlag/725d8790eee956a397ade890c31ca69f to your computer and use it in GitHub Desktop.
Helper to reduce boilderplate when creating presenters / viewModels.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.charlag.rx.inputs | |
import rx.Observable | |
import rx.Subscription | |
import rx.subjects.PublishSubject | |
import rx.subscriptions.CompositeSubscription | |
import java.lang.reflect.InvocationHandler | |
import java.lang.reflect.Method | |
import java.lang.reflect.Proxy | |
/** | |
* Holder for the [Observable] inputs. Creates subjects for you so you don't have to create a | |
* subject for every view input. | |
* Usually when there are reactive relationships between presenter and view, presenter has to | |
* create a subject for every input of the view because view can be detached in every moment but we | |
* don't want to terminate our observables. Also presenter should take care of not forwarding | |
* any of the onCompleted/onError events to the subject (or use RxRelay). | |
* This snippet does exactly that. It lets you create a proxy for your inputs which is backed | |
* by the subjects and never terminates. It also subscribes to all of the [Observable] inputs | |
* at once. You can describe all your logic/observables in your presenter's init and let | |
* this holder to take care of forwarding any observables from the view. | |
* | |
* It doesn't use kotlin-reflect to avoid heavy dependencies. It's written for RxJava 1.* but can | |
* be easily ported to RxJava 2.*. | |
* | |
* @param T type of input | |
*/ | |
interface InputsHolder<T : Any> { | |
/** | |
* Contains an object what implements [T] using subjects. | |
*/ | |
val proxiedInputs: T | |
/** | |
* For every method that returns [Observable] and doesn't have have parameters it forwards | |
* onNext events to the proxy. | |
* @param inputs to forward to the proxy | |
*/ | |
fun subscribe(inputs: T) | |
/** | |
* Unsubscribes from the input so no more events from any added inputs will be forwarded to the | |
* proxy. New inputs will be forwarded. | |
*/ | |
fun clear() | |
} | |
/** | |
* Creates a new [InputsHolder] instance for the given [klass]. | |
* It scans for every method without arguments and with return type of | |
* [Observable] (or it's subtype) and adds it to the [InputsHolder] (so, Kotlin properties work as | |
* well becasause they're just getters for Java). All calls to such methods will be intercepted | |
* and a subject from the holder will be returned instead of an actual value. | |
* @param klass class of which proxy will be created | |
* @param T type of the input | |
* @return holder for the inputs | |
*/ | |
fun <T : Any> rxInputOf(klass: Class<T>): InputsHolder<T> { | |
val observableMethods = klass.declaredMethods.filter { | |
Observable::class.java.isAssignableFrom(it.returnType) && | |
it.parameterTypes.isEmpty() | |
} | |
val subjects = mutableMapOf<Method, PublishSubject<Any>>() | |
observableMethods | |
.forEach { method -> | |
subjects[method] = PublishSubject.create() | |
} | |
val handler = InvocationHandler { proxy: Any, method: Method, args: Array<out Any>? -> | |
subjects[method] ?: method.invoke(proxy, args) | |
} | |
@Suppress("UNCHECKED_CAST") | |
val inputs = Proxy.newProxyInstance(klass.classLoader, arrayOf(klass), handler) as T | |
return InputsHolderImpl(inputs, observableMethods.toList(), subjects) | |
} | |
private class InputsHolderImpl<T : Any>(override val proxiedInputs: T, | |
private val methods: List<Method>, | |
private val subjects: Map<Method, PublishSubject<Any>>) | |
: InputsHolder<T> { | |
val subscription = CompositeSubscription() | |
override fun subscribe(inputs: T) { | |
methods.forEach { method -> | |
@Suppress("UNCHECKED_CAST") | |
val observable = method.invoke(inputs) as Observable<Any> | |
val subject = subjects[method] | |
observable.subscribe { subject?.onNext(it) }.addTo(subscription) | |
} | |
} | |
override fun clear() { | |
subscription.clear() | |
} | |
} | |
private fun Subscription.addTo(compositeSubscription: CompositeSubscription) = | |
compositeSubscription.add(this) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment