I am accessing the server in my Android app. I want to get a list of my friends and a list of friend requests in different queries. They have to come at the same time. Then I want to show this data on the screen.
I tried to get data from two queries at using flatMap.
interactor.getColleagues() and interactor.getTest() returns the data type Observable<List<Colleagues>>
private fun loadColleaguesEmployer() {
if (disposable?.isDisposed == true) disposable?.dispose()
//запрос на список друзей
interactor.getColleagues(view.getIdUser() ?: preferences.userId)
.subscribeOn(Schedulers.io())
.flatMap {
interactor.getTest().subscribeOn(Schedulers.io())
.doOnNext {
result-> view.showTest(mapper.map(result))
}
}
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onNext = { result1 ->
//Обработка списка коллег работодателей
view.showColleagues(mapper.map(result1.filter { data -> data.typeFriend == "Работодатель" }))
},
onError = { it.printStackTrace() }
)
}
I want to get and process data from different queries at the same time.
Combining observable results of multiple async http requests with rxjava's Observable.zip.
public class Statistics {
public static void main(String[] args) {
List<Observable<ObservableHttpResponse>> observableRequests = Arrays.asList(
Http.getAsync("http://localhost:3001/stream"),
Http.getAsync("http://localhost:3002/stream"),
Http.getAsync("http://localhost:3003/stream"),
Http.getAsync("http://localhost:3004/stream"));
List<Observable<Stats>> observableStats = observableRequests.stream()
.map(observableRequest ->
observableRequest.flatMap(response ->
response.getContent()
.map(new EventStreamJsonMapper<>(Stats.class))))
.collect(toList());
Observable<List<Stats>> joinedObservables = Observable.zip(
observableStats.get(0),
observableStats.get(1),
observableStats.get(2),
observableStats.get(3),
Arrays::asList);
// This does not work, as FuncN accepts (Object...) https://github.com/Netflix/RxJava/blob/master/rxjava-core/src/main/java/rx/functions/FuncN.java#L19
// Observable<List<Stats>> joinedObservables = Observable.zip(observableStats, Arrays::asList);
joinedObservables
.take(10)
.subscribe(
(List<Stats> statslist) -> {
System.out.println(statslist);
double average = statslist.stream()
.mapToInt(stats -> stats.ongoingRequests)
.average()
.getAsDouble();
System.out.println("avg: " + average);
},
System.err::println,
Http::shutdown);
}
}
you can do it by simple operation zip like
private fun callRxJava() {
RetrofitBase.getClient(context).create(Services::class.java).getApiName()
.subscribeOn(Schedulers.single())
.observeOn(AndroidSchedulers.mainThread())
getObservable()
.flatMap(object : io.reactivex.functions.Function<List<User>, Observable<User>> {
override fun apply(t: List<User>): Observable<User> {
return Observable.fromIterable(t); // returning user one by one from usersList.
} // flatMap - to return users one by one
})
.subscribe(object : Observer<User> {
override fun onSubscribe(d: Disposable) {
showProgressbar()
}
override fun onNext(t: User) {
userList.add(t)
hideProgressBar()
}
override fun onError(e: Throwable) {
Log.e("Error---", e.message)
hideProgressBar()
}
override fun onComplete() {
userAdapter.notifyDataSetChanged()
}
})
}
this function combines your response from 2 queries
private fun getObservable(): Observable<List<User>> {
return Observable.zip(
getCricketFansObservable(),
getFootlaballFansObservable(),
object : BiFunction<List<User>, List<User>, List<User>> {
override fun apply(t1: List<User>, t2: List<User>): List<User> {
val userList = ArrayList<User>()
userList.addAll(t1)
userList.addAll(t2)
return userList
}
})
}
here is example of first observable
fun getCricketFansObservable(): Observable<List<User>> {
return RetrofitBase.getClient(context).create(Services::class.java).getCricketers().subscribeOn(Schedulers.io())
}
If both observables return the same data type and you don't mind mixing of both sources data - consider using Observable.merge()
For example:
Observable.merge(interactor.getColleagues(), interactor.getTest())
.subscribeOn(Schedulers.io())
.subscribe(
(n) -> {/*do on next*/ },
(e) -> { /*do on error*/ });
Note, that .merge() operator doesn't care about emissions order.
Zip combine the emissions of multiple Observables together via a
specified function
You can use Zip (rx Java) http://reactivex.io/documentation/operators/zip.html, some sudo code will be like this -
val firstApiObserver = apIs.hitFirstApiFunction(//api parameters)
val secondApiObserver = apIs.hitSecondApiFunction(//api parameters)
val zip: Single<SubscriptionsZipper>//SubscriptionsZipper is the main model which contains first& second api response model ,
zip = Single.zip(firstApiObserver, secondApiObserver, BiFunction { firstApiResponseModel,secondApiResponseModel -> SubscriptionsZipper(firstApiResponseModelObjectInstance, secondApiResponseModelObjectInstance) })
zip.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(object : SingleObserver<SubscriptionsZipper> {
override fun onSubscribe(d: Disposable) {
compositeDisposable.add(d)
}
override fun onSuccess(subscriptionsZipper: SubscriptionsZipper) {
Utils.hideProgressDialog()
//here you will get both api response together
}
override fun onError(e: Throwable) {
Utils.hideProgressDialog()
}
})
Hope it helps you .
Related
I have a code in my repository which has to call two endpoints. I have used Flowable.zip() but it doesn't seem to return a value. The Call doesn't fail even if there is no network available.
fun fetchRateRemote(): Flowable<ResultWrapper<List<RateModel>>> {
return Flowable.zip<Flowable<CurrenciesDTO>, Flowable<RateDTO>, ResultWrapper<List<RateModel>>>(
{
apiEndpoints.fetchCurrencies(key)
}, {
apiEndpoints.fetchRate(key)
}, { t1, t2 ->
val rateList = mutableListOf<RateModel>()
t2.subscribe { rate->
for((k,v) in rate.quotes ){
val currency = k.removeRange(0,3)
t1.subscribe {cur->
val currencyName = cur.currencies[currency]
if (currencyName != null) {
rateList.add(RateModel("$currencyName ($currency)", v.toString()))
}
}
}
}
ResultWrapper.Success(rateList)
}).subscribeOn(Schedulers.io())
}
I use a wrapper to mimic state and this is what I do in my viewmodel.
private fun fetchRates(){
disposable.add(repository.fetchRateRemote()
.startWith(ResultWrapper.Loading)
.onErrorReturn {
ResultWrapper.Error(it)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableSubscriber<ResultWrapper<List<RateModel>>>() {
override fun onComplete() {}
override fun onNext(rate: ResultWrapper<List<RateModel>>) {
rates.postValue(rate)
}
override fun onError(error: Throwable) {
error.printStackTrace()
}
})
)
}
I then observe rate in my activity via LiveData. The wrapper or the observation isn't the issue. It works with other calls, I do not know why the zip call doesn't work. I'm fairly new to RxJava so If I didn't implement something correctly in my repository please help correct me.
Okay! I made a lot of mistakes with the code in the repository above but I managed to fix it. Here's the solution. The Type arguments for the zip method was wrong! I didn't call the BiFunction argument properly too.
fun fetchRateRemote(): Flowable<ResultWrapper<List<RateModel>>> {
return Flowable.zip<CurrenciesDTO, RateDTO, ResultWrapper<List<RateModel>>>(
apiEndpoints.fetchCurrencies(key), apiEndpoints.fetchRate(key), BiFunction { t1, t2 ->
val rateList = mutableListOf<RateModel>()
for((k,v) in t2.quotes ){
val currencyCode = k.removeRange(0,3)
val currencyName = t1.currencies[currencyCode]
if (currencyName != null) {
rateList.add(RateModel("$currencyName ($currencyCode)", v.toString()))
}
}
ResultWrapper.Success(rateList)
}).subscribeOn(Schedulers.io())
}
I'm using the following code in my Android app to load data from a remote source into a local database cache.
inline fun <ResultType, RequestType> networkBoundResource(
crossinline query: () -> Flow<ResultType>,
crossinline fetch: suspend () -> RequestType,
crossinline saveFetchResult: suspend (RequestType) -> Unit,
crossinline onFetchSuccess: () -> Unit = { },
crossinline onFetchFailed: (Throwable) -> Unit = { },
crossinline shouldFetch: (ResultType) -> Boolean = { true }
) = flow {
val data = query().first()
val flow = if (shouldFetch(data)) {
emit(Resource.(data))
try {
// this could take a while, I want to keep getting updates meanwhile
saveFetchResult(fetch())
onFetchSuccess()
query().map { Resource.Success(it) }
} catch (t: Throwable) {
onFetchFailed(t)
query().map { Resource.Error(t, it) }
}
} else {
query().map { Resource.Success(it) }
}
emitAll(flow)
}
The query is a database query that keeps emitting database updates through emitAll until we call this method again.
The problem with this setup is that Resource.Loading only contains a "snapshot" of the current data (first()) and we won't receive any database updates until we get to the end of the try/catch block and call emitAll. But I would like to keep receiving database updates while Loading is still in progress. However, I can't just call emitAll on Resource.Loading because it would block the whole Flow.
Is there a way to call emitAll on Loading and then switch to Success/Error once the try block has finished?
I've only done simple testing on this to validate it, but it looks like you can listen to the query and emit any/all data it propagates in a newly launched coroutine based on the outer Flow's context -- the other work in the function will continue, unblocked. Once that other work is done, the coroutine that's listening to the query can be cancelled. For example:
inline fun <ResultType, RequestType> networkBoundResource(
crossinline query: () -> Flow<ResultType>,
crossinline fetch: suspend () -> RequestType,
crossinline saveFetchResult: suspend (RequestType) -> Unit,
crossinline onFetchFailed: (Throwable) -> Unit = { },
crossinline shouldFetch: (ResultType) -> Boolean = { true }
): Flow<Resource<ResultType>> = flow {
emit(Resource.Loading())
val data = query().first()
val flow = if (shouldFetch(data)) {
val flowContext = currentCoroutineContext()
val loading: Job = coroutineScope {
launch(flowContext) {
query().map { Resource.Loading(it) }
.collect { withContext(flowContext) { emit(it) } }
}
}
try {
val request = fetch()
loading.cancel()
saveFetchResult(request)
query().map { Resource.Success(it) }
} catch (throwable: Throwable) {
loading.cancel()
onFetchFailed(throwable)
query().map { Resource.Error(throwable, it) }
}
} else {
query().map { Resource.Success(it) }
}
emitAll(flow)
}
Let me know if this works out!
Hi I have a rxjava flat map in which I want to call a coroutine usecase onStandUseCase which is an api call
Initially the use case was also rxjava based and it used to return Observable<GenericResponse> and it worked fine
now that I changed the use to be coroutines based it only returns GenericResponse
how can modify the flatmap to work fine with coroutines use case please
subscriptions += view.startFuellingObservable
.onBackpressureLatest()
.doOnNext { view.showLoader(false) }
.flatMap {
if (!hasOpenInopIncidents()) {
//THIS IS WHERE THE ERROR IS IT RETURNS GENERICRESPONSE
onStandUseCase(OnStandUseCase.Params("1", "2", TimestampedAction("1", "2", DateTime.now()))) {
}
} else {
val incidentOpenResponse = GenericResponse(false)
incidentOpenResponse.error = OPEN_INCIDENTS
Observable.just(incidentOpenResponse)
}
}
.subscribe(
{ handleStartFuellingClicked(view, it) },
{ onStartFuellingError(view) }
)
OnStandUseCase.kt
class OnStandUseCase #Inject constructor(
private val orderRepository: OrderRepository,
private val serviceOrderTypeProvider: ServiceOrderTypeProvider
) : UseCaseCoroutine<GenericResponse, OnStandUseCase.Params>() {
override suspend fun run(params: Params) = orderRepository.notifyOnStand(
serviceOrderTypeProvider.apiPathFor(params.serviceType),
params.id,
params.action
)
data class Params(val serviceType: String, val id: String, val action: TimestampedAction)
}
UseCaseCoroutine
abstract class UseCaseCoroutine<out Type, in Params> where Type : Any {
abstract suspend fun run(params: Params): Type
operator fun invoke(params: Params, onResult: (type: Type) -> Unit = {}) {
val job = GlobalScope.async(Dispatchers.IO) { run(params) }
GlobalScope.launch(Dispatchers.Main) { onResult(job.await()) }
}
}
startFuellingObservable is
val startFuellingObservable: Observable<Void>
Here is the image of the error
Any suggestion on how to fix this please
thanks in advance
R
There is the integration library linking RxJava and Kotlin coroutines.
rxSingle can be used to turn a suspend function into a Single. OP wants an Observable, so we can call toObservable() for the conversion.
.flatMap {
if (!hasOpenInopIncidents()) {
rxSingle {
callYourSuspendFunction()
}.toObservable()
} else {
val incidentOpenResponse = GenericResponse(false)
incidentOpenResponse.error = OPEN_INCIDENTS
Observable.just(incidentOpenResponse)
}
}
Note that the Observables in both branches contain just one element. We can make this fact more obvious by using Observable#concatMapSingle.
.concatMapSingle {
if (!hasOpenInopIncidents()) {
rxSingle { callYourSuspendFunction() }
} else {
val incidentOpenResponse = GenericResponse(false)
incidentOpenResponse.error = OPEN_INCIDENTS
Single.just(incidentOpenResponse)
}
}
The following code does not compile:
override fun storeConnections(connections: List<Connection>): Observable<List<Connection>> =
Observable.fromCallable<List<Connection>> {
appDao.storeConnections(connections.map {
mapper.toDb(it)})
}
The line with appDao.storeConnections indicates the following error:
Required List!
Found Unit
The storeConnections is done using Room:
#Dao
interface RoomDao {
#Insert(onConflict = REPLACE)
fun storeConnections(linkedInConnection: List<LinkedInConnectionEntity>)
}
The storeConnections is called from my rx stream:
val startPositions = BehaviorSubject.createDefault(0)
startPositions.flatMap { startPos -> App.context.repository.getConnections(startPos) }
.flatMap { connections -> Observable.fromCallable(App.context.repository.storeConnections(connections)) }
.doOnNext { ind -> startPositions.onNext(ind + 1) }
.subscribe({ ind -> println("Index $ind") })
How do I properly implement this fromCallable?
Given your reply to your question:
storeConnections is returning nothing. But I need to wrap it in an observable in order to push it down the stream. So maybe my question is how to wrap an API call with an Observable when that api call returns nothing.
I will answer how you can wrap it in an observable in order to push it down the stream:
.flatMap {
connections ->
App.context.repository.storeConnections(connections)
.andThen(Observable.just(connections))
}
Given that storeConnections returns a Completable:
override fun storeConnections(connections: List<Connection>): Completable =
Completable.fromAction {
appDao.storeConnections(connections.map { mapper.toDb(it) } )
}
}
If storeConnections returns "nothing", you could simply move the Completable.fromAction to your stream:
.flatMap {
connections ->
Completable.fromAction { App.context.repository.storeConnections(connections) }
.andThen(Observable.just(connections))
}
The key to getting it to work is using this:
return#fromCallable connections
So this is the corrected code:
override fun storeConnections(connections: List<Connection>): Observable<List<Connection>> =
Observable.fromCallable<List<Connection>> {
appDao.storeConnections(connections.map {
mapper.toDb(it)
})
return#fromCallable connections
}
And the rx stream that calls it:
val startPositions = BehaviorSubject.createDefault(0)
startPositions.flatMap { startPos -> App.context.repository.getConnections(startPos) }
.flatMap {
connections -> App.context.repository.storeConnections(connections)
}
.doOnNext {
connections -> startPositions.onNext(startPos++)
}
.subscribe({ ind -> println("Index $ind") })
I'm currently using the Android-ReactiveLocation Library (Github). The LastKnownLocationObservable (Code) is working as intended. I'm using a flatMap to fetch nearby stations from a db and (because of realm) I'm creating a model from the data. So I have a list of items and I'm creating the new Observable in flatMap with Observable.from(data).
Then I want to sort the locations, filter them and group them.
.toSortedList()
.flatMap { Observable.from(it) }
.filter { it.distance <= (maxDistance.toDouble() * 1000) }
.groupBy { //Group the stations in categories
if (it.distance <= maxDistance && it.favorite) {
"nearbyFavorite"
} else if (it.favorite) {
"outOfReachFavorite"
} else {
"nearby"
}
}
However the onComplete is never called when I subscribe to the Observable. The Observable just stalls at toSortedList().
The Subscribe:
.subscribe(object: Subscriber<GroupedObservable<String, NearbyLocationItem>>() {
override fun onNext(p0: GroupedObservable<String, NearbyLocationItem>?) {
val locationItems = ArrayList<NearbyLocationItem>()
p0.subscribe { loc ->
locationItems.add(loc)
}
locations.put(p0.key, locationItems)
}
override fun onCompleted() {
Log.d(javaClass.simpleName, "Never called")
}
override fun onError(p0: Throwable?) {
}
}