ReactiveX / RxKotlin

RxJava bindings for Kotlin

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

combineLatest/zip array overload to be typesafe

dmstocking opened this issue · comments

I wanted to use the version of combineLatest that takes an array/iterable. The only problem is that it gives you a Array<Any> which wasn't very useful. I thought it was kind of weird that RxJava doesn't have a version that gives you Array<T>. Was this because of the Java compiler couldn't figure out the two overloads? Anyway, would we want to provide a version in this project that correctly has the type T for the array? Maybe there is a good reason not to, but I found it really useful for what I was working on.

I made one myself by using both Kotlin and Java together to do what I think is the most efficient version while still being useful.

inline fun <reified T: Any,R> Observables.combineLatest(observables: Array<Observable<T>>,
                                                        crossinline combiner: (Array<T>) -> R)
        : Observable<R> =
        Observable.combineLatest(
                observables,
                { items -> combiner.invoke(ArrayUtils.cast(items, arrayOfNulls<T>(items.size))) },
                Observable.bufferSize())
    @NotNull
    public static <T> T[] cast(@NotNull Object[] items,
                               @NotNull T[] ts) {
        return Arrays.asList(items).toArray(ts);
    }

This uses Kotlin to actually create the array we are copying to. We can do this because the type is reified. The java then uses the Arrays.asList to create a simple list wrapper for the array and then copies it into the array we made. So we make one copy of the array and a list wrapper for the array.

In practice this will be the exact same thing as the generic parameter will be widened to Any. For example, combineLatest(listOf(just(1), just("hey")), values -> /* it's Array<Any> here */). Reification doesn't offer anything here.

It is explained in the JavaDoc:

Note on method signature: since Java doesn't allow creating a generic array with new T[], the implementation of this operator has to create an Object[] instead. Unfortunately, a Function<Integer[], R> passed to the method would trigger a ClassCastException.

As the apply method first tries to cast the Object[] into Integer[] which is not possible. I'd guess Kotlin has the same restrictions.

Man you guys are fast lol.

@JakeWharton I will admit it doesn't help when T is widened. My specific use case was using it like this. (I changed the name of some types)

combineLatest(viewModelRequests) { requests ->
    val loading = request.any { it is Request.Loading }
    val items = request.mapNotNull { it.successDataOrNull() }
            .flatmap{ it } // the type of "it" is List<ListItemViewModel>
    return@combineLatest ViewModel(loading, items)
}

I have an android recycler view that is populated from many sources and I combine them all as a list. It was easier then having the overload that took five observables. I basically had a list that I would unpack for combineLatest then immediately repack it back into a list.

@akarnokd Kotlin does have the same restrictions for non inline methods. For inline methods, you can make the type reified this is basically like the compiler automatically adding a parameter Class<T> clazz to your method. This let me make the array, new T[], in Kotlin. This is how I sort of cheated around it in Kotlin. I then pass it to Java to do the array copy. I could see that the RxJava project didn't want to add a overload that used Class<T> clazz if it wasn't going to be used very often.

This does the trick:

inline fun <reified T> Iterable<Single<T>>.zip(): Single<List<T>> {
    return Single.zip(this) {
        it.filterIsInstance<T>()
    }
}