I would like to wrap a synchronous method from parse.com mainly ParseObject.save() into a RxJava wrapper. I have come up with the below:
public Observable<Void> uploadFix(final ParseObject parseObject) {
return Observable.defer(new Func0<Observable<Void>>() {
#Override
public Observable<Void> call() {
try {
return Observable.just(fix.save());
} catch (ParseException e) {
return Observable.error(e);
}
}
});
}
This is giving me an error: Observable cannot be applied to void.
Basically is there any way to wrap this call with RxJava and get notified if the save is successful?
fix.save() returns void so you can't use it as an argument to Observable.just(). You can return a boolean instead.
public Observable<Boolean> uploadFix(final ParseObject parseObject) {
return Observable.defer(new Func0<Observable<Boolean>>() {
#Override
public Observable<Boolean> call() {
try {
fix.save();
return Observable.just(true);
} catch (ParseException e) {
return Observable.error(e);
}
}
});
}
you could also use a Completable. It is used when you don't except a return-value. If RxJava for Android will bump-up to version 2, you can not use Observabl anymore, because null values are not allowed anymore.
Please look at my example. I am using RxJava2-RC5 for testing. Test should complete within 2 seconds + overhead.
#org.junit.Test
public void name() throws Exception {
Completable completable = Completable.fromAction(() -> doWorkSync());
TestObserver<Void> test = completable.test();
test.assertComplete();
}
private void doWorkSync() {
// simulate work
try {
Thread.sleep(2_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Related
I have following code
private void tryToLauch() {
try {
launch();
} catch (MyException e) {
postError(e.getErrorMessage());
e.printStackTrace();
}
}
How can I convert it to Rx that will retry in some period in case of exception ?
Given that your method have as return type void, I suggest you to use a Completable.
You can try this solution, using RxJava 2
Completable myCompletable = Completable.fromAction(new Action() {
#Override
public void run() throws Exception {
launch();
}
}).retry(3 /*number of times to retry*/, new Predicate<Throwable>() {
#Override
public boolean test(Throwable throwable) throws Exception {
return throwable instanceof MyException;
}
});
Then subscribe to the Completable
myCompletable.subscribeOn(SubscribeScheduler)
.observeOn(ObserveScheduler)
.subscribe(this::onComplete, this::onError);
Hope this helps.
I use retrofit2 with rxjava extension.
I have a list of REST API urls and want to do this:
for each
check whether a corresponding file locally exists
if yes: call the API and store the response or the HTTP error
if not: store a customized error
return the list of those results
My problem is: apply returns (with an empty RequestResult) before the server response is received. I think, I understand why, but I don't know how to fix it, because I need to return a RequestResult and not the Retrofit observable.
How can this be solved?
Here is my code:
#GET
Observable<Response<ResponseBody>> enroll(#Url String url);
class RequestResult {
CustomException error;
Response<ResponseBody> response;
}
Observable<ClassOfListItem> observable = Observable.fromIterable(listOfItems);
observable
.flatMap(new Function<ClassOfListItem, ObservableSource<RequestResult>>() {
#Override
public ObservableSource<RequestResult> apply(ClassOfListItem listItem) throws Exception {
RequestResult requestResult = new RequestResult();
if (fileExists(listItem.url)) {
Observable<Response<ResponseBody>> callObservable = restAPI.enroll(listItem.url)
.subscribeOn(Schedulers.io());
callObservable
.subscribe(new DisposableObserver<Response<ResponseBody>>() {
#Override
public void onNext(Response<ResponseBody> responseBodyResponse) {
onPremiseEnrollmentResult.response = responseBodyResponse;
}
#Override
public void onError(Throwable e) {
onPremiseEnrollmentResult.error = new CustomException(e);
}
#Override
public void onComplete() {
}
});
}
else {
requestResult.error = new CustomException("file not found");
}
return Observable.just(requestResult);
}
}
.toList()
.observerOn(AndroidScheduler.mainThread())
.subscribe(new DisposableSingleObserver<List<RequestResult>>() {
#Override
public void onError(Throwable e) {
Log.d("onError", e.getMessage());
}
#Override
public void onSuccess(List<RequestResult> requestResults) {
// parse results
}
}
)
The flatMap() operator allows you to turn one observable into a different observable. You have a nested observer chain inside your apply() which is not part of the observer chain, so it will be empty because it has not completed yet.
To fix this, when the file exists, return the observable.
observable
.flatMap(new Function<ClassOfListItem, ObservableSource<RequestResult>>() {
#Override
public ObservableSource<RequestResult> apply(ClassOfListItem listItem) throws Exception {
RequestResult requestResult = new RequestResult();
if (fileExists(listItem.url)) {
return restAPI.enroll(listItem.url)
.subscribeOn(Schedulers.io());
}
return Observable.error( new CustomException("file not found") );
}
}
.toList()
.observerOn(AndroidScheduler.mainThread())
.subscribe(new DisposableSingleObserver<List<RequestResult>>() {
#Override
public void onError(Throwable e) {
Log.d("onError", e.getMessage());
}
#Override
public void onSuccess(List<RequestResult> requestResults) {
// parse results
}
}
If you need to capture both errors and successes into the list, then you can add map() operator to wrap RequestResult around the response and onErrorResumeNext() to wrap RequestResult around the error before the toList() operator.
If you are making api call on background thread then what you can do is invoke it synchronously....in your case your retrofit api method would change to following
Call<Response<ResponseBody>> enroll(#Url String url);
and you'd invoke by calling restAPI.enroll(listItem.url).execute()
I'm very new to RXJava.
I have a function called politelyrefresh() that concats two observables together, but the functions in these two observables only run the first time I called politeRefresh, I'm not sure this is the right way to do it. What I want is run this function inside the observables everytime.
public void politelyRefresh() {
Observable.concat(refreshStoreDataObservable, refreshProjectDataObservable)
.finallyDo(()-> {
try {
//someother other long runnning-network requests
} catch (Exception e) {
Log.e(TAG, "politelyRefresh finallyDo Error", e);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(reloadUiFromLocalStorageSubscriber);
}
//the other observable is pretty much the same but making another request
Observable<String> refreshStoreDataObservable = Observable.create(new Observable.OnSubscribe<String>() {
#Override
public void call(Subscriber<? super String> subscriber) {
//DOESN'T GET HERE SECOND TIME!
Store.syncStores(new ListCallback() {
#Override
public void syncSuccess() {
getSyncStateManager().setStoresRefreshed();
subscriber.onCompleted();
}
#Override
public void syncError() {
subscriber.onError(new Throwable("SYNC STORES ERROR"));
getSyncStateManager().setStoresSyncCompleted();
}
});
}
});
Subscriber<String> reloadUiFromLocalStorageSubscriber = new Subscriber<String>() {
#Override
public void onCompleted() {
if (mStoreRefreshLayout != null){
mStoreRefreshLayout.setRefreshing(false);
}
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "reloadUiFromLocalStorageSubscriber: onError", e);
if (mStoreRefreshLayout != null){
mStoreRefreshLayout.setRefreshing(false);
}
}
#Override
public void onNext(String s) {
Log.d(TAG, "reloadUiFromLocalStorageSubscriber: onNext " + s);
}
};
I think you're looking for Observable.defer(). What this basically does is defer the creation of the Observable to when it is being subscribed to.
Here's a quick example:
public class Refresher {
Refresher() {
politelyRefresh();
politelyRefresh();
}
public void politelyRefresh() {
Observable.defer(() -> Observable.concat(refreshProjectData(), refreshStoreData()))
.map(this::processData)
.subscribe(this::printData);
}
private Observable<String> refreshStoreData() {
System.out.println("StoreData Refreshed");
return Observable.just("data1","data2","data3");
}
private Observable<String> refreshProjectData() {
System.out.println("ProjectData Refreshed");
return Observable.just("Project1","Project2", "Project3");
}
private String processData(String data) {
return data + " processed";
}
private void printData(String data) {
System.out.println(data);
}
}
If you instantiate our refresher object, you'll get
StoreData Refreshed
StoreData Refreshed
Project1 processed
Project2 processed
Project3 processed
data1 processed
data2 processed
data3 processed
StoreData Refreshed
StoreData Refreshed
Project1 processed
Project2 processed
Project3 processed
data1 processed
data2 processed
data3 processed
If you'd like something to run on a different thread, you'd specify that on the specific observable you're looking to run on a non-ui thread.
So, for example, you might want to run the Observable in politelyRefresh on a background thread and subscribe to it on the UI thread. The creation of the other Observables will happen in a background thread too!
I finally got this to work by move the subscriber from an class instance to inside the .subscribe() function(). I have no idea why this is happening.
Observable.concat(refreshStoreDataObservable, refreshProjectDataObservable)
.finallyDo(()-> {
try {
//someother other long runnning-network requests
} catch (Exception e) {
Log.e(TAG, "politelyRefresh finallyDo Error", e);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe( new Subscriber<String>() { /*rest of code */}); //**here
I'm pretty new to RxJava and Retrofit and am trying to write my API calls with it. All the API calls return a JSON body on error which is in the general format as,
{"errors":[{"code":100, "message":"Login/Password not valid", "arguments":null}]}
Currently my code for the login API call (others are also similar) is,
mConnect.login(id, password)
.subscribe(new Subscriber<Token>() {
#Override
public void onCompleted() {
Log.d(TAG, "onCompleted()");
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "onError(): " + e);
if (e instanceof HttpException) {
// dump e.response().errorBody()
}
}
#Override
public void onNext(Token token) {
Log.d(TAG, "onNext(): " + token);
}
});
When I get an error at the onError(), I would like to automatically decode the JSON in the error body to a POJO instead and use that. Is there a way to do this preferably in one place for all other API calls. Any help is appreciated.
I would suggest the use of a reusable Transformer along with the onErrorResumeNext operator to encapsulate your logic. It'd look something like this:
<T> Observable.Transformer<T, T> parseHttpErrors() {
return new Observable.Transformer<T, T>() {
#Override
public Observable<T> call(Observable<T> observable) {
return observable.onErrorResumeNext(new Func1<Throwable, Observable<? extends T>>() {
#Override
public Observable<? extends T> call(Throwable throwable) {
if (throwable instanceof HttpException) {
HttpErrorPojo errorPojo = // deserialize throwable.response().errorBody();
// Here you have two options, one is report this pojo back as error (onError() will be called),
return Observable.error(errorPojo); // in this case HttpErrorPojo would need to inherit from Throwable
// or report this pojo back as part of onNext()
return Observable.just(errorPojo); //in this case HttpErrorPojo would need to inherit from <T>
}
// if not the kind we're interested in, then just report the same error to onError()
return Observable.error(throwable);
}
});
}
};
}
Pay attention to the comments in the code, since you have to make the decision whether you want to report the parsed response onError() or onNext().
Then you can use this transformer anywhere in your API calls like this:
mConnect.login(id, password)
.compose(this.<Token>parseHttpErrors()) // <-- HERE
.subscribe(new Subscriber<Token>() {
#Override
public void onCompleted() {
Log.d(TAG, "onCompleted()");
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "onError(): " + e);
if (e instanceof HttpErrorPojo) {
// this will be called if errorPojo was reported via Observable.error()
}
}
#Override
public void onNext(Token token) {
Log.d(TAG, "onNext(): " + token);
if (token instanceof HttpErrorPojo) {
// this will be called if errorPojo was reported via Observable.just()
}
}
});
Deserialize may be an issue too. You can use the retrofit converter to deserialize it (or do it yourself).
My solution adds a bit to the one from murki:
<T> Observable.Transformer<T, T> parseHttpErrors() {
return new Observable.Transformer<T, T>() {
#Override
public Observable<T> call(Observable<T> observable) {
return observable.onErrorResumeNext(new Func1<Throwable, Observable<? extends T>>() {
#Override
public Observable<? extends T> call(Throwable throwable) {
if ( throwable instanceof HttpException ) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(SERVER_URL) // write your url here
.addConverterFactory(GsonConverterFactory.create())
.build();
Converter<ResponseBody, Error> errorConverter =
retrofit.responseBodyConverter(Error.class, new Annotation[0]);
// Convert the error body into our Error type.
try {
Error error = errorConverter.convert(((HttpException) throwable).response().errorBody());
// Here you have two options, one is report this pojo back as error (onError() will be called),
return Observable.error(new Throwable(error.getMessage()));
}
catch (Exception e2) {
return Observable.error(new Throwable());
}
}
// if not the kind we're interested in, then just report the same error to onError()
return Observable.error(throwable);
}
});
}
};
}
and then at the onError(),
#Override
public void onError(Throwable e) {
progressBar.setVisibility(View.GONE); // optional
if ( !TextUtils.isEmpty(e.getMessage()) ) {
// show error as you like
return;
}
// show a default error if you wish
}
I can not understand how to translate a simple AsyncTask in RxJava. Take for example:
private class Sync extends AsyncTask<String, String, String> {
#Override
protected String doInBackground(String... params) {
String proxy_arr = "";
try {
Document jsoup_proxy = Jsoup.connect(Constants.SITE_PROXY_LIST)
.userAgent(Constants.USER_AGENT)
.ignoreContentType(true)
.ignoreHttpErrors(true)
.timeout(Constants.USER_TIMEOUT)
.get();
if (jsoup_proxy != null) proxy_arr = jsoup_proxy.text().trim();
} catch (IOException e) {
new DebugLog(getActivity(), "News", "Sync PROXY", Log.getStackTraceString(e));
}
return proxy_arr;
}
#Override
protected void onPostExecute(String result) {
if (result.equals("err_internet")){
func.toastMessage(R.string.toast_err_nointernet, "", "alert");
}
reloadAdapter();
}
}
As it can be translated in the same working condition RxJava?
Thank you!
Instead of using Observable.create you should use either Observable.defer() or better yet Observable.fromCallable (which was introduced in RxJava 1.0.15) - because these methods will ensure a proper observable contract and save you from some mistakes you can introduce when creating observable by hand.
Also instead of going with runOnUiThread as suggested in one of the answers above, you should really use AndroidSchedulers.mainThread() which was created for exactly this purpose. Just use RxAndroid library which provides it.
I suggest the following solution:
public Observable<String> getJsoupProxy() {
return Observable.fromCallable(() -> {
try {
Document jsoup_proxy = Jsoup.connect(Constants.SITE_PROXY_LIST)
.userAgent(Constants.USER_AGENT)
.ignoreContentType(true)
.ignoreHttpErrors(true)
.timeout(Constants.USER_TIMEOUT)
.get();
return jsoup_proxy != null ? jsoup_proxy.text().trim() : "";
} catch (IOException e) {
// just rethrow as RuntimeException to be caught in subscriber's onError
throw new RuntimeException(e);
}
});
}
getJsoupProxy()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) // this scheduler is exported by RxAndroid library
.subscribe(
proxy -> {
if(proxy.equals("err_internet")) {
// toast
}
reloadAdapter();
},
error -> new DebugLog(getActivity(), "News", "Sync PROXY", Log.getStackTraceString(error)));
When you convert a functionality to be reactive please keep in mind that you should define
onNext
onError
onCompleted
"events".
Actually rx is working well with data sequences, but of course you can create a data sequence with only one emitted item.
So to modify your method to be reactive I'd say first you should decouple the responsibilities.
Somewhere in a repository class or you name it according to your architecture you just create this:
public Observable<String> getProxyAsync() {
return Observable.create(subscriber -> {
String proxy_arr = "";
try {
Document jsoup_proxy = Jsoup.connect(Constants.SITE_PROXY_LIST)
.userAgent(Constants.USER_AGENT)
.ignoreContentType(true)
.ignoreHttpErrors(true)
.timeout(Constants.USER_TIMEOUT)
.get();
if (jsoup_proxy != null) proxy_arr = jsoup_proxy.text().trim();
subscriber.onNext(proxy_arr);
} catch (IOException e) {
subscriber.onError(e);
} finally {
subscriber.onCompleted();
}
});
}
after that, somewhere near to the activity, just subscribe to this method like this:
public void myPreciousMethod() {
myCustomRepo.getProxyAsync()
.subscribeOn(Schedulers.newThread())
.subscribe(result -> {
runOnUiThread(() -> {
if (result.equals("err_internet")) {
func.toastMessage(R.string.toast_err_nointernet, "", "alert");
}
});
}, throwable -> {
// some exception happened emmited by your code, handle it well
new DebugLog(getActivity(), "News", "Sync PROXY", Log.getStackTraceString(e));
}, () -> {
// onCompleted:
runOnUiThread(() -> reloadAdapter());
});
}
I would suggest to use .runOnUiThread() (in your activity or any other view related operation with rx) to avoid backpressure, but it really depends on the amount and frequency of emitted data. (you can use .observeOn() and .subscribeOn() as well) Plus using the retrolambda is highly recommended too in sake of much more cleaner code.
This is one way of doing it. You could forego the defer if need be.
Observable.defer(new Func0<Observable<String>>() {
#Override
public Observable<String> call() {
return Observable.create(new Observable.OnSubscribe<String>() {
#Override
public void call(Subscriber<? super String> subscriber) {
String proxy_arr = "";
try {
Document jsoup_proxy = Jsoup.connect(Constants.SITE_PROXY_LIST)
.userAgent(Constants.USER_AGENT)
.ignoreContentType(true)
.ignoreHttpErrors(true)
.timeout(Constants.USER_TIMEOUT)
.get();
if (jsoup_proxy != null) proxy_arr = jsoup_proxy.text().trim();
} catch (IOException e) {
new DebugLog(getActivity(), "News", "Sync PROXY", Log.getStackTraceString(e));
}
if (!subscriber.isUnsubscribed()) {
subscriber.onNext(proxy_arr);
}
}
})
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<String>() {
#Override
public void call(String result) {
if (result.equals("err_internet")){
func.toastMessage(R.string.toast_err_nointernet, "", "alert");
}
reloadAdapter();
}
});