How to until wait 2 parallel retrofit calls both finish?

Viewed 105

I want to call 2 retrofit services in parallel and then do an action only when both of them finished, but I don't seem to figuer it out how.

I have a viewModel where I have defined my services:

var config= List<Configuration>
fun getClientProducts() {
    getClientClientConfigUseCase
        .build(this)
        .executeWithError({ config ->
             config = config
        }, {
           
        })
}

var therapies = List<DtoTherapy>
fun getTherapies() {
    getTherapiesUseCase
        .build(this)
        .executeWithError({ config ->
             therapies = it
        }, {
            
        })
}

And then I want to call both services in parallel in my fragment:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
   
    setupUi(view)
    loadUserData()

    viewModel.getClientProducts()
    viewModel.getTherapies()
}

And when both variables config and therapies have the value do an action. But as I said maybe one service take 1 sec to respond and another 4 secs, and I want only to perfom an action when both have finished. Any help with be appreciated.

Here is the class I use to build the use case call:

abstract class SingleUseCase<T> : UseCase() {
    private lateinit var single: Single<T>
    private lateinit var useCaseInterface: UseCaseInterface
    private var withLoader: Boolean = false
    private var withErrorMessage: Boolean = false

    internal abstract fun buildUseCaseSingle(): Single<T>

    fun build(useCaseInterface: UseCaseInterface): SingleUseCase<T> {
        this.withLoader = false
        this.withErrorMessage = false

        this.useCaseInterface = useCaseInterface
        this.single = buildUseCaseSingle()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .doAfterSuccess { useCaseInterface.onSuccess(it) }
        return this
    }

    fun withLoader(): SingleUseCase<T> {
        this.withLoader = true
        return this
    }

    fun withErrorMessage(): SingleUseCase<T> {
        this.withErrorMessage = true
        return this
    }

    fun single(): Single<T> {
        return this.single
    }

    fun execute(onSuccess: ((t: T) -> Unit)) {
        useCaseInterface.onPrepareRequest(withLoader)
        buildObservable(onSuccess)
    }

    private fun buildObservable(onSuccess: ((t: T) -> Unit)) {
        disposeLast()
        lastDisposable = single
            .doFinally { useCaseInterface.onFinishRequest(this.withLoader) }
            .subscribe(
                { onSuccess(it) },
                {
                    useCaseInterface.onError(mapError(it), withErrorMessage)
                })

        lastDisposable?.let {
            compositeDisposable.add(it)
        }
    }

    fun executeWithError(onSuccess: ((success: T) -> Unit), onError: ((error: ApiError ) -> Unit)) {
        useCaseInterface.onPrepareRequest(withLoader)
        buildObservable(onSuccess, onError)
    }

    private fun buildObservable(onSuccess: ((success: T) -> Unit), onError: ((error: ApiError ) -> Unit)) {
        disposeLast()
        lastDisposable = single
            .doFinally { useCaseInterface.onFinishRequest(this.withLoader) }
            .subscribe(
                { onSuccess(it) },
                {
                    onError(mapError(it))
                    useCaseInterface.onError(mapError(it), withErrorMessage)
                })

        lastDisposable?.let {
            compositeDisposable.add(it)
        }
    }

    private fun mapError(t: Throwable): ApiError {
        return if(t is HttpException) {
            val apiError = t.response()?.errorBody()?.string()
            try {
                ApiError (t.code(), t.response()?.errorBody()?.string(), Gson().fromJson(apiError, GenericError::class.java))
            } catch(e: Exception) {
                ApiError (-2, "Unkown error")
            }
        } else ApiError (-1, "Unkown error")
    }
}

And this is a specific usecase class:

class GetClientConfigUseCase @Inject constructor(private val repository: UserRepository) :
    SingleUseCase<ClientConfigResponse>() {

    override fun buildUseCaseSingle(): Single<ClientConfigResponse> {
        return repository.getUserConfig()
    }
}
1 Answers

I guess you need zip operation. With zip operation you can have a result of two observable in one place when both of them received data.

Observable<List<ClientProducts>> observable1 = ...
Observable<List<DtoTherapy>> observable2 = ...

Observable.zip(observable1, observable2, new BiFunction<List<ClientProducts>, List<DtoTherapy>, Result>() {

    @Override
    public Result apply(List<ClientProducts> products, List<DtoTherapy> therapies) throws Exception
    {
        // here you have both of your data
        // do operations on products and therapies
        // then return the result
        return result;
    }
});

Related