Search code examples
androidkotlingenericsvariadic-functions

Is there a way to preserve the types in a vararg parameter?


My question is it is possible to do something like:

fun <T, R> someFunction(vararg sources<out T>, doSomething: (vararg sources<out T>) -> R) {
    // do something here
}

So if I do something like:

someFunction(SomeType<A>(), SomeType<B>(), SomeType<C>()) { a: A, b: B c: C ->
    // do Something
}

Basically, the higher ordered function needs to require all the parameter types.

The reason why I'm asking this is that I would like to simplify code that are like this:

inline fun <T1, T2, R> MediatorLiveData<out R>.merge(source1: LiveData<out T1>, source2: LiveData<out T2>, crossinline merger: (T1?, T2?) -> R?) {
    addSource(source1) {
        this.value = merger.invoke(source1.value, source2.value)
    }
    addSource(source2) {
        this.value = merger.invoke(source1.value, source2.value)
    }
}

inline fun <T1, T2, T3, R> MediatorLiveData<out R>.merge(source1: LiveData<out T1>, source2: LiveData<out T2>, source3: LiveData<out T3>, crossinline merger: (T1?, T2?, T3?) -> R?) {
    addSource(source1) {
        this.value = merger.invoke(source1.value, source2.value, source3.value)
    }
    addSource(source2) {
        this.value = merger.invoke(source1.value, source2.value, source3.value)
    }
    addSource(source3) {
        this.value = merger.invoke(source1.value, source2.value, source3.value)
    }
}

inline fun <T1, T2, T3, T4, R> MediatorLiveData<out R>.merge(source1: LiveData<out T1>, source2: LiveData<out T2>, source3: LiveData<out T3>, source4: LiveData<out T4>, crossinline merger: (T1?, T2?, T3?, T4?) -> R?) {
    addSource(source1) {
        this.value = merger.invoke(source1.value, source2.value, source3.value, source4.value)
    }
    addSource(source2) {
        this.value = merger.invoke(source1.value, source2.value, source3.value, source4.value)
    }
    addSource(source3) {
        this.value = merger.invoke(source1.value, source2.value, source3.value, source4.value)
    }
    addSource(source4) {
        this.value = merger.invoke(source1.value, source2.value, source3.value, source4.value)
    }
}

Can anyone suggest something? Thanks in advance!


Solution

  • Try out the following function:

    fun <T, R> someFunction(vararg sources: LiveData<out T>, doSomething: (sources: Array<out LiveData<out T>>) -> R) {
        // ...
    
        doSomething(sources)
    }
    

    It seems we can't use modifier vararg in lambda expression doSomething, replacing it with Array will work.

    EDIT: So basically you will be able to do something like the following for different number of sources without creating additional functions like in your example:

    fun <T, R> someFunction(vararg sources: LiveData<out T>, doSomething: (sources: Array<out LiveData<out T>>) -> R) {
        sources.forEach {
            addSource(it) {
                doSomething.invoke(sources)
            }
        }
    }
    
    // Call someFunction with different number of args:
    val l1: LiveData<Int> = MutableLiveData()
    val l2: LiveData<String> = MutableLiveData()
    val l3: LiveData<String> = MutableLiveData()
    
    // Call with two args:
    someFunction(l1, l2) { sources: Array<out LiveData<out Any>> ->
        val data1 = sources[0] as LiveData<Int>
        val data2 = sources[1] as LiveData<String>
        // do your work here   
    }
    // Or Call with three args:
    someFunction(l1, l2, l3) { sources: Array<out LiveData<out Any>> ->
        val data1 = sources[0] as LiveData<Int>
        val data2 = sources[1] as LiveData<String>
        val data3 = sources[2] as LiveData<String>
        // do your work here   
    }