I am trying to learn about rxJava and reactive programming in context of android and I feel I am nearly there, I just can't quite grasp the complete picture to fully understand what I am doing.
I have the below code which gets a list of instances of a class called iApps from the database
myHelper m = new myHelper(getApplication());
m.getApps()
.observeOn(Schedulers.newThread())
.subscribe(currentApps::addAll,
throwable -> Log.e("Error Observable", throwable.toString() + " " + Arrays.toString(throwable.getStackTrace())),
() -> compareLists(availableApps, currentApps));
}
Which uses the following methods:
//From my database caller function
public Callable<ArrayList<iApp>> getApps()
{
return this::getCurrentInfo;
}
A custom helper function
public class myHelper {
Context ctx;
tQuery t;
public myHelper(Context _ctx)
{
this.ctx = _ctx;
t = new tQuery(_ctx);
}
Observable<ArrayList<iApp>> getApps()
{
return makeObservable(t.getApps())
.subscribeOn(Schedulers.computation());
}
private static <T> Observable<T> makeObservable(final Callable<T> func) {
return Observable.create(
new Observable.OnSubscribe<T>() {
#Override
public void call(Subscriber<? super T> subscriber) {
try {
subscriber.onNext(func.call());
} catch (Exception ex) {
subscriber.onError(ex);
}
}
});
}
}
However my on complete never runs. I have checked the onNext by looping through the results of iApp and outputting one of the fields so I can see that the data is being collected, however my compareLists function is never run.
Could someone explain my oversight?
Well that was embarassing!
private static <T> Observable<T> makeObservable(final Callable<T> func) {
return Observable.create(
new Observable.OnSubscribe<T>() {
#Override
public void call(Subscriber<? super T> subscriber) {
try {
subscriber.onNext(func.call());
subscriber.onCompleted();
} catch (Exception ex) {
subscriber.onError(ex);
}
}
});
}
Related
I've built an headless webview in an Android application for scrape an URL from a webpage. Every time I retrieve an URL I may need to redoing the scraping in the webpage with this URL. I'm using RxJava to handle these operations concurrently, and I'm using the flatMap function to make a recursive call.
The problem is that I need to dispose the WebView in the mainThread, so I tried to add .unsubscribeOn(AndroidSchedulers.mainThread()) but it seems it doesn't work, and the dispose() method in HeadlessRequest is called in the last thread I called observeOn(Schedulers.computation()). What should I change to execute the dispose() method in the mainThread?
This is my code:
HeadlessRequest
public class HeadlessRequest implements Disposable {
...
private class HeadlessWebView extends WebView {
...
private void destroyWebView() {
this.removeAllViews();
this.clearCache(false);
this.loadUrl("about:blank");
this.onPause();
this.removeAllViews();
this.destroy();
this.isDisposed = true;
}
}
#Override
public void dispose() {
// This doesn't print the mainThread id
Log.d(TAG, "Disposing on thread " + Thread.currentThread().getId());
this.webView.destroyWebView();
this.webView = null;
}
#Override
public boolean isDisposed() {
return (this.webView == null || this.webView.isDisposed);
}
}
NetworkUtils
public static Single<Document> downloadPageHeadless(final String url, final int delay, final Context context) {
return Single.create((SingleEmitter<Document> emitter) -> {
try {
emitter.setDisposable(new HeadlessRequest(url, USER_AGENT, delay, context, emitter::onSuccess, emitter::onError));
} catch (Exception e) {
emitter.onError(e);
}
}).unsubscribeOn(AndroidSchedulers.mainThread()) // It MUST be executed on the mainThread
.subscribeOn(AndroidSchedulers.mainThread());
}
ServerService
private static Single<String> resolveRecursive(String url, Context context) {
Server server = getServerInstance(url, context);
if (server == null) {
return Single.error(new UnsupportedOperationException("Server for " + url + " not supported"));
} else if (server.isVideo()) {
return server.resolve(url, context); // This method return a Single with observeOn(Schedulers.computation())
} else {
return server.resolve(url, context)
.observeOn(Schedulers.computation())
.flatMap(resolvedUrl -> resolveRecursive(resolvedUrl, context));
}
}
public static Single<String> resolveURL(String url, Context context) {
return resolveRecursive(url, context)
.observeOn(AndroidSchedulers.mainThread());
}
At the end I found another method to dispose the webview in the mainThread without RxJava. I've used the post method of the WebView.
private void destroyWebView() {
this.post(() -> {
this.removeAllViews();
this.clearCache(false);
this.loadUrl("about:blank");
this.onPause();
this.removeAllViews();
this.destroy();
this.isDisposed = true;
});
}
I am trying to call the web service to fetch the data and storing it into database using following code. I have created a separate class to perform following operation.
Now , the issue is i want to notify my activity when i successfully fetch and store data in database. if some error occurs then i want to show that on UI itself.
somehow i am able to write a code to fetch the data using pagination but not sure how would i notify UI where i can subscribe catch the update related to progress and error if any.
public Flowable<Response> getFitnessData() {
Request request = new Request();
request.setAccess_token("d80fa6bd6f78cc704104d61146c599bc94b82ca225349ee68762fc6c70d2dcf0");
Flowable<Response> fitnessFlowable = new WebRequest()
.getRemoteClient()
.create(FitnessApi.class)
.getFitnessData("5b238abb4d3590001d9b94a8",request.toMap());
fitnessFlowable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.takeUntil(response->response.getSummary().getNext()!=null)
.subscribe(new Subscriber<Response>() {
#Override
public void onSubscribe(Subscription s) {
s.request(Long.MAX_VALUE);
}
#Override
public void onNext(Response response) {
Log.e(TAG, "onNext" );
if(response !=null){
if(response.getFitness()!=null && response.getFitness().size()!=0){
Realm realm = Realm.getDefaultInstance();
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.copyToRealmOrUpdate(response.getFitness());
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
Log.i(TAG, "onSuccess , fitness data saved");
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
Log.i(TAG, "onError , fitness data failed to save"+error.getMessage() );
}
});
}else{
Log.i(TAG, "onError , no fitness data available" );
}
}else{
Log.i(TAG, "onError , response is null" );
}
}
#Override
public void onError(Throwable t) {
Log.e(TAG, "onError" +t.getMessage());
}
#Override
public void onComplete() {
Log.e(TAG, "onComplete");
}
});;
return null;
}
Updated
Created RxBus to propagate events and error on UI
public class RxBus {
private static final RxBus INSTANCE = new RxBus();
private RxBus(){}
private PublishSubject<Object> bus = PublishSubject.create();
public static RxBus getInstance() {
return INSTANCE;
}
public void send(Object o) {
bus.onNext(o);
}
public void error(Throwable e){bus.onError(e);}
public Observable<Object> toObservable() {
return bus;
}
}
in activity
FitnessRepo fitnessRepo = new FitnessRepo();
fitnessRepo.getFitnessData();
RxBus.getInstance().toObservable().subscribe(new Observer<Object>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(Object o) {
if(o instanceof RealmList ){
RealmList<Fitness> realmList = (RealmList<Fitness>) o;
Log.e(TAG,"Fitness data size "+realmList.size());
}
}
#Override
public void onError(Throwable e) {
Log.e(TAG,e.getMessage()+ "");
if (e instanceof HttpException) {
ResponseBody body = ((HttpException) e).response().errorBody();
try {
Response response= LoganSquare.parse(body.byteStream(),Response.class);
if(response.getErrors() !=null)
if(response.getErrors().size() > 0)
Log.e(TAG, "Error "+response.getErrors().get(0).getErrors());
} catch (IOException t) {
t.printStackTrace();
}
}
}
#Override
public void onComplete() {
}
});
in a web service call
public void getFitnessData() {
Request request = new Request();
request.setAccess_token("d80fa6bd6f78cc704104d61146c599bc94b82ca225349ee68762fc6c70d2dcf0");
request.setEnd_date("2018-07-01T00:00:00");
Flowable<Response> fitnessFlowable = new WebRequest()
.getRemoteClient()
.create(FitnessApi.class)
.getFitnessData("5b238abb4d3590001d9b94a8",request.toMap());
fitnessFlowable.subscribeOn(Schedulers.io())
.takeUntil(response->response.getSummary().getNext()!=null)
.doOnNext((response) -> {
if(response ==null || response.getFitness() == null || response.getFitness().isEmpty()) {
Log.e(TAG, " Error ");
return;
}
RxBus.getInstance().send(response.getFitness());
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction((realm) -> {
realm.copyToRealmOrUpdate(response.getFitness());
});
}
}).subscribe(item ->{
},
error ->{
RxBus.getInstance().error(error);
});
}
I have good news for you! You can delete almost all of that code and just make it generally better as a result!
public void fetchFitnessData() {
Request request = new Request();
request.setAccess_token("d80fa6bd6f78cc704104d61146c599bc94b82ca225349ee68762fc6c70d2dcf0");
Flowable<Response> fitnessFlowable = new WebRequest()
.getRemoteClient()
.create(FitnessApi.class)
.getFitnessData("5b238abb4d3590001d9b94a8",request.toMap());
fitnessFlowable.subscribeOn(Schedulers.io())
.takeUntil(response->response.getSummary().getNext()!=null)
.doOnNext((response) -> {
if(response ==null || response.getFitness() == null || response.getFitness().isEmpty()) return;
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction((realm) -> {
realm.insertOrUpdate(response.getFitness());
});
}
}
}).subscribe();
}
This method is on a background thread now and returns void, so the way to emit stuff out of this method would be to use either a PublishSubject (one for success, one for failure) or an EventBus.
private PublishSubject<Object> fitnessResults;
public Observable<Object> observeFitnessResults() {
return fitnessResults;
}
public static class Success {
public Success(List<Fitness> data) {
this.data = data;
}
public List<Fitness> data;
}
public static class Failure {
public Failure(Exception exception) {
this.exception = exception;
}
public Exception exception;
}
public void fetchFitnessData() {
...
fitnessResults.onNext(new Success(data));
} catch(Exception e) {
fitnessResults.onNext(new Failure(e));
And then
errors = observeFitnessResults().ofType(Error.class);
success = observeFitnessResults().ofType(Success.class);
There are different ways to achieve this. I will never handle the subscriptions on my own out of a lifecycle scope as it creates a possibility of memory leak. In your case it seems that both success and failure is bound to the UI so you can simply do this.
public Completable fetchFitnessData() {
Request request = new Request();
request.setAccess_token("d80fa6bd6f78cc704104d61146c599bc94b82ca225349ee68762fc6c70d2dcf0");
Flowable<Response> fitnessFlowable = new WebRequest()
.getRemoteClient()
.create(FitnessApi.class)
.getFitnessData("5b238abb4d3590001d9b94a8",request.toMap());
return fitnessFlowable.subscribeOn(Schedulers.io())
.takeUntil(response->response.getSummary().getNext()!=null)
.doOnNext((response) -> {
if(response ==null || response.getFitness() == null || response.getFitness().isEmpty()) return;
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction((realm) -> {
realm.insertOrUpdate(response.getFitness());
});
}
}
}).ignoreElements();
}
At UI level, you can just handle your subscription with both success and failure. In case you need success model can replace Completable with Single or Flowable.
fetchFitnessData.subscrible(Functions.EMPTY_ACTION, Timber::d);
The major advantage with this approach is that you handle your subscription lifecycles.
I have 3 layers in my app. Layer1 subscribes to Observable from layer2. Layer2 subscribes to layer3 in order to emit returned data to layer1.
Layer1
layer2.getData(data).subscribe(newData -> {Log.d("onNext", "returned");},
throwable -> {Log.d("onError", throwable.getMessage());});
Suppose layer3 has a method called downloadDataFromApi(data);
public Observable<Data> getData(String data) {
return Observable.create(new Observable.OnSubscribe<Data>() {
#Override
public void call(Subscriber<? super Data> subscriber) {
Data data = new Data();
subscriber.onNext(data);
subscriber.onCompleted();
// Can't find a way to connect to layer3.
}
});
}
What do I need to do in layer2's getData() method? I basically want to have logics before returning Observable back to layer1.
Does that make sense?
Just return the Observable directly. Then layer1 handles subscription as usual.
class Layer2 {
public Observable<Data> getData(String data) {
return layer3.getData(data);
}
}
From what I see you have 3 layers (presentation, business logic, data access).
So what you could do is the following:
class PresentationLayer {
private BusinessLogicLayer layer;
PresentationLayer() {
layer = new BusinessLogicLayer();
}
public void showName() {
layer.getNameWithoutRxPrefix()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<String>() {
#Override
public void accept(String name) throws Exception {
// show name somewhere
Log.d("PresentationLayer", "name: " + name);
}
});
}
}
class BusinessLogicLayer {
private DataAccessLayer layer;
BusinessLogicLayer() {
layer = new DataAccessLayer();
}
public Observable<String> getNameWithoutRxPrefix() {
return layer.getName()
.map(new Function<String, String>() {
#Override
public String apply(String name) throws Exception {
return name.replace("Rx", "");
}
});
}
}
class DataAccessLayer {
public Observable<String> getName() {
return Observable.just("RxAndroid");
}
}
As you can see, I return an Observable in my data access layer (getName), and chain another method to it in my business logic method (map) before returning it to the presentation layer.
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
}