Android RxJava Cancel the old request - android

I am new to Rxjava and exploring the possibilities and need help in below described scenario.
Step 1 : Swipe the View
Step 2 : Make an API Call to fetch Data
The above steps are repeated for the number of Views.
Problem :
API_CALL_1 fetching the View_1 Results
User swipes a View_2
API_CALL_2 fetching the View_2 Results
View_1 results are returned and populated in View_2 along with View_2 results
I need to cancel the API_CALL_1 request when the API_CALL_2 request.
Thanks.

Class member:
Subscription mSubscription;
Your API call:
if(subscription != null && !subscription.isUnsubscribed()){
subscription.unsubscribe();
subscription = null;
}
subscription = doApiCall("some_id")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Object>() {
#Override
public void call(Object o) {
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
}
});
So, the main idea of it, that you have to unsubscribe from previous call:
if(subscription != null && !subscription.isUnsubscribed()){
subscription.unsubscribe();
subscription = null;
}
Before starting new call

a better way is to use switchMap to cancel previous request

You are able to provide 'cancel' logic by using 'switchMap' operator. For example, you want to start/stop loading (Code written in Kotlin).
val loadingObservable = Observable.timer(10, TimeUnit.SECONDS)
val load = PublishSubject.create<Boolean>()
val resultObservable = load.switchMap { if(it) loadingObservable else Observable.never() }
Explanation:
'loadingObservable' is a timer that simulate long running operation
'load' is a command from user to start or stop loading. If you want
to start loading - send True else False
'switchMap' operator is
designed to dispose previous observable. So it you send True it will
start loading. If you send False it will dispose (cancel) previous
observable.

Related

Why is Parse Server saveEventually with callback taking so long?

I'm using Parse Server for my Android app and everything is working fine, but every time I call saveEventually on a new or old ParseObject, it is taking a really long time. Sometimes it's more than 1 minute for 1 item to return the callback.
Anyone had this problem?
Example:
orderObject.p.apply {
put(ORDER_STATE, ORDER_STATE_FINISHED)
put(ORDER_NEXT_DATE, orderEndDate)
}
createLog("FinishOrderSeq", "OrderActivity - saveOrder - before saveEvent")
orderObject.p.saveEventuallyEx(isOnline(this)){ e ->
createLog("FinishOrderSeq", "OrderActivity - saveOrder - after saveEvent")
if (e == null){
createToast(getString(R.string.order_dialog_success), this)
createOrderCopy(orderObject, dialog)
} else {
createToast(getString(R.string.order_dialog_err), this)
changeButtonState(posBtn, true)
changeButtonState(negBtn, true)
}
}
fun ParseObject.saveEventuallyEx(isOnline: Boolean, callback: (ParseException?) -> Unit){
if (isOnline){
saveEventually{ err ->
callback(err)
}
} else {
saveEventually()
callback(null)
}
}
Also logs as I replaced it with saveInBackground with callback(still 30 seconds):
2020-05-28 14:53:49.805 18673-18673/? I/FinishOrderSeq: OrderActivity - saveOrder - before saveEvent
2020-05-28 14:54:15.694 18673-18673/? I/FinishOrderSeq: OrderActivity - saveOrder - after saveEvent
UPDATE:
So I figured out from parse dashboard, that ParseObject is saved as record in table immediatelly, but callback from saveEventually is sent after 30sec - 2 minutes.
UPDATE 2:
I also tried to use saveInBackground() if user is online (with callback). This also took 30seconds to 2 minutes for callback to return. Object was saved to parse database with all data after 100ms (checked from Parse Dashboard).
Then I thought something is wrong with ParseSDK threads, so I used save() inside Coroutine. Same problem occured here, save() took up to 2 minutes to perform.
Code with coroutine:
fun ParseObject.saveAsync(context: CoroutineContext, scope: CoroutineScope, isOnline: Boolean, callback: (ParseException?) -> Unit){
if (isOnline){
scope.launch {
var ex: ParseException? = null
try {
save()
} catch (e: ParseException){
ex = e
}
withContext(context){
callback(ex)
}
}
}
}
There is some serious problem with callbacks in ParseSDK for Android and I don't know what can cause this. No exception no error on server side.
UPDATE 3:
After deeper investigation, I found which function is taking long time to proceed.
ParseObject.State result = saveTask.getResult();
Approximately 30 seconds - 2 minutes to get into next line of code.
This is lowest level of function I can get inside SDK.
Inside function save() or saveInBackground() there is this inner function in Java:
Task<Void> saveAsync(final String sessionToken, final Task<Void> toAwait) {
if (!isDirty()) {
return Task.forResult(null);
}
final ParseOperationSet operations;
synchronized (mutex) {
updateBeforeSave();
validateSave();
operations = startSave();
}
Task<Void> task;
synchronized (mutex) {
// Recursively save children
/*
* TODO(klimt): Why is this estimatedData and not... I mean, what if a child is
* removed after save is called, but before the unresolved user gets resolved? It
* won't get saved.
*/
task = deepSaveAsync(estimatedData, sessionToken);
}
return task.onSuccessTask(
TaskQueue.<Void>waitFor(toAwait)
).onSuccessTask(new Continuation<Void, Task<ParseObject.State>>() {
#Override
public Task<ParseObject.State> then(Task<Void> task) {
final Map<String, ParseObject> fetchedObjects = collectFetchedObjects();
ParseDecoder decoder = new KnownParseObjectDecoder(fetchedObjects);
return getObjectController().saveAsync(getState(), operations, sessionToken, decoder);
}
}).continueWithTask(new Continuation<ParseObject.State, Task<Void>>() {
#Override
public Task<Void> then(final Task<ParseObject.State> saveTask) {
ParseObject.State result = saveTask.getResult(); <--- THIS IS TAKING LONG TIME
return handleSaveResultAsync(result, operations).continueWithTask(new Continuation<Void, Task<Void>>() {
#Override
public Task<Void> then(Task<Void> task) {
if (task.isFaulted() || task.isCancelled()) {
return task;
}
// We still want to propagate saveTask errors
return saveTask.makeVoid();
}
});
}
});
}
From the docs:
Most save functions execute immediately, and inform your app when the save is complete. If you don’t need to know when the save has finished, you can use saveEventually instead.
It can take a long time because with saveEventually you are basically saying "save it soon". If you want to "save it as soon a possible" then use saveInBackground as described in the docs.
Further it says:
All calls to saveEventually (and deleteEventually) are executed in the order they are called, so it is safe to call saveEventually on an object multiple times. If you have the local datastore enabled, then any object you saveEventually will be pinned as long as that save is in progress. That makes it easy to retrieve your local changes while waiting for the network to be available.
Which means that you can save and modify the object locally multiple times and the latest version will be stored in the database as soon as the network connection is reestablished.

I'm using RxJava2 Disposable to observe clicks from button, how to change so that it does not add to CompositeDisposable each click (see code)?

So say in my app I have a SignInUserActivity. I have an API that allows the user to sign in, which returns a Single (RxJava 2) from signInUser(). I use the MVVM pattern so the following onClick method gets called each time I click the button with id button_sign_in_with_email.
I'm using CompositeDisposable to add and dispose of disposables.
I think the following code is wrong because it's adding a new Disposable each time I click the sign in button, when really I just want ONE disposable Single that returns a response each time I click sign in button, either "sign in FAILED" (in which a Toast will be shown and the Disposable will continue to observe the sign in button for future clicks) and "sign in SUCCESS" (in which the SignInUserActivity will have an Intent to MainActivity, thus calling onDestroy on SignInUserActivity, thus clearing disposables from CompositeDisposable.
protected void onDestroy() {
mViewModel.clearDisposables();
super.onDestroy();
}
And here is the onClick method:
public void onClick(View view) {
switch (view.getId()) {
case R.id.button_sign_in_with_email:
Disposable disposable = signInUser() // get response observable
.subscribe((Response<SignInUserResponseBody> response) -> { // subscribe to observable as disposable
if (response.body() != null && response.isSuccessful()) { // verify that response was successful
String signedInUserId = response.body().getUserId();
if (signedInUserId != null) { // verify that user id exists in SignInUserResponseBody
boolean signInSuccessful = handleSignIn(signedInUserId, response.headers()); // save new user's credentials in realm
if (signInSuccessful) { // successfully save new user's credentials
((SignInUserActivity) view.getContext()).onSignInSuccessful();
} else {
((SignInUserActivity) view.getContext()).onSignInFailed(view.getContext().getString(R.string.error_network_connectivity)); // error with saving to realm
}
} else {
((SignInUserActivity) view.getContext()).onSignInFailed(response.body().getErrorMessage()); // error with user input
}
} else {
((SignInUserActivity) view.getContext()).onSignInFailed(view.getContext().getString(R.string.error_network_connectivity)); // bad response from SkoolarService
}
}, (Throwable ex) -> {
onSignInFailed(view.getContext(), ex.getMessage());
});
addDisposable(disposable);
break;
So how do I fix it so I don't always add a disposable when I click?
Since your using a Single observable you don't have to dispose them manually. They get automatically disposed on Success or on Error. So you can remove the CompositeDisposable and call just subscribe on the single.
Refer RxJava reference
Usually, adding Disposables to CompositeDisposable over time should not cause any trouble, however, if you want to avoid the chance of leaking completely, you have to remove the Disposable when the flow terminates. This can get complicated so there is an extensions project consumer that removes the Disposable automatically upon termination:
// compile "com.github.akarnokd:rxjava2-extensions:0.18.5"
CompositeDisposable composite = new CompositeDisposable();
Disposable d = SingleConsumers.subscribeAutoRelease(
Single.just(1), composite,
System.out::println, Throwable::printStackTrace, () -> System.out.println("Done")
);
assertEquals(0, composite.size());

Is it possible to load several data asynchronously with Retrofit and wait for all data before doing something else?

Here is my current code. The problem with this code is I need to wait get the data sequentially. The loading time is poor because of this. I want to use something like .enqueue() to get asynchronously several data at once, but I want to wait until I get all the data before continuing the process. Is it possible to do it with Retrofit?
List<Data> datas = new ArrayList<>();
for (long dataId : mDataIds) {
Response<T> response = resource.getData(dataId).execute();
if (response.isSuccessful()) {
datas.add(data.body());
}
}
//do something else
You can solve this problem very elegantly using RxJava.
If you never heard of RxJava before, it is a solution to many of your problems.
If you don't use java8 or retrolambda I recommend you to start using it, as it makes working with RxJava a piece of cake.
Anyway here's what you need to do:
// 1. Stream each value from mDataIds
Observable.from(mDataIds)
// 2. Create a network request for each of the data ids
.flatMap(dataId -> resource.getData(dataId))
// 3. Collect responses to list
.toList()
// Your data is ready
.subscribe(datas -> {}, throwable -> {});
1) First add RxJava2 dependencies to your project
2) Define retrofit api interface methods which return RxJava observable types
public interface DataApi {
#GET("dataById/")
Observable<Data> getData(#Query("id") String id);
}
3) Call api passing input data like below.
Observable.fromIterable(idList).subscribeOn(Schedulers.computation())
.flatMap(id -> {
return retrofitService.getData(id).subscribeOn(Schedulers.io());
}).toList().
.observeOn(AndroidSchedulers.mainThread()).subscribe( listOfData -> {// do further processing }, error -> { //print errors} );
For reference : http://www.zoftino.com/retrofit-rxjava-android-example
Define interface with callback Model type.
public interface LoginService {
#GET("/login")
Call<List<Login>> getLogin();
}
In you calling method override the callback method.
LoginService loginService = ServiceGenerator.createService(LoginService.class);
Call<List<Login>> call = loginService.getLogin();
call.enqueue(new Callback<List<Login>>() {
#Override
public void onResponse(Call<List<Login>> call, Response<List<Login>> response) {
if (response.isSuccessful()) {
// Login successful
} else {
// error response, no access to resource?
}
}
#Override
public void onFailure(Call<List<Login>> call, Throwable t) {
// something went completely south (like no internet connection)
Log.d("Error", t.getMessage());
}
}
I would recommend using RxJava and try it. You have something called FlatMap to combine the results.
To Start here is the tutorial start for RxJava2 and Retrofit2.

RxJava concatMap no response

Hope you guys are doing well,
I have been working on a personal Android Project using RxJava and Retrofit. It's a search request for GitHub issues, I am getting input from the user when he clicks Search Button and using a PublishSubject object to emit the search text.
button.setOnClickListener(view -> {
publishSubject.onNext(editText.getText().toString());
});
and I am mapping this emit to an Observable using retrofit like this
publishSubject.concatMap(dataModel::getIssues)
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::loadData, this::onError);
public Observable<List<Issue>> getIssues(String queryText) {
String[] query_params = queryText.split("/");
return gitHubApiService.getIssues(query_params[0], query_params[1], "open");
}
In result I am expecting List of Issues
public void loadData(List<Issue> issues) {
mProgressDialog.setVisibility(View.INVISIBLE);
if( issues.size() == 0) {
noIssueText.setVisibility(View.VISIBLE);
} else {
mRecyclerView.setVisibility(View.VISIBLE);
mIssuesList.clear();
mIssuesList.addAll(issues);
mAdapter.notifyDataSetChanged();
}
}
But my code seems to have some implementation issue Since it never emits anything from the network, not even on error is called.
I have tested the same example with the Observable I get from Retrofit API, so there is no retrofit error and so I think there is some problem with my concatMap logic.
Any help will be much appreciated
On first parse, I think that you might be making the network call in the main thread. Have you tried the following?
public Observable<List<Issue>> getIssues(String queryText) {
String[] query_params = queryText.split("/");
return gitHubApiService.getIssues(query_params[0], query_params[1], "open")
.subscribeOn(Schedulers.io());
}
Thing is, your onClickListener callback runs on the main thread, and there's no other context switch in the observable pipeline.

Reuse RxJava stream from a remote API

I have an API call and I want to wrap it using Observable:
private Observable<RealmResults<Account>> getAccounts() {
final Observable<RealmResults<Account>> realmAccounts =
Observable.defer(new Func0<Observable<RealmResults<Account>>>() {
#Override
public Observable<RealmResults<Account>> call() {
return RealmObservable.results(getActivity(), new Func1<Realm, RealmResults<Account>>() {
#Override
public RealmResults<Account> call(Realm realm) {
return realm.where(Account.class).findAll();
}
});
}
});
return Observable
.create(new Observable.OnSubscribe<RealmResults<Account>>() {
#Override
public void call(final Subscriber<? super RealmResults<Account>> subscriber) {
DataBridge.getAccounts(Preferences.getString(Constant.ME_GUID, ""), new OnResponseListener() {
#Override
public void OnSuccess(Object data) {
Log.d("Stream", "onSuccess");
realmAccounts.subscribe(subscriber);
}
#Override
public void onFailure(Object data) {
subscriber.onError(new Exception(data.toString()));
}
});
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.startWith(realmAccounts);
}
and I use it like
Observable<Accounts> accounts = getAccounts().flatMap(
new Func1<RealmResults<Account>, Observable<Account>>() {
#Override
public Observable<Account> call(RealmResults<Account> accounts) {
return Observable.from(accounts);
}
});
How can I use the accounts observable multiple times without calling the API each time. I need to process the stream of accounts and extract different sets of data out of it.
The easiest method is to use operator cache, which internally uses ReplaySubject. It cache the source observable items and then serve the results from cache.
...
Observable<<RealmResults<Account>> cachedResult = getAccounts().cache();
Observable<Accounts> accountsObservable = cachedResult.flatMap(...);
Observable<X> xObservable = cachedResult.flatMap(...);
If you would like to avoid caching results you should use Connectable Observables. Usually it only does matter for Hot Observables. Connectable observable does not begin emitting items until its Connect method is called. You can use publish operator to convert to Connectable Observable.
ConnectableObservable<<RealmResults<Account>> connectebleObservable = getAccounts().publish();
Observable<Accounts> accountsObservable = connectebleObservable .flatMap(...);
Observable<X> xObservable = connectebleObservable .flatMap(...);
//You must subscribe before connect
accountsObservable.subsribe(...);
xObservable.subscribe(...);
//start emiting data
connectebleObservable.connect();
The important catch here is that you must subscribe before connect - to avoid data loss - otherwise you must use replay operator, which is similar to cache operator, but used for connectable observable
And what about share ?
It create ConnectableObservable and exposes it as regular Observable. First subscription automatically causes connection and emission.
Share used in your case, without replay may cause data loss or multiple executions depending on timing.
for example for 2 subscribers and one item int the stream you may have fallowing cases:
2 subscriptions created before onNext - works as expected.
second subscription created after onNext but before onComplete - second subscription gets only onComplete
second subscriptinon created after onComplete - 2 executions wihtout caching

Categories

Resources