In the below code, I am trying to add the body for the .subscribe(). I tried to add the lambda notation but it never worked. Would you please tell me how to implement the .subscribe() method?
Given that, the setupCommRequestService() returns Single<..>
code:
setupCommRequestService()?.
flatMap {
it.getAllPhotos()
.map {
Observable.fromIterable(it)
.map {
it
}
}
.toSortedList()
}
?.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(
)
There are 4 implementations for subscribe method according Single documentation. In a simple approach, you should implement a strategy for both onSucess and onError. therefor you should use the subscribe method either by passing a BiConsumer or 2 Consumer one for onSucess case and one for onError.
using BiConsumer in lambda:
val disposable = Single.just(1)
.subscribe { success, failure ->
/* whichever is not null */
}
or using 2 Consumer in lambda:
val disposable = Single.just(1)
.subscribe({ success ->
/* success */
}, { failure ->
/* failure */
})
Related
I want to write a function that automatically subsbcribes to RxJava's Flowable<T> and get the resulting data. This data will then be passed as an argument to another method that does the processing. I am struggling with Kotlin's extension function syntax and generics.
I want to convert this call:
val scheduler = Schedulers.newThread()
disposable.add(
viewModel.getExams().subscribeOn(scheduler)
.observeOn(scheduler)
.subscribe({ exams ->
exams.forEach {
getSubjectOfExam(it, Schedulers.newThread())
}
}, { error ->
Log.e(
"OverviewFragment",
"Unable to fetch list, $error"
)
})
)
which is very lengthy in my Activity code, to a method that returns the data that I want to process.
In this case I'd like a list of exams (List<Exam>) passed into the argument of getSubjectOfExam(), which is the method for the list processing.
My function so far, which compiles but does not work at all:
/**
* General subscription of items in a Flowable list
* #param f method to be executed when list is loaded
* #param scheduler scheduling units
*/
private fun Flowable<out List<Any>>.listSubscribe(
f: (List<Any>) -> Unit,
scheduler: Scheduler
) {
disposable.add(
this.subscribeOn(scheduler)
.observeOn(scheduler)
.subscribe({
f(it)
}, { error ->
Log.e(
"OverviewFragment",
"Unable to fetch list, $error"
)
})
)
}
it will be called like so:
viewModel.getExams().listSubscribe({ resultData ->
resultData.forEach {
val exam = it as Exam
getSubjectOfExam(exam, Schedulers.newThread())
}
}, Schedulers.newThread())
So yeah, I tried to make an extension function and passing a function as one of its arguments (called a higher-order function I believe).
With my method, the getSubjectOfExam doesn't get called at all. Is there something I'm missing?
I'll be subscribing to Flowable's all the time in my Activity so this function will really help me.
I tried your code and it seems it is working okay. Is there any chance that viewModel.getExams() or getSubjectOfExam() is not working?
Also I could suggest few optimizations:
protected fun <T> Flowable<out List<T>>.listSubscribe(
f: (List<T>) -> Unit,
scheduler: Scheduler
) {
disposable.add(
this.subscribeOn(scheduler)
.observeOn(scheduler)
.subscribe(f, { error ->
Log.e(
"OverviewFragment",
"Unable to fetch list, $error"
)
})
)
}
Then you won't need type conversion:
viewModel.getExams().listSubscribe({ resultData ->
resultData.forEach {
getSubjectOfExam(exam, Schedulers.newThread())
}
}, Schedulers.newThread())
In fact you can replace List<T> with just T and make it work with any types. Also, observing and subscribing with same scheduler doesn't make a lot of sense to me. I think you can remove.observeOn completely and the code will still observe on the same scheduler you put in .subscribeOn
So, I have a Repository class, which has a search() method which creates a zipped Single and returns data to a listener.
Inside of this method, I need to call 4 services and zip their results. The services return different data types, from which I build the SearchResult object.
The method looks like this:
fun search() {
Single.just(SearchResult.Builder())
.zipWith(
service1.search()
.subscribeOn(Schedulers.io())
.onErrorReturnItem(emptyList()),
{ result, data -> result.apply { this.data1 = data } })
.zipWith(
service2.search()
.subscribeOn(Schedulers.io())
.onErrorReturnItem(emptyList()),
{ result, data -> result.apply { this.data2 = data } })
// Other two services done in the same way
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ listener.onSearchComplete(it.build()) },
{ listener.onSearchFailed() })
}
The implementation of search() in the services looks like this:
fun search(): Single<List<DataTypeX>> =
Single.create<List<DataTypeX>> { subscriber ->
makeNetworkRequest()
?.let{
//logic omitted for clarity
subscriber.onSuccess(it)
} ?: subscriber.onError(IllegalStateException())
The problem is the last line. When this network call fails and the response is null, the IllegalStateException will be propagated and crash the app, instead of being silently caught by onErrorReturnItem(emptyList()).
Is there any reason for this? Am I misunderstanding the idea behind onErrorReturnItem()?
I'm using a travel API which I first hit with a request to create a session and then I use the session URL returned from that URL to then call until its status parameter returns UpdatesComplete.
Here's what I have so far:
lateinit var pollUrl: String
travelInteractor.createSession("LHR", "AKL", "2018-04-20", "2018-04-22")
.doOnSubscribe {
loading.postValue(true)
}
.flatMap { url ->
pollUrl = url
travelInteractor.pollResults(pollUrl)
.retryWhen {
it.delay(1000, TimeUnit.MILLISECONDS)
}
}
.doOnNext {
if (it.status != "UpdatesComplete") travelInteractor.pollResults(pollUrl)
.retryWhen {
it.delay(1000, TimeUnit.MILLISECONDS)
}
}
.subscribe({
// Subscription stuff
)}
What's currently happening is that it will call doOnNext() and then it will make the network poll but I won't be capturing the subscription and also won't chain another poll. Is there a more efficient way I can be writing this?
Solution
Thanks to iagreen I managed to achieve this with:
lateinit var pollUrl: String
travelInteractor.createSession("LHR", "AKL", "2018-04-20", "2018-04-22")
.doOnSubscribe {
loading.postValue(true)
}
.flatMap { url ->
travelInteractor.pollResults(url)
.retryWhen {
it.delay(1000, TimeUnit.MILLISECONDS)
}
.repeatWhen {
it.delay(1000, TimeUnit.MILLISECONDS)
}
.filter {
it.itineraries.map { ... } // Use response here appropriately and then check status
it.status == "UpdatesComplete"
}
.take(1)
}
.subscribe({
// Subscription stuff
)}
I am assuming your pollResults(url) method returns a Single or an Observable that behaves like a single -- it returns one result and then onComplete. If that is truly the case, you can use repeatWhen to retry the request on success and retryWhen to retry on error. See the code below.
skyScannerInteractor.createSession("LHR", "AKL", "2018-04-20", "2018-04-22")
.doOnSubscribe {
loading.postValue(true)
}
.flatMap { url ->
skyScannerInteractor.pollResults(url)
.repeatWhen { complete -> complete.delay(1, TimeUnit.SECONDS) }
.retryWhen { errors -> errors.delay(1, TimeUnit.SECONDS) }
.filter({ it.status == "UpdatesComplete" })
.take(1) // Take the first valid value and complete
}
.subscribe({
// Subscription stuff
)}
A little explanation -
repeatWhen/retryWhen will try the request every second.
filter will cause elements with the wrong status to be ignored.
When you get the first status == "UpdatesComplete" element, take(1) will emit that value and complete -- this will have the effect of cancelling the retries.
Note: In the case of an error, retrying a network request forever is usually the wrong thing to do. I recommend you modify the retryWhen above to suit you use case to terminate in the event of network failure. For example, you could retry three times and then propagate the error. See this article for some examples on how you could do that. It is also a good reference on repeatWhen/retryWhen.
retry() and retryWhen() both respond to an onError event in the Observable, that is why it isn't actually retrying; you're not receiving those onError events in your pollResults() Observable. Right now, your retrying code doesn't actually depend on the JSON response.
There are two ways I would think about going about this:
Throw an exception in your pollResults() Observable if the JSON response is unsatisfactory. This should trigger the retryWhen(). You'll need to test for it in the Observable somewhere.
Reorganize your Observable like this:
`
lateinit var pollUrl: String
skyScannerInteractor.createSession("LHR", "AKL", "2018-04-20", "2018-04-22")
.doOnSubscribe {
loading.postValue(true)
}
.flatMap { url ->
pollUrl = url
skyScannerInteractor.pollResults(pollUrl)
}
.doOnNext {
if (it.status != "UpdatesComplete") {
throw IOException("Updates not complete.") //Trigger onError
}
}
.retryWhen { //Retry the Observable (createSession) when onError is called
it.delay(1000, TimeUnit.MILLISECONDS)
}
.subscribe({
// Will give result only when UpdatesComplete
}
My Question is probably more of the conceptual nature.
I get that by the Observable contract my Observable will not emit any more items after onComplete or onError is called.
But I'm using the RxBindings for Android and therefore it's not "my Observable" but the click on a Button that emits items.
fun observeForgotPasswordButton(): Disposable {
return view.observeForgotPasswordButton()
.flatMap {
authService.forgotPassword(email).toObservable<Any>()
}
.subscribe({
// on next
Timber.d("fun: onNext:")
}, { error ->
// on error
Timber.e(error, "fun: onError")
}, {
// onComplete
Timber.d("fun: onComplete")
})
}
observeForgotPasswordButton() returns an Observable
fun observeForgotPasswordButton(): Observable<Any> = RxView.clicks(b_forgot_password)
The problem is that authService.forgotPassword(email) is a Completable and it will call either onComplete or onError both of which lead to the fact that I cannot reuse the button anymore since the subscription ended.
Is there a way to circumvent this behavior?
Because in an error occurs I would like to be able to retry.
Also I would like it to be possible to send more then one password forgotten emails.
You can use the retry() and repeat() operators to automatically resubscribe to the original Observable (or Completable).
fun observeForgotPasswordButton(): Disposable {
return view.observeForgotPasswordButton()
.flatMap {
authService.forgotPassword(email).toObservable<Any>()
}
.repeat() // automatically resubscribe on completion
.retry() // automatically resubscribe on error
.subscribe({
// on next
Timber.d("fun: onNext:")
}, { error ->
// on error
Timber.e(error, "fun: onError")
}, {
// onComplete
Timber.d("fun: onComplete")
})
}
I'm trying to implement a basic RxJava chaining call where
Getting the user name and photo from Facebook Graph API
Use that information to then register with my Backend service
So I try using flatMap like below
getProfileFromFacebook().flatMap { user -> userRepository.createUser(user) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ Log.d("test", "onNext") },
{ Log.d("test", "onError") },
{ Log.d("test", "onComplete") })
Strangely, none of the logging in the subscriber get printed out.
So I tried replace getProfleFromFacebook() with Observable.just(User()). This time it works, onNext get printed out. So I guess the problem is at getProfleFromFacebook(). Here is its implementation
fun getProfileFromFacebook(): Observable<User> {
return Observable.create<User>({ emitter ->
val request = GraphRequest.newMeRequest(AccessToken.getCurrentAccessToken()) {
_, _ ->
emitter.onNext(User())
emitter.onCompleted()
}
val parameters = Bundle()
parameters.putString("fields", "name,picture")
request.parameters = parameters
request.executeAsync()
}, Emitter.BackpressureMode.BUFFER)
}
I've checked by adding more logging and found that the flatMap function does get the event with a user object but for some reason, it doesn't seem to actually execute the Observable return from userRepository.createUser().
If I change the Graph API call from executeAsync() to executeAndWait(), it works. Why is that?
What could I possibly do wrong here?