I have a specific task to get several packs of data from server by calling same request several times. While answers contains more flag - i have to recall this request.
It seems something like this:
fun getData(some params): Single<Response<DataEntity>>
//
repository.getData(params)
.flatMap {
if (it.body()?.more == false)
more = false
else (
// here i want to repeat my request
// repository.getData(params) to get more data
)
}
.flatMap {// here i want to get all the data from
//previous requests to save to db etc.
}
Maybe i have to use something like repeatWhen or repeautUntil operators but i can't find the solution for now. Please help!)
You can use the concatMap operator in a recursive way, and as exit condition return just the result:
Single<Response<DataEntity>> getDataAndContinue(params) {
return getData(params)
.concatMap(new Func1<Response<DataEntity>, Single<Response<DataEntity>>>() {
#Override
public Single<Response<DataEntity>> call(Response<DataEntity> response) {
if (!response.body().hasMore()) {
return Single.just(response);
}
return Single.just(response)
.concatWith(getDataAndContinue(params));
}
});
}
Related
In my Android Kotlin project, I call a webservice in a coroutine (myWebservice is just a custom class that manages webservice calls):
fun searchForItems(userInput: String)
{
CoroutineScope(Dispatchers.IO + Job()).launch {
val listOfItems = myWebService.call(userInput)
}
}
That method is called everytime a user types a character in an EditText, so the app calls a webservice that returns a list of items matching his request. But I want to optimize that.
Let's say that the user types the word: "apple". In order to minimise the number of webservice calls, here is what I want to achieve:
when the user types the first letter (a), the webservice is called
when the user types the next letters, there is no new webservice call as long as the first called hasn't returned (let's assume that he has enough time to type the next letters (pple))
when the first webservice call is done, a new call is done automatically with the new user input (apple)
What would be the best practices to achieve that?
Or is there a better way to minimize the number of webservice calls?
Thanks.
Using Kotlin coroutines I solved it like this:
class SomeViewModel : ViewModel() {
private var searchJob: Job? = null
fun search(userInput: String) {
searchJob?.cancel() // cancel previous job when user enters new letter
searchJob = viewModelScope.launch {
delay(300) // add some delay before search, this function checks if coroutine is canceled, if it is canceled it won't continue execution
val listOfItems = myWebService.call(userInput)
...
}
}
}
When user enters first letter search() function is called, coroutine is launched and Job of this coroutine is saved to searchJob. Then delay(300) function is called to wait for another user input before calling the WebService. If user enters another letter before 300 milliseconds expire search() function will be called again and previous coroutine will be cancelled using searchJob?.cancel() function and WebService will not be called in the first coroutine.
You need debouncing. Coroutines or not, you shouldn't call the web service with each letter for every active user. That will eventually DDOS the web service if your app is used by many people at once.
Since you are using Kotlin, instead of coroutines you can use Flow. It comes with a built in debounce method. Also the stream of letters is easily modelled as a flow. It would be something like this (I'm not sure this even runs, but you get the idea):
textFlow = flow {
myTextView.doOnTextChanged { text, start, count, after -> emit(text)}
}.debounce(1000)
The more complex alternative is RxJava's debounce operator.
You can also try LiveData with lupajz's debounce extension
Or you can also roll your own solution.
To optimize web service calls you can use StateFlow and its debounce operator. For example in ViewModel:
val inputFlow = MutableStateFlow("")
init {
inputFlow
.debounce(300) // filters out values that are followed by the newer values within the given timeout. The latest value is always emitted.
.filterNot { userInput -> userInput.isEmpty() } // filter the unwanted string like an empty string in this case to avoid the unnecessary network call.
.distinctUntilChanged() // to avoid duplicate network calls
.flowOn(Dispatchers.IO) // Changes the context where this flow is executed to Dispatchers.IO
.onEach { userInput -> // go through each filtered userInput
val listOfItems = myWebService.call(userInput)
// do sth with listOfItems
}
.launchIn(viewModelScope)
}
fun searchForItems(userInput: String) {
inputFlow.value = userInput
}
You can achieve the same functionality using Rx Java's debounce operator. every time you will enter a text in Edit Text Rx Java's debounce will call the webservice and quickly will produce result and if Again user enter another text it will then call the web service.
Please refer the below link for batter understaing , So you can modify your code and achieve same functinolaity
https://blog.mindorks.com/implement-search-using-rxjava-operators-c8882b64fe1d
RxSearchObservable.fromView(searchView)
.debounce(300, TimeUnit.MILLISECONDS)
.filter(new Predicate<String>() {
#Override
public boolean test(String text) {
if (text.isEmpty()) {
textViewResult.setText("");
return false;
} else {
return true;
}
}
})
.distinctUntilChanged()
.switchMap(new Function<String, ObservableSource<String>>() {
#Override
public ObservableSource<String> apply(String query) {
return dataFromNetwork(query)
.doOnError(throwable -> {
// handle error
})
// continue emission in case of error also
.onErrorReturn(throwable -> "");
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<String>() {
#Override
public void accept(String result) {
textViewResult.setText(result);
}
});
var myApiUpdateTask: Deferred<Unit>? = null
fun getApiResponse() = viewModelScope.launch {
myApiUpdateTask?.cancel()
myApiUpdateTask = async {
delay(500)
// TODO: your api request
}
}
Using this structure inside the viewmodel; You can wait for 500 milliseconds and send the request you will send consecutively.
I have an API call which verifies some status against an "Id". The API returns Single or error. I have a list of such Id's, Only one Id is valid to return success or none (all id's return error). What I need is, Iterate through each Id and skip the errors from API call, until either a success or end of the list. I am able to achieve this sequentially. However, I am trying to do the same, using ParallelFlowable.
It works fine when an Id returns success, But when there is no id which returns success (all ids fail), then it just skip all the errors from API, but does not notify the subscriber after all the ids are validated. I am not sure how to handle this.
// API call
fun getStatus(Id: String): Single<String> {
//... returns Single<String> or error
}
//Sequential flow, Working
fun getStatus(ids: List<String>): Single<String> {
Observable.fromIterable(ids)
.flatMapSingle { id ->
getStatus(id)
.onErrorResumeWith { singleSource ->
if (ids.last() == id)) { //If this is last item in list, return error
singleSource.onError(NoStatusFoundException())
} else {
// Skip errors until valid id is found or till the list reached end.
Flowable.empty<String>()
}
}
}.firstOrError()
}
// Parallel Flow, How to identify the list is completed and return NoStatusFoundException in case of all id's fail?
fun getStatus(ids: List<String>): Single<String> {
Flowable.fromIterable(ids)
.parallel()
.runOn(io())
.flatMap{ id -> getStatus(id).toFlowable()
.onErrorResumeWith { Flowable.empty<String>() }
}
.sequentialDelayError()
.firstOrError()
.onErrorResumeNext { Single.error(it) }
}
// Subscription
getStatus(listOf("1","2","3","4","5",))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscriber({ id->
// success
this is notified when an id is success
},
{ // error handler - Need help here
Never notified when all the id's fail?
})
I am able to resolve this issue by removing onErrorResumeWith { Flowable.empty<String>() } within flatMap and implementing RxJavaPlugins.setErrorHandler{...}.
sequentialDelayError() delays all the errors until all the rails have finished their task.
fun getStatus(ids: List<String>): Single<String> {
Flowable.fromIterable(ids)
.parallel()
.runOn(io())
.flatMap{ id -> getStatus(id).toFlowable()
}
.sequentialDelayError()
.firstOrError()
.onErrorResumeNext { Single.error(it) }
}
///
RxJavaPlugins.setErrorHandler{ it: Throwable ->
is UndeliverableException -> {...}
.....
}
You are returning Flowable.empty() that immediately completes the subscription. Taken from the docs:
Returns a Flowable that emits no items to the {#link Subscriber} and immediately invokes its {#link Subscriber#onComplete onComplete} method.
Maybe you can return Flowable.just("") or provide some expected argument incase of an error.
.onErrorResumeWith { Flowable.just("") }
The problem is in this line:
.onErrorResumeWith { Flowable.empty<String>() }
The parameter of onErrorResumeWith is a Publisher<out T>, not () -> Publisher<out T>. The Publisher interface happens to have a single method, void subscribe(Subscriber<? super T> s). As such, it is eligible for SAM conversion.
The lambda { Flowable.empty<String>() } is a perfectly valid (Subscriber<String>) -> Unit that ignores its single parameter, calls a method, and ignores the result. This compiles, but the result is for all practical purposes the same as Flowable.never().
Instead of a lambda, you need to pass Flowable.empty() directly into onErrorResumeNext():
.flatMap{ id -> getStatus(id).toFlowable()
.onErrorResumeWith(Flowable.empty<String>())
}
I'm confused about how to implement this in RxJava.
I would like to
take an object from my database
upload it
delete it from the database
take the next item from the database and repeat 2 and 3
complete when the database has no objects remaining
I know how to do this via loading all objects from the database at first and creating an Observable like this Observable.fromIterable(allMyDbObjects), however I would like to take objects one at a time, in case the database is updated while I'm uploading.
I can't figure out how to do this. I've looked at repeatUntil but it just seems to repeat instantly. Here is pseudocode for what I'm thinking:
getFirstDbObject()
.flatMapCompletable { obj ->
upload(obj)
.doOnComplete {
deleteFromDb(obj)
}
}
.repeatUntil {
// dbIsEmptyLogic.
// This doesn't work. I need to somehow call getFirstDbObject() again
}
Can anyone help?
Assuming getFirstDbObject() returns a Maybe, you can achieve this by mapping the result to a boolean (true if the database is empty, false if not) and then repeating the sequence until getFirstDbObject() returns empty and the stream completes.
getFirstDbObject()
.toObservable()
.flatMapSingle { obj ->
upload(obj)
.doOnComplete { deleteFromDb(obj) } // probably better to use .andThen(deleteFromDb(obj)) if delete also returns a completable
.toSingleDefault(false)
}
.defaultIfEmpty(true)
.repeat()
.takeUntil { isComplete ->
isComplete
}
This is a working solution in my code base.
val source = HashSet<String>()
source.add("a")
source.add("b")
source.add("c")
source.add("d")
source.add("e")
io.reactivex.Observable.just(Unit)
.flatMap { it ->
io.reactivex.Observable.fromCallable {
println("first flatmap print $it")
// uploadObj()
source.first()
}
}.flatMap {
// delete
io.reactivex.Observable.fromCallable {
source.remove(it)
println("second flatmap remove $it")
// delete object
}
}
.repeatUntil { source.isEmpty() }
.subscribe()
So, I'm using MVVM architecture with Repository pattern to make different API calls.
I have a Repository called X, where I have different related API calls.
Before any of these calls are made, I would like to do validation. If that proceeds successfully, only then network request should be made.
fun getSomethingX(data: Data): Single<Data> {
return if (validation(data)) {
service.getSomethingX()
.onErrorResumeNext(...)
.map { ... ->
...
}
} else {
Single.just(null)
}
}
fun getSomethingY(data: Data): Single<Data> {
return if (validation(data)) {
service.getSomethingX()
.onErrorResumeNext(...)
.map { ... ->
...
}
} else {
Single.just(null)
}
}
As you can see I might have many network request functions called getSomething..(). I see this as a boiler-plate code.
Is there some other way of dealing with validation (not only token validation but permission in general)? If so, can you show me an example?
Is it okay to do permission validation in the Repository level?
Maybe a better approach would be doing validation in Interceptor? But I don't see a clean way of canceling the request if validation does not pass.
A better approach will be to keep the validation at repository level only and keeping your viewmodel dumb as possible. It’s very simply with Kotlin’s Function literals with receiver.
In your repository
fun getSomethingX(
data: Data,
onSuccess: (Single<Data>) -> Unit,
onError: (String) -> Unit
) {
if (validation(data)) {
// Do the network call
onSuccess(//pass result)
} else onError(“Invalid data”)
}
In your ViewModel
repository.getSomethingX(
data,
onSuccess = {
//it will give you Single<Data>
//Update the value
},
onError = {
//Emit error to view
}
)
I have something like:
private Single<List<Data>> getFirstApiResponse() {
return Single.just(....)
/////
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
private Single<AnotherData> getSecondApiResponse() {
return Single.just(....)
/////
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
public void execute() {
//Here I need to run both observables one by one, and show result of each in View
// Code exetuting both
.subscribe(......);
}
How can I run two observables and subscribe on them in last method. In other words, I need to run method execute which will display in UI result of each Observables.
By the way, Observable not connected, they fetch different data (so I can run them asynchronous)
One way to do that is with flatMap:
public void execute() {
getFirstApiResponse()
.flatMap(response1 -> {
// getFirstApiResponse has completed
// ...
return getSecondApiResponse();
})
.subscribe(response2 -> {
// getSecondApiResponse has completed
// ...
}, error -> {
// One of the other operation has failed
});
}
You could look into the zip operator as well, depending on your needs. The downside to this solution is you are forced to combine your responses into a pair or another suitable datastructure, which may not make sense for you.
public void execute() {
Single.zip(getFirstApiResponse(), getSecondApiResponse(),
(first, second) -> {
//now you have both
return Pair.of(first, second);
}).subscribe(pair -> {/**do stuff**/});
}