Why debounce() with toList() doen't working in RxAndroid? - android

While I'm using debounce() ,then fetch data from backend and the data
I want to convert to another data and lastly use toList().
when I'm using toList() nothing happens no any log not in subscribe and error ,without toList() it works and subscribe() method enters as much as I have list of books, I tested the second part of code it without debounce() just getItems() and using toList() it works.
Below is my code the first part with debounce() and itList() which is not working and the second with toList() which works
public Flowable<List<Book>> getItems(String query) {}
textChangeSubscriber
.debounce(300, TimeUnit.MILLISECONDS)
.observeOn(Schedulers.computation())
.switchMap(s -> getItems(s).toObservable())
.flatMapIterable(items -> items)
.map(Book::convert)
.toList()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(books -> {
Log.i("test", "" + books.toString());
}, error -> {
Log.i("test", "" + error);
});
getItems(query).flatMapIterable(items -> items)
.map(Book::convert)
.toList()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(books -> {
Log.i("test", "" + "" + books.toString());
}, error -> {
Log.i("test", "" + error);
});

toList requires the sequence to terminate which doesn't happen on the outer stream that responds to text events. You should move the processing of the books into the switchMap:
textChangeSubscriber
.map(CharSequence::toString) // <-- text components emit mutable CharSequence
.debounce(300, TimeUnit.MILLISECONDS)
.observeOn(Schedulers.computation())
.switchMap(s ->
getItems(s)
.flatMapIterable(items -> items)
.map(Book::convert)
.toList()
.toFlowable() // or toObservable(), depending on textChangeSubscriber
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(books -> {
Log.i("test", "" + books.toString());
}, error -> {
Log.i("test", "" + error);
});

Related

How to execute many RxJava2 flux in a row

I'm introducing myself about RxJava2, but i feel like i'm doing something wrong. In my case, i want to do some following asynchronous actions.
In this example, the first action is to check if the device is connected (wifi or data, let's admit it take time), then i want to connect to an api and then i want to do a http call for get a list (observable) and then work with it. If one of those operation fail, an onError or exception should be raised and handled in the subscribe.
I have this code who works:
Single.create((SingleEmitter<Boolean> e) -> e.onSuccess(Connectivity.isDeviceConnected(MainActivity.this)) )
.subscribeOn(Schedulers.io())
.flatMap(isDeviceConnected -> {
Log.i("LOG", "isDeviceConnected : "+ isDeviceConnected);
if(!isDeviceConnected)
throw new Exception("whatever"); // TODO : Chercher vrai erreur
return awRepository.getFluxAuthenticate(host, port, user, password); // Single<DisfeApiAirWatch>
})
.toObservable()
.flatMap(awRepository::getFluxManagedApps) // List of apps : Observable<AirwatchApp>
.observeOn(AndroidSchedulers.mainThread())
.doFinally(this::hideProgressDialog)
.subscribe(
app -> Log.i("LOG", "OnNext : "+ app),
error -> Log.i("LOG", "Error : " + error),
() -> Log.i("LOG", "Complete : ")
);
But do a single who emmit a boolean for a simple "if" sounds wrong. A Completable seems more logical (work or not, continue or stop). I tried with the following code but it's not working.
Completable.create((CompletableEmitter e) -> {
if(Connectivity.isDeviceConnected(MainActivity.this))
e.onComplete(); // Guess not good, should call the complete of subscribe ?
else
e.onError(new Exception("whatever"));
} ).toObservable()
.subscribeOn(Schedulers.io())
.flatMap(awRepository.getFluxAuthenticate(host, port, user, password)) //Single<DisfeApiAirWatch>
.toObservable()
.flatMap(awRepository::getFluxManagedApps) // List of apps : Observable<AirwatchApp>
.observeOn(AndroidSchedulers.mainThread())
.doFinally(this::hideProgressDialog)
.subscribe(
app -> Log.i("LOG", "OnNext : "+ app),
error -> Log.i("LOG", "Error : " + error),
() -> Log.i("LOG", "Complete : ")
);
How to make this code work ?
I know i can do a first subscribe on the complatable and in the "onSuccess" of this one write another flux / the rest of the code. But i don't think stack flows inside each other is a good solution.
Best regards
Completable has no value so flatMap will never be invoked. You have to use andThen and make the authentication success value the input for the subsequent flatMap:
Completable.create((CompletableEmitter e) -> {
if(Connectivity.isDeviceConnected(MainActivity.this))
e.onComplete();
else
e.onError(new Exception("whatever"));
})
.subscribeOn(Schedulers.io())
.andThen(awRepository.getFluxAuthenticate(host, port, user, password)) // <-----------
.flatMapObservable(awRepository::getFluxManagedApps)
.observeOn(AndroidSchedulers.mainThread())
.doFinally(this::hideProgressDialog)
.subscribe(
app -> Log.i("LOG", "OnNext : "+ app),
error -> Log.i("LOG", "Error : " + error),
() -> Log.i("LOG", "Complete : ")
);

JavaRx doOnNext not called after onErrorResumeNext

I've tried to loop throw items and do additional request for each item. the issue is there it a possibility that request returns an error but i need to continue requests and collect all data to list. but it looks like after onErrorResumeNext called no other data adds to list.
Observable<List<TestModel2>> observable = apiTest
.performTest()
.flatMapIterable(items -> items)
.flatMap(testModel -> {
Log.d("TestItemData", "testModel.userId = " + testModel.userId);
if (testModel.userId < 5) {
return apiTest.performTest2(testModel.userId);
} else {
return apiTest.performTest3(testModel.userId);
}
})
.doOnNext(testModel2 -> {Log.d("TestItemData", "doOnNext --- " + testModel2.title);})
.onErrorResumeNext(throwable ->{
Log.d("TestItemData", "onErrorResumeNext -------- ");
return Observable.empty();
})
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
in this example afret performTest() called I get 10 items that i need to loop throw and call performTest2() that returns data
and for some id's i called performTest3() that returns 404 exception.
a for result in logs i see all 10 testModel.userIds (from 1 to 10) but doOnNext calls until onErrorResumeNext calls only, so in the result list i can got 1 item or 2 or 3 items only
Apply the doOnNext and onErrorResumeNext inside the flatMap:
Observable<List<TestModel2>> observable = apiTest
.performTest()
.flatMapIterable(items -> items)
.flatMap(testModel -> {
Log.d("TestItemData", "testModel.userId = " + testModel.userId);
Observable<TestModel2> obs;
if (testModel.userId < 5) {
obs = apiTest.performTest2(testModel.userId);
} else {
obs apiTest.performTest3(testModel.userId);
}
return obs
.doOnNext(testModel2 -> {
Log.d("TestItemData", "doOnNext --- " + testModel2.title);
})
.onErrorResumeNext(throwable ->{
Log.d("TestItemData", "onErrorResumeNext -------- ");
return Observable.empty();
});
})
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());

Filter Observable<List> with text from an EditText using RxBinding

I have an array of Objects and I want to filter that array based on the text user types on an EditText android view.
What I thought it that I should try and convert the array of POJOs to an Observable of Strings and this is what I did :
Observable<String> professionsObservable = Observable.fromArray(((GetStartedActivity) getActivity()).professions)
.map(profession -> {
if (profession.getName().length() > 0) {
professionsNameList.add(capitalizeFirstLetter(profession.getName()));
}
return professionsNameList;
})
.flatMapIterable(items -> items);
Now I want to combine the text from the EditText with the `professionsObservable I posted above.
This is the code I'm using :
RxTextView.textChangeEvents(etProfession)
.doOnEach(notif -> {
if (etProfession.getText().toString().trim().length() > 0) {
etCompany.setVisibility(GONE);
etIndustry.setVisibility(GONE);
} else {
etCompany.setVisibility(VISIBLE);
etIndustry.setVisibility(VISIBLE);
}
})
.debounce(EDITTEXT_DELAY, TimeUnit.MILLISECONDS)
.skip(1)
.map(textChangeEvent -> textChangeEvent.text().toString())
.switchMap(search -> {
return professionsObservable
.filter(profession -> {
return profession.toLowerCase().startsWith(search);
});
}
)
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
filteredProfessions -> {
Timber.i("NOT ENTERING");
rvProfession.setVisibility(VISIBLE);
professionAdapter.addItems(filteredProfessions);
},
throwable -> Log.i("THROW", "PROFESSIONS ", throwable));
I'm using map operator to turn the text change event to a String and then for each String I get from the stream I'm using switchMap (cause I don't care for results from previous searches). Then I compose all Strings to a List with toList. The problem is that it never reaches the subscribe call while I have a lot of strings in the initial Array I used and I do type text that fits the condition of the filter operator.
Is it something that I might missing here ?
EDIT : I updated my code to :
RxTextView.textChangeEvents(etProfession)
.doOnEach(notif -> {
if (etProfession.getText().toString().trim().length() > 0) {
etCompany.setVisibility(GONE);
etIndustry.setVisibility(GONE);
} else {
etCompany.setVisibility(VISIBLE);
etIndustry.setVisibility(VISIBLE);
}
})
.subscribeOn(AndroidSchedulers.mainThread())
.debounce(EDITTEXT_DELAY, TimeUnit.MILLISECONDS)
.skip(1)
.map(textChangeEvent -> textChangeEvent.text().toString())
.flatMap(search -> {
return Observable.fromArray(((GetStartedActivity) getActivity()).professions)
.map(profession -> {
List<String> professionsList = new ArrayList<>();
if (profession.getName().length() > 0) {
professionsList.add(capitalizeFirstLetter(profession.getName()));
}
return professionsList;
})
.flatMapIterable(items -> items)
.filter(profession -> {
if (profession.toLowerCase().startsWith(search.toLowerCase())) {
}
return profession.toLowerCase().startsWith(search.toLowerCase());
});
})
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
filteredProfessions -> {
rvProfession.setVisibility(VISIBLE);
// professionAdapter.addItems(filteredProfessions);
},
throwable -> Log.i("THROW", "PROFESSIONS ", throwable));
If I remove toList() operator my code works (enters the subscribe call) but if I leave it there it wont. Anyone knows why ?
In my experience, RxBinding requires .subscribeOn(AndroidSchedulers.mainThread()) right after .textChangeEvents() or any other event. So probably you are causing it to fail by adding .subscribeOn(Schedulers.io)
See method .checkMainThread() in https://github.com/JakeWharton/RxBinding/blob/master/rxbinding/src/main/java/com/jakewharton/rxbinding2/internal/Preconditions.java
UPDATE
Also, probably because .onComplete() never comes from upstream, .toList() is never executed.

Update the UI ( Loading and Error Views ) on RecyclerView using RxJava

I have a SearchView that executes a network request to search for some tracks and then populates a RecylerView with the results. I have found this code which works fine.
I have already integrated the RecyclerView EmptyView through the Adapter but now I am trying to integrate the LoadingView(Progress) and ErrorView inside this code. I tried to put the LoadingView(ProgressBar) on Visibility True inside the concatMap but got the error that the “Only the original thread that created a view hierarchy can touch its views.” which can be solved running that on the MainThread but I am sure there is a better way to do this.
Can someone have a better idea where and how the logic about show/hide the ErrorView and LoadingView can be integrated into this code?
I am using also RxBinding. Maybe also using RxRecyclerView would be a good idea?
RxSearchView.queryTextChanges(searchView).
filter(charSequence ->
!TextUtils.isEmpty(charSequence))
.throttleLast(100, TimeUnit.DAYS.MILLISECONDS)
.debounce(200, TimeUnit.MILLISECONDS)
.onBackpressureLatest()
.concatMap(searchTerm ->
{
return searchTracks(searchTerm).
.subscribeOn(Schedulers.io())
.onErrorResumeNext(throwable1 -> {
//handle error somehow, change UI
return Observable.empty();
}
);
}
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(tracks -> {
populateTracks(tracks);
}
});
}, throwable -> {
//show errorView
});
This here was my complete solution without stripping code for the presentation.
RxSearchView.queryTextChanges(searchView)
.skip(1)
.doOnNext(charSequence -> Log.v(TAG, "searching: " + charSequence))
.throttleLast(100, TimeUnit.MILLISECONDS)
.debounce(200, TimeUnit.MILLISECONDS)
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.filter(charSequence -> {
final boolean empty = TextUtils.isEmpty(charSequence);
if (empty) {
Log.v(TAG, "empty view");
mAdapter.clear();
}
return !empty;
})
.concatMap(charSequence -> {
Log.v(TAG, "requesting " + charSequence);
return onErrorResumeNext(
mGitApiService.searchRepositoriesSingle(charSequence.toString())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()),
throwable -> {
try {
throw throwable;
} catch (HttpException httpException) {
showEmptyErrorView(httpException.message());
} catch (Throwable other) {
showEmptyErrorView(other.getMessage());
other.printStackTrace();
}
return Observable.empty();
});
})
.doOnNext(charSequence -> Log.v(TAG, "got data"))
.subscribe(response -> {
showRepositories(response.getItems());
}, throwable -> {
throwable.printStackTrace();
showEmptyErrorView(throwable.getMessage());
});
so basically whenever you touch your view you have to call .observeOn(AndroidSchedulers.mainThread())

Use RxJava and Retrofit to iterate through list and augment results based on subqueries

I'm using retrofit and I feel like rxjava (with retrolambda) would be a good fit for the following flow:
get list of widgets (http)
for each widget
a) get a list of articles (http) for the given widget type
b) save all those to db
c) take the first (latest) article in list and update widget.articleName and widget.articleUrl with appropriate values from this article
transform back to list and complete
However I'm unsure what to do after step 2a. Here's my code so far
apiService.getWidgets(token)
.flatMapIterable(widgets -> widgets)
.flatMap(widget -> apiService.getArticles(token, widget.type))
...
.toList()
.subscribe(
modifiedWidgets -> saveWidgets(modifiedWidgets),
throwable -> processWidgetError(throwable)
);
I've played around with some operators but when chaining, I always seem to narrow down
too far (e.g. get a handle on a single article) and then no longer have access to the
original widget to make modifications.
#GET("/widgets")
Observable<List<Widget>> getWidgets(#Header("Authorization") String token);
#GET("/articles")
Observable<List<Article>> getArticles(#Header("Authorization") String token, #Query("type") String type);
You could insert doOnNext at certain points of the stream to add side-effects:
apiService.getWidgets(token)
.flatMapIterable(v -> v)
.flatMap(w ->
apiService.getArticles(token, w.type)
.flatMapIterable(a -> a)
.doOnNext(a -> db.insert(a))
.doOnNext(a -> {
w.articleName = a.name;
w.articleUrl = a.url;
})
.takeLast(1)
.map(a -> w)
)
.toList()
.subscribe(
modifiedWidgets -> saveWidgets(modifiedWidgets),
throwable -> processWidgetError(throwable)
);
Here is runnable example of this.
adding this here since I couldn't find an example of iterating a list that is returned in an object as variable.
getUserAccount(token)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(userResponse -> Observable.just(userResponse.list)) //get list from response
.flatMapIterable(baseDatas -> baseDatas) //make the list iterable
.flatMap(baseData -> //on each project, get the details
getProjectDetails(baseData.name,token)
.subscribeOn(Schedulers.io()) //get network call off the main thread
.observeOn(AndroidSchedulers.mainThread()))
.subscribe(
(dataResponse) -> {
Timber.d( "Got Data Details:" + dataResponse);
},
(error) -> {
Timber.e( "Got Error:" + error.getMessage());
},
() -> {
Timber.d("Completed Data Details");
}
);
akarnokd's answer is quite helpful but that may cause NetworkOnMainThreadException.
To solve that I have added
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
on every requests
apiService.getWidgets(token)
.observeOn(AndroidSchedulers.mainThread()) //added this
.subscribeOn(Schedulers.io()) //added this
.flatMapIterable(v -> v)
.flatMap(w ->
apiService.getArticles(token, w.type)
.observeOn(AndroidSchedulers.mainThread()) //added this
.subscribeOn(Schedulers.io()) //added this
.flatMapIterable(a -> a)
.doOnNext(a -> db.insert(a))
.doOnNext(a -> {
w.articleName = a.name;
w.articleUrl = a.url;
})
.takeLast(1)
.map(a -> w)
)
.toList()
.subscribe(
modifiedWidgets -> saveWidgets(modifiedWidgets),
throwable -> processWidgetError(throwable)
);

Categories

Resources