android rxjava2 zip vararg parameter - android

I m now working on about the rxjava2 in android kotlin . And try to write a function to zip multiple observable and observer. But it seems like some mistake. Can anyone help?
First , i try to write zip 2 observable and it works. but when I want to extend it to vararg , it fails.
fun <T> ApiSubscribeZip2(observable1: Observable<T>, observable2: Observable<T>, observer: Observer<List<T>>) {
Observable.zip(observable1, observable2, BiFunction<T, T, List<T>> { t1: T, t2: T ->
zipAdd(t1, t2)
})
?.subscribeOn(Schedulers.io())?.unsubscribeOn(Schedulers.io())?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(observer as Observer<in List<T>>)
}
fun <T> ApiSubscribeZipN(vararg observable: Observable<T>?, observer: Observer<List<T>>) {
Observable.zip(observable, Function<T, List<T>> { it ->
zipAdd(it)
})
}
private fun <T> zipAdd(vararg observableType: T): List<T> {
val list = ArrayList<T>()
for (ob in observableType) {
list.add(ob)
}
return list
}
the apisubscribezipN shows that None of the following function can be called with the arguments supplied.

You can use Observable.zipIterable like this:
fun <T> ApiSubscribeZipN(vararg observable: Observable<T>?, observer: Observer<List<T>>) {
Observable.zipIterable<T, List<T>>(observable.filterNotNull().toList(), { it.toList() as List<T>? }, false, 100)
.subscribeOn(Schedulers.io()).unsubscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.io())
.subscribe(observer)
}

What about this?
import io.reactivex.Observable
import io.reactivex.functions.Function
import org.junit.Test
fun <T> apiSubscribeZipN(vararg observable: Observable<T>?): Observable<List<T>> {
val filterNotNull = observable.filterNotNull()
return Observable.zip(filterNotNull, Function { inArr ->
inArr.map {
it as T
}
})
}
Test
#Test
fun whatever() {
val mergeWith1 = Observable.fromArray("test11", "test12").mergeWith(Observable.never())
val mergeWith2 = Observable.fromArray("test21", "test22").mergeWith(Observable.never())
apiSubscribeZipN(mergeWith1, mergeWith2)
.test()
.assertValues(listOf("test11", "test21"), listOf("test12", "test22"))
}

Related

Creating a Kotlin DSL to accept processing pipeline and deliver an end result

I want to try to write a DSL, which can launch multiple methods piped one after the other to give an end result of a specific type, lets call it “Result”. The intermediate results of each of the calls can have different types, lets call them for example “Type 1", “Type 2” etc. I tried to accomplish something like this with following code.
class PipelineBuilder<I, R> {
val steps = mutableListOf<suspend (Any?) -> Any?>()
var initialFunction: (suspend () -> I)? = null
fun startWith(step: suspend () -> I) {
initialFunction = step
}
inline fun <reified T> then(noinline step: suspend (T) -> Any) {
steps.add { step }
}
suspend fun execute(): Any? {
if (steps.isEmpty()) {
throw Exception("Your pipeline is empty.")
}
var result: Any? = initialFunction?.invoke()
if (steps.size == 1) {
return result
}
for (index in 0 until steps.size) {
result = steps[index].invoke(result)
}
return result
}
suspend fun <T> executeConcurrent(vararg steps: suspend () -> T): List<T> {
return steps.map { step ->
coroutineScope {
async { step() }
}
}.awaitAll()
}
}
suspend fun <I, R> pipeline(block: suspend PipelineBuilder<I, R>.() -> Unit): Any? {
val builder = PipelineBuilder<I, R>()
builder.block()
return builder.execute()
}
suspend fun type1Method(input: Int): Type1 {
delay(1000)
return Type1()
}
suspend fun type2Method(input: Type1): Type2 {
delay(1000)
return Type2()
}
suspend fun type3Method(input: Type2): Int {
delay(1000)
return 3
}
class Type1
class Type2
class Type3
fun main() {
runBlocking {
val result = pipeline<Type1, Int> {
startWith { type1Method(1) }
then<Type1> {
type2Method(it)
}
then<Type2> {
type3Method(it)
}
}
println(result)
}
}
The pipeline should be able to execute multiple steps concurrently as well and combine the results in a combined result. When calling execute, you should execute the entire pipeline and then get an end result.
When I execute the code above, I don't get a "3" as a result, but "Function2<com.unisoft.myapplication.Type2, kotlin.coroutines.Continuation<? super java.lang.Object>, java.lang.Object>
". Where did I make a mistake and is there a simpler way of achieving this?

Kotlin-flow: Suspension functions can be called only within coroutine body

I'm trying to understand how Kotlin Flow works with coroutines and I decided to work on a test app. I'm using an MVVM + clean architecture. Here's a snippet of my data layer
class QuestionListRepositoryImpl(
private val localDataStore: LocalDataStore,
private val remoteDataStore: RemoteDataStore,
private val mapper: DataToDomainMapper,
private val coroutineScopes: CoroutineScopes) : GetQuestionRepository {
override suspend fun getQuestions(): Either<Failure, Flow<List<QuestionDomainModel>>> {
coroutineScopes.io.launch {
remoteDataStore.fetchQuestions().map {
localDataStore.deleteAllQuestion(). // Error is here
localDataStore.saveQuestions(it.items) // and here
}
}
val concatMapper = flow {
emitAll(localDataStore.readQuestions().map { allDataModel ->
mapper.map(allDataModel)
})
}
return Either.Right(concatMapper.distinctUntilChanged())
}
}
Here's my dataStore interface:
interface LocalDataStore {
suspend fun saveQuestions(data: List<QuestionDataModel>)
suspend fun readQuestions(): Flow<List<QuestionDataModel>>
suspend fun deleteAllQuestion()
}
interface RemoteDataStore {
suspend fun fetchQuestions(): Either<Failure, QuestionListApiResponse>
}
I'm having an issue in the repository in the lines where deleteAllQuestion() and saveQuestions() are called. Putting the cursor on any of the 2 function calls shows me this error: Suspension functions can be called only within coroutine body
Can anyone point me to the right direction ? What am I missing here ?
EDIT The implementation of Either.map taken from the comments:
fun <T, L, R> Either<L, R>.map(fn: (R) -> (T)): Either<L, T> =
this.flatMap(fn.c(::right))
fun <T, L, R> Either<L, R>.flatMap(fn: (R) -> Either<L, T>): Either<L, T> =
when (this) {
is Either.Left -> Either.Left(failure)
is Either.Right -> fn(success)
}

NetworkBoundResource with Kotlin coroutines

Do you have any ideas how to implement repository pattern with NetworkBoundResource and Kotlin coroutines? I know we can launch a coroutine withing a GlobalScope, but it may lead to coroutine leak. I would like to pass a viewModelScope as a parameter, but it is a bit tricky, when it comes to implementation (because my repository doesn't know a CoroutineScope of any ViewModel).
abstract class NetworkBoundResource<ResultType, RequestType>
#MainThread constructor(
private val coroutineScope: CoroutineScope
) {
private val result = MediatorLiveData<Resource<ResultType>>()
init {
result.value = Resource.loading(null)
#Suppress("LeakingThis")
val dbSource = loadFromDb()
result.addSource(dbSource) { data ->
result.removeSource(dbSource)
if (shouldFetch(data)) {
fetchFromNetwork(dbSource)
} else {
result.addSource(dbSource) { newData ->
setValue(Resource.success(newData))
}
}
}
}
#MainThread
private fun setValue(newValue: Resource<ResultType>) {
if (result.value != newValue) {
result.value = newValue
}
}
private fun fetchFromNetwork(dbSource: LiveData<ResultType>) {
val apiResponse = createCall()
result.addSource(dbSource) { newData ->
setValue(Resource.loading(newData))
}
result.addSource(apiResponse) { response ->
result.removeSource(apiResponse)
result.removeSource(dbSource)
when (response) {
is ApiSuccessResponse -> {
coroutineScope.launch(Dispatchers.IO) {
saveCallResult(processResponse(response))
withContext(Dispatchers.Main) {
result.addSource(loadFromDb()) { newData ->
setValue(Resource.success(newData))
}
}
}
}
is ApiEmptyResponse -> {
coroutineScope.launch(Dispatchers.Main) {
result.addSource(loadFromDb()) { newData ->
setValue(Resource.success(newData))
}
}
}
is ApiErrorResponse -> {
onFetchFailed()
result.addSource(dbSource) { newData ->
setValue(Resource.error(response.errorMessage, newData))
}
}
}
}
}
}
Update (2020-05-27):
A way which is more idiomatic to the Kotlin language than my previous examples, uses the Flow APIs, and borrows from Juan's answer can be represented as a standalone function like the following:
inline fun <ResultType, RequestType> networkBoundResource(
crossinline query: () -> Flow<ResultType>,
crossinline fetch: suspend () -> RequestType,
crossinline saveFetchResult: suspend (RequestType) -> Unit,
crossinline onFetchFailed: (Throwable) -> Unit = { Unit },
crossinline shouldFetch: (ResultType) -> Boolean = { true }
) = flow<Resource<ResultType>> {
emit(Resource.Loading(null))
val data = query().first()
val flow = if (shouldFetch(data)) {
emit(Resource.Loading(data))
try {
saveFetchResult(fetch())
query().map { Resource.Success(it) }
} catch (throwable: Throwable) {
onFetchFailed(throwable)
query().map { Resource.Error(throwable, it) }
}
} else {
query().map { Resource.Success(it) }
}
emitAll(flow)
}
The above code can be called from a class, e.g. a Repository, like so:
fun getItems(request: MyRequest): Flow<Resource<List<MyItem>>> {
return networkBoundResource(
query = { dao.queryAll() },
fetch = { retrofitService.getItems(request) },
saveFetchResult = { items -> dao.insert(items) }
)
}
Original answer:
This is how I've been doing it using the livedata-ktx artifact; no need to pass in any CoroutineScope. The class also uses just one type instead of two (e.g. ResultType/RequestType) since I always end up using an adapter elsewhere for mapping those.
import androidx.lifecycle.LiveData
import androidx.lifecycle.liveData
import androidx.lifecycle.map
import nihk.core.Resource
// Adapted from: https://developer.android.com/topic/libraries/architecture/coroutines
abstract class NetworkBoundResource<T> {
fun asLiveData() = liveData<Resource<T>> {
emit(Resource.Loading(null))
if (shouldFetch(query())) {
val disposable = emitSource(queryObservable().map { Resource.Loading(it) })
try {
val fetchedData = fetch()
// Stop the previous emission to avoid dispatching the saveCallResult as `Resource.Loading`.
disposable.dispose()
saveFetchResult(fetchedData)
// Re-establish the emission as `Resource.Success`.
emitSource(queryObservable().map { Resource.Success(it) })
} catch (e: Exception) {
onFetchFailed(e)
emitSource(queryObservable().map { Resource.Error(e, it) })
}
} else {
emitSource(queryObservable().map { Resource.Success(it) })
}
}
abstract suspend fun query(): T
abstract fun queryObservable(): LiveData<T>
abstract suspend fun fetch(): T
abstract suspend fun saveFetchResult(data: T)
open fun onFetchFailed(exception: Exception) = Unit
open fun shouldFetch(data: T) = true
}
Like #CommonsWare said in the comments, however, it'd be nicer to just expose a Flow<T>. Here's what I've tried coming up with to do that. Note that I haven't used this code in production, so buyer beware.
import kotlinx.coroutines.flow.*
import nihk.core.Resource
abstract class NetworkBoundResource<T> {
fun asFlow(): Flow<Resource<T>> = flow {
val flow = query()
.onStart { emit(Resource.Loading<T>(null)) }
.flatMapConcat { data ->
if (shouldFetch(data)) {
emit(Resource.Loading(data))
try {
saveFetchResult(fetch())
query().map { Resource.Success(it) }
} catch (throwable: Throwable) {
onFetchFailed(throwable)
query().map { Resource.Error(throwable, it) }
}
} else {
query().map { Resource.Success(it) }
}
}
emitAll(flow)
}
abstract fun query(): Flow<T>
abstract suspend fun fetch(): T
abstract suspend fun saveFetchResult(data: T)
open fun onFetchFailed(throwable: Throwable) = Unit
open fun shouldFetch(data: T) = true
}
#N1hk answer works right, this is just a different implementation that doesn't use the flatMapConcat operator (it is marked as FlowPreview at this moment)
#FlowPreview
#ExperimentalCoroutinesApi
abstract class NetworkBoundResource<ResultType, RequestType> {
fun asFlow() = flow {
emit(Resource.loading(null))
val dbValue = loadFromDb().first()
if (shouldFetch(dbValue)) {
emit(Resource.loading(dbValue))
when (val apiResponse = fetchFromNetwork()) {
is ApiSuccessResponse -> {
saveNetworkResult(processResponse(apiResponse))
emitAll(loadFromDb().map { Resource.success(it) })
}
is ApiErrorResponse -> {
onFetchFailed()
emitAll(loadFromDb().map { Resource.error(apiResponse.errorMessage, it) })
}
}
} else {
emitAll(loadFromDb().map { Resource.success(it) })
}
}
protected open fun onFetchFailed() {
// Implement in sub-classes to handle errors
}
#WorkerThread
protected open fun processResponse(response: ApiSuccessResponse<RequestType>) = response.body
#WorkerThread
protected abstract suspend fun saveNetworkResult(item: RequestType)
#MainThread
protected abstract fun shouldFetch(data: ResultType?): Boolean
#MainThread
protected abstract fun loadFromDb(): Flow<ResultType>
#MainThread
protected abstract suspend fun fetchFromNetwork(): ApiResponse<RequestType>
}
I am new to Kotlin Coroutine. I just come across this problem this week.
I think if you go with the repository pattern as mentioned in the post above, my opinion is feeling free to pass a CoroutineScope into the NetworkBoundResource. The CoroutineScope can be one of the parameters of the function in the Repository, which returns a LiveData, like:
suspend fun getData(scope: CoroutineScope): LiveDate<T>
Pass the build-in scope viewmodelscope as the CoroutineScope when calling getData() in your ViewModel, so NetworkBoundResource will work within the viewmodelscope and be bound with the Viewmodel's lifecycle. The coroutine in the NetworkBoundResource will be cancelled when ViewModel is dead, which would be a benefit.
To use the build-in scope viewmodelscope, don't forget add below in your build.gradle.
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-alpha01'

How I can test what paged list return error during response?

I am trying to test error in PagedList's data source (when load new group of data). My DataSource looks like that:
package com.ps.superheroapp.ui.character_screen.list
import androidx.paging.PositionalDataSource
import com.ps.superheroapp.api.MarvelApiService
import com.ps.superheroapp.objects.SchedulerNames
import io.reactivex.Scheduler
import io.reactivex.disposables.CompositeDisposable
import javax.inject.Named
class CharactersDataSource(
private val compositeDisposable: CompositeDisposable,
private val marvelApi: MarvelApiService,
#Named(SchedulerNames.MAIN) private val scheduler: Scheduler,
private val filter: Filter
) : PositionalDataSource<Character>() {
var events: SourcedDataEventsHandler? = null
override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<Character>) {
compositeDisposable.add(marvelApi.searchCharacter(params.pageSize, 0, filter.searchQuery)
.observeOn(scheduler)
.doOnSubscribe {
events?.onLoadStarted()
}
.subscribe({
callback.onResult(it.data.results ?: arrayListOf(), 0)
events?.onLoadFinishedSuccessfully()
}, {
events?.onLoadFinishedWithError(it)
})
)
}
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<Character>) {
compositeDisposable.add(
marvelApi.searchCharacter(params.loadSize, params.startPosition, filter.searchQuery)
.observeOn(scheduler)
.doOnSubscribe {
events?.onLoadStarted()
}
.subscribe({
callback.onResult(it.data.results ?: arrayListOf())
events?.onLoadFinishedSuccessfully()
}, {
events?.onLoadFinishedWithError(it)
})
)
}
}
If I will just mock events handler class and during test will call methods from it, this test will not test anything.
I searched ways or best practice to test this type of behaviour but I didn't found something.
My test looks like that:
#Test
fun should_show_network_error_when_screen_data_cannot_be_loaded_because_of_internet_connection() {
`when`(connectivityChecker.isOffline()).thenReturn(true)
//logic to imitate error during loading from data source
vm.fetchCharacters()
Assert.assertEquals(ErrorType.NETWORK, vm.error.get())
}
Could you please give me some advice or architectural example, or example of unit test to test this.
Thanks in advance
I found next solution which is work:
I changed callback interface on PublishSubject in Data Source:
class CharactersDataSource(
private val compositeDisposable: CompositeDisposable,
private val marvelApi: MarvelApiService,
#Named(SchedulerNames.MAIN) private val scheduler: Scheduler,
val filter: Filter
) : PositionalDataSource<Character>() {
private val onEvent = PublishSubject.create<CharacterLoadEvent>()
override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<Character>) {
compositeDisposable.add(marvelApi.searchCharacter(params.pageSize, 0, filter.searchQuery)
.observeOn(scheduler)
.doOnSubscribe {
onEvent.onNext(CharacterLoadEvent.LOAD_STARTED)
}
.subscribe({
callback.onResult(it.data.results ?: arrayListOf(), 0)
onEvent.onNext(CharacterLoadEvent.LOADED)
}, {
onEvent.onNext(CharacterLoadEvent.ERROR)
})
)
}
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<Character>) {
compositeDisposable.add(
marvelApi.searchCharacter(params.loadSize, params.startPosition, filter.searchQuery)
.observeOn(scheduler)
.doOnSubscribe {
onEvent.onNext(CharacterLoadEvent.LOAD_STARTED)
}
.subscribe({
callback.onResult(it.data.results ?: arrayListOf())
onEvent.onNext(CharacterLoadEvent.LOADED)
}, {
onEvent.onNext(CharacterLoadEvent.ERROR)
})
)
}
fun observeCharactersLoadEvents(): Observable<CharacterLoadEvent> = onEvent
}
and in tests mock behaviour of this publisher.
Couple of tests looks like:
#Test
fun should_show_network_error_when_screen_data_cannot_be_loaded_because_of_internet_connection() {
`when`(connectivityChecker.isOffline()).thenReturn(true)
`when`(interactor.observeCharactersLoadEvents()).thenReturn(Observable.just(CharacterLoadEvent.ERROR))
`when`(interactor.getCharacters()).then {
TestPageList.get<Character>(listOf())
}
vm.fetchCharacters()
Assert.assertEquals(ErrorType.NETWORK, vm.error.get())
}
#Test
fun should_show_general_error_when_screen_data_cannot_be_loaded_because_of_unknown_error() {
`when`(connectivityChecker.isOffline()).thenReturn(false)
`when`(interactor.observeCharactersLoadEvents()).thenReturn(Observable.just(CharacterLoadEvent.ERROR))
vm.fetchCharacters()
Assert.assertEquals(ErrorType.GENERAL, vm.error.get())
}
#Test
fun should_filter_character_list_when_user_enter_text_in_search_field() {
`when`(interactor.observeCharactersLoadEvents()).thenReturn(Observable.just(CharacterLoadEvent.LOAD_STARTED))
`when`(interactor.getCharacters()).then {
TestPageList.get<Character>(listOf())
}
vm.searchQuery.value = "Hulk"
Mockito.verify(interactor, times(1)).getCharacters("Hulk")
}
This can help to test behaviour of DataSource and it works for me.
I will also be very appreciate for any idea how to improve this Architecture/Approach/Test

using android pagination library with items in Memory

i try to implement the pagination library , using rxJava , first of all , i call the NetworkApi to load the full data , then i want to use the pagiantion with the full loaded data, how can i do it with the library , i am trying to use the ItemKeyedDataSource Class , but please , do i need always to pass the element size to my api call or i can work only with the in memory loaded data ?
this is my api call :
public fun getMembersPagination(): MutableLiveData<ResultContainer<PagedList<MembersModel.Data.Member?>>> {
disposable = client.getMembersPaged()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retry(0)
.subscribe(
{ result ->
onRetrieveUserData(result)
},
{ error -> onRetrieveUserDataError(error) }
)
return pagedList
}
i don't treat the pagiantion from my Api
this is the api :
#GET("members")
fun getMembersPaged(): Observable<PagedList<MembersModel.Data.Member?>>
ItemKeyedDataSource code :
class MembersPaginationDataSource(private val memberId: Int)
: ItemKeyedDataSource<Int, MembersModel.Data.Member?>() {
val client by lazy {
RetrofitClient.RetrofitClient()
}
var disposable: Disposable? = null
private var allMembers = MutableLiveData<PagedList<MembersModel.Data.Member?>>()
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<MembersModel.Data.Member?>) {
getMembersPagination().observe()
}
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<MembersModel.Data.Member?>) {
}
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<MembersModel.Data.Member?>) {
}
override fun getKey(item: MembersModel.Data.Member): Int = item.id!!
public fun getMembersPagination(): MutableLiveData<PagedList<MembersModel.Data.Member?>> {
disposable = client.getMembersPaged()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retry(0)
.subscribe(
{ result ->
onRetrieveUserData(result)
},
{ error -> onRetrieveUserDataError(error) }
)
return allMembers
}
private fun onRetrieveUserData(membersModel: PagedList<MembersModel.Data.Member?>?) {
allMembers.postValue(membersModel)
}
private fun onRetrieveUserDataError(error: Throwable) {
allMembers.postValue(null)
}
}
i stop at that point

Categories

Resources