I want to perform long calculations on background thread using RXJava in android. After calculation I am trying to present the result in Recylerview. I am using following piece of code:
Observable.just("true")
.subscribeOn(Schedulers.io())
.map(new Func1<String, String>() {
#Override
public String call(String s) {
feedlist.clear();
if (eventFeedItems != null && !eventFeedItems.isEmpty()) {
for (int i = 0; i < eventFeedItems.size(); i++) {
if (eventFeedItems != null && eventFeedItems.get(i) != null
&& ((eventFeedItems.get(i).getType() != null && eventFeedItems.get(i).getType().equalsIgnoreCase("EVENT"))
|| (eventFeedItems.get(i).getActivityRequestType() != null && eventFeedItems.get(i).getActivityRequestType().equalsIgnoreCase(EventConstants.TRENDING_ACTIVITY)))) {
if (eventFeedItems.get(i).getActivityRequestType() != null && !eventFeedItems.get(i).getActivityRequestType().equalsIgnoreCase("")) {
feedlist.add(new FeedsListModel(eventFeedItems.get(i), eventFeedItems.get(i).getActivityRequestType(), null));
} else if (eventFeedItems.get(i).getRequestType() != null && !eventFeedItems.get(i).getRequestType().equalsIgnoreCase("")) {
feedlist.add(new FeedsListModel(eventFeedItems.get(i), eventFeedItems.get(i).getRequestType(), null));
} else
feedlist.add(new FeedsListModel(eventFeedItems.get(i), EventConstants.ATTENDEE_POST, null));
}
}
}
Log.d("calculations","Completed");
return "";
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<String>() {
#Override
public void call(String s) {
// feed_list.setLayoutManager(mLayoutManager);
// feedListAdapter.notifyDataSetChanged();
Log.d("Adapter", "Set");
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
Log.d("Exception", "oh! fish...");
throwable.printStackTrace();
}
});
With the above piece of code I am facing UI Hindring as the ArrayList eventFeedItems size is of about more then 300 items.I am new to RXJava. Please help me out.
you will not achieve concurrency using map-Operator.
The first subscribeOn will move all emitions to IO-scheduler. No concurrency happening here.
.subscribeOn(Schedulers.io())
The map-operator will be called synchronously from previous thread. In your case it would be some thread from IO-threadpool.
.map(new Func1<String, String>() {
After the map-Operator has been executed, you will move the value from the IO-thread to the Android-UI-event-loop with
.observeOn(AndroidSchedulers.mainThread())
After the value has been translated from IO-thread to the UI-thread the next value from the initial observable will be processed.
Observable.just("true")
In your example there will be no more values because you only produce one value.
In order to achieve concurrency you should use flatMap instead of map. And use subscribeOn() in the flatMap to create each stream on another thread.
Please consider this example, to see how concurrency is happening. Every observable will be subscribed at once, so the max. time for the teset would be something around 5 seconds. If now concurrency would happen it would take 1+2+3+4+5 seconds plus execution time.
#Test
public void name1() throws Exception {
Observable<Integer> value = Observable.just(1_000, 2_000, 3_000, 4_000, 5_000)
.flatMap(i -> Observable.fromCallable(() -> doWork(i)).subscribeOn(Schedulers.io())
).doOnNext(integer -> System.out.println("value"));
value.test().awaitTerminalEvent();
}
private int doWork(int sleepMilli) {
try {
Thread.sleep(sleepMilli);
} catch (InterruptedException e) {
e.printStackTrace();
}
return -1;
}
If you want to know more about how concurrency is happening with flatMap please consider reading http://tomstechnicalblog.blogspot.de/2015/11/rxjava-achieving-parallelization.html
In regard to your code I would suggest:
Move the anonymouse implementation of interface to a private inner class implementation and use an instance of it. You will get a more readable observable
Don't use side-effects to global variables from operators within. You
will get race-condition if concurrency is involved.
List<FeedsListModel> eventFeedItems = Arrays.asList(new FeedsListModel(), new FeedsListModel());
Observable<FeedsListModel> feedsListModelObservable = Observable.fromIterable(eventFeedItems)
.flatMap(feedsListModel -> Observable.fromCallable(() -> calculation(feedsListModel))
.subscribeOn(Schedulers.computation())
);
feedsListModelObservable
.toList()
.observeOn(Schedulers.io())
.subscribe(feedsListModels -> {
// do UI stuff
});
Helping-method:
private FeedsListModel calculation(FeedsListModel model) {
// do calculation here
return new FeedsListModel();
}
Related
I have this code :
getLocationObservable() // ---> async operation that fetches the location.
// Once location is found(or failed to find) it sends it to this filter :
.filter(location -> { // ---> I want to use this location in the the onNext in the end
after finishing some calculation here, I either return 'true' and continue
to the next observable which is a Retrofit server call, or simply
return 'false' and quit.
})
.flatMap(location -> getRetrofitServerCallObservable( location )
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new Observer<MyCustomResponse>() {
#Override
public void onSubscribe(Disposable d) {
_disposable = d;
}
#Override
public void onNext(MyCustomResponse response) {
// I want to be able to use the `location` object here
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
I want to be able to use the location object from line 3(first observable), in the "onNext" that is trigerred by the second observable.
I can't manage to work it out.. any help would be much appreciated.
Instead of
getRetrofitServerCallObservable( location )
you could map the result to be a Pair (from your favourite library) of the response and the location:
getRetrofitServerCallObservable( location ).map(response -> Pair.create(location, response))
Then, in your onNext, you'd be receiving Pair<Location,MyCustomResponse> instances.
If you don't want to use a Pair class, you could use Object[], but if you do, please don't tell me about it :P
I have 2 collections, which buffer location update events:
private List<LocationGeoEvent> mUpdateGeoEvents = new ArrayList<>();
private List<LocationRSSIEvent> mUpdateRSSIEvents = new ArrayList<>();
There is also present in my code:
private final ScheduledExecutorService mSaveDataExecutor = Executors.newSingleThreadScheduledExecutor();
private boolean mSaveDataScheduled;
private final Object mEventsMonitor = new Object();
private ScheduledFuture<?> mScheduledStopLocationUpdatesFuture;
private final ScheduledExecutorService mStopLocationUpdatesExecutor = Executors.newSingleThreadScheduledExecutor();
I add event to this colections like this:
public void appendGeoEvent(LocationGeoEvent event) {
synchronized (mEventsMonitor) {
mUpdateGeoEvents.add(event);
scheduleSaveEvents();
}
}
The same goes for the RSSI event
Now, the scheduleSaveEvents method looks like this:
private void scheduleSaveEvents() {
synchronized (mSaveDataExecutor) {
if (!mSaveDataScheduled) {
mSaveDataScheduled = true;
mSaveDataExecutor.schedule(
new Runnable() {
#Override
public void run() {
synchronized (mSaveDataExecutor) {
saveEvents(false);
mSaveDataScheduled = false;
}
}
},
30,
TimeUnit.SECONDS);
}
}
}
The problem is, that i need to synchronize the other method which stops the updates. It is triggered like this:
private void scheduleStopLocationUpdates() {
synchronized (mStopLocationUpdatesExecutor) {
if (mScheduledStopLocationUpdatesFuture != null)
mScheduledStopLocationUpdatesFuture.cancel(true);
mScheduledStopLocationUpdatesFuture = mStopLocationUpdatesExecutor.schedule(
new Runnable() {
#Override
public void run() {
synchronized (mStopLocationUpdatesExecutor) {
stopLocationUpdates();
saveEvents(true);
cleanAllReadingsData();
}
}
},
45,
TimeUnit.SECONDS);
}
}
In the saveEvents method i do:
private void saveEvents(boolean locationUpdatesAboutToStop) {
synchronized (mEventsMonitor) {
if (mUpdateGeoEvents.size() > 0 || mUpdateRSSIEvents.size() > 0) {
//do something with the data from buffered collection arrayLists and with the boolean locationUpdatesAboutToStop
mUpdateGeoEvents.clear();
mUpdateRSSIEvents.clear();
}
}
}
Is there a way to refactor this simplier to RxJava using Kotlin?
UPDATE
Here is my appendRSSIevents method:
private fun appendRSSIEvent(event: LocationRSSIEvent) {
synchronized(mEventsMonitor) {
if (!shouldSkipRSSIData(event.nexoIdentifier)) {
mUpdateRSSIEvents.add(event)
acknowledgeDevice(event.nexoIdentifier)
scheduleSaveEvents()
startLocationUpdates()
} else
removeExpiredData()
}
}
You can buffer the two streams of data and then combine them for saving. Also, you can use the buffer trigger to stop the updates as well.
PublishSubject<LocationGeoEvent> mUpdateGeoEventsSubject = PublishSubject.create();
PublishSubject<LocationRSSIEvent> mUpdateRSSIEventsSubject = PublishSubject.create();
public void appendGeoEvent(LocationGeoEvent event) {
mUpdateGeoEventsSubject.onNext( event );
triggerSave.onNext( Boolean.TRUE );
}
and the same for RSS feed.
Now we need triggers that will be used to drive the saving step.
PublishSubject<Boolean> triggerSave = PublishSubject.create();
PublishSubject<Boolean> triggerStopAndSave = PublishSubject.create();
Observable<Boolean> normalSaveTrigger = triggerSave.debounce( 30, TimeUnit.SECONDS );
Observable<Boolean> trigger = Observable.merge( normalSaveTrigger, triggerStopAndSave );
The trigger observable fires when either the normal save process fires or if we are stopping the save.
private void saveEvents(
List<LocationGeoEvent> geo,
List<LocationRSSIEvent> rss,
boolean locationUpdatesAboutToStop) {
synchronized (mEventsMonitor) {
if (geo.size() > 0 || rss.size() > 0) {
//do something with the data from buffered collection arrayLists and with the boolean locationUpdatesAboutToStop
}
}
}
private void scheduleStopLocationUpdates() {
stopLocationUpdates();
triggerStopAndSave.onNext( Boolean.FALSE );
cleanAllReadingsData();
}
Observable.zip( mUpdateGeoEventsSubject.buffer( trigger ),
mUpdateRSSIEventsSubject.buffer( trigger ),
trigger, (geo, rss, trgr) -> saveEvents(geo, rss, trgr) )
.subscribe();
You will still need to some tuning with respect to multi-threading and safety. The first step would be to turn the various subjects into SerializedSubjects so that multiple threads can emit events.
If you want saveEvents to run on a particular scheduler, you will either need to add an intermediate data structure, a triple, to pass the parameters through observeOn() operator, or apply observeOn() operator to each of zip() arguments.
i would like to implement a Pollingservice which calls a REST Api every nDelay Seconds and notify all subscribers if the data has been changed. Now i have a little problem with my code since it always returns a value to my Consumer, even if the data has not been changed.
private Observable<List<HueLight>> pollingLightsObservable = null;
public Observable<List<HueLight>> getPollingLightsObservable() {
if (pollingLightsObservable == null) {
pollingLightsObservable = Observable.fromCallable(
() -> LightManager
.getInstance(context)
.getLights()
.blockingSingle())
// .distinctUntilChanged( (l1, l1) -> !l1.equals(l2) )
.repeatWhen(o -> o.concatMap(v -> Observable.timer(1, TimeUnit.SECONDS)));
}
return pollingLightsObservable;
}
Enabling or using the distinctUntilChanged dont change anything. Doesnt matter if i put it before or after my repeatWhen.
Since my RetroFit Call returns an Observable, i have to use blockingSingle(). Using the Observable directly it leads into a return of "4, 8, 12, 16, .." items with this sample:
LightManager.getInstance(context).getLights()
.repeatWhen(o -> o.concatMap(v -> Observable.timer(1, TimeUnit.SECONDS)))
Currently i subscribe from different classes/activites with
this.lightChangeSubscriber = PollingManager
.getInstance(getContext())
.getPollingLightsObservable()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(hueLights -> {
{
Log.d(TAG, "Lights received successfully! Size=" + hueLights.size());
}
});
I would lovely avoid using interfaces and timer to create the polling. What would you recommend ?
what about using some custom filter?
public class FilterDuplicateHueConfig implements Predicate<HueConfig> {
private HueConfig lastVal;
#Override
public boolean test(HueConfig newVal) {
if(lastVal == null) {
lastVal = newVal;
return true;
}
... compare here the two values and return true/false appropriately...
}
}
I am messing around some with the google awareness api and now my understanding of RxJava is limiting me.
What I want to achieve in the end:
I want to get a Weather and a Location from the Api, and merge them into one object that I can pass on to my view for update.
However, I'm not sure how I achieve the returning of an Observable from the api callback here since it has void return type, and how to achieve merging of the weather and location object from api.getWeather and api.getLocation
public void requestUserCurrentInfo() {
Subscription userInfo = getWeatherLocation().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(userinfo ->
Log.d(TAG,userinfo.something()));
}
public Observable<UserInfo> getWeatherLocation () {
try {
Awareness.SnapshotApi.getWeather(client)
.setResultCallback(weather -> {
if (!weather.getStatus().isSuccess()) {
Log.d(TAG, "Could not get weather");
return;
}
//How do I do here?
return weather.getWeather();
});
Awareness.SnapshotApi.getLocation(mGoogleApiClient)
.setResultCallback(retrievedLocation -> {
if(!retrievedLocation.getStatus().isSuccess()) return;
Log.d("FRAG", retrievedLocation.getLocation().getLatitude() + "");
});
} catch (SecurityException exception) {
throw new SecurityException("No permission " + exception);
}
}
For my other things in my Project, I get some stuff through a REST api following the repository pattern, then I can get it like this because every step returns a Observable< SmhiResponse >
getWeatherSubscription = getWeatherUsecase.execute().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(
smhiResponseModel -> {Log.d(TAG,"Retrieved weather"); locationView.hideLoading();},
err -> {Log.d(TAG,"Error fetching weather"); locationView.hideLoading();}
);
You don't return an observable from the callback but wrap your callbacks into observables to make them combinable (untested):
Observable<WeatherResult> weatherObservable = Observable.create(subscriber -> {
Awareness.SnapshotApi.getWeather(client)
.setResultCallback(weather -> {
if (!weather.getStatus().isSuccess()) {
subscriber.onError(new Exception("Could not get weather."));
Log.d(TAG, "Could not get weather");
} else {
//How do I do here?
subscriber.onNext(weather);
subscriber.onCompleted();
}
});
});
Observable<LocationResult> locationObservable = Observable.create(subscriber -> {
Awareness.SnapshotApi.getLocation(mGoogleApiClient)
.setResultCallback(retrievedLocation -> {
if(!retrievedLocation.getStatus().isSuccess()) {
subscriber.onError(new Exception("Could not get location."));
} else {
Log.d("FRAG", retrievedLocation.getLocation().getLatitude() + "");
subscriber.onNext(retrievedLocation);
subscriber.onCompleted();
}
});
});
now combine them via .combineLatest() or .zip():
Observable<CombinedResult> combinedResults = Observable.zip(weatherObservable, locationObservable,
(weather, location) -> {
/* somehow combine weather and location then return as type "CombinedResult" */
});
don't forget to subscribe, otherwise none of them gets executed:
combinedResults.subscribe(combinedResult -> {/*do something with that stuff...*/});
Observable.combineLatest(getWeather (), getLocation(), new Func2<List<Object_A>, List<Object_B>, Object>() {
#Override
public Object call(Object o, Object o2) {
combine both results and return the combine result to observer
}
})
getweather() and getlocation() return observables
I have an observable which might fail with a special exception in which case I want to show a dialog with a retry button. I've seen this answer, but it doesn't quite do what I want. I wasn't able to use retryWhen to solve my problem, so instead I used onErrorResumeNext. If you can come up with a way to do the same with retryWhen, please tell.
Right now I have this piece of code:
public Observable<Order> proceedWithOrdering(Activity activity) {
return apiService.createOrder()
.subscribeOn(Schedulers.io())
.compose(applyRetryLogic(activity))
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread());
}
public <T extends ApiResponse> Observable.Transformer<T, T> applyRetryLogic(Activity activity) {
return observable -> observable
.onErrorResumeNext(retry(observable, activity))
.subscribeOn(AndroidSchedulers.mainThread());
}
public <T> Func1<Throwable, ? extends Observable<? extends T>> retry(Observable toRetry, Activity activity) {
return throwable -> {
if (throwable instanceof NetworkException) {
MaterialDialog dialog = retryDialog(activity);
View retry = dialog.getActionButton(DialogAction.POSITIVE);
View cancel = dialog.getActionButton(DialogAction.NEGATIVE);
Observable<Object> retryClick = RxView.clicks(retry).map(o -> {
dialog.dismiss();
return o;
});
Observable<Object> cancelClick = RxView.clicks(cancel).flatMap(o -> {
dialog.dismiss();
return Observable.error(throwable);
});
dialog.show();
return Observable.amb(retryClick, cancelClick)
.flatMap(o -> toRetry.compose(applyRetryLogic(activity)));
} else {
return Observable.error(throwable);
}
};
}
The problem is that the call inside the retry gets executed not on the main thread and it raises the Can't create handler inside thread that has not called Looper.prepare() exception.
The question is - how do I force it to be executed on the main thread? As you can see, I have already tried doing subscribeOn(AndroidSchedulers.mainThread()) right after both compose and onErrorResumeNext with no luck.
I have tested my code using simple observables that don't operate on separate threads and it works fine.
You can accomplish this by flatMapping a PublishSubject which is then updated once the relevant button is pressed. Here is a classical Java Swing example.
public class RetryWhenEnter {
public static void main(String[] args) {
AtomicInteger d = new AtomicInteger();
Observable<Integer> source = Observable.just(1);
source.flatMap(v -> {
if (d.incrementAndGet() < 3) {
return Observable.error(new RuntimeException());
}
return Observable.just(v);
})
.retryWhen(err -> {
return err.flatMap(e -> {
System.out.println(Thread.currentThread() + " Error!");
PublishSubject<Integer> choice = PublishSubject.create();
SwingUtilities.invokeLater(() -> {
int c = JOptionPane.showConfirmDialog(null,
e.toString() + "\r\nRetry?", "Error",
JOptionPane.YES_NO_OPTION);
if (c == JOptionPane.YES_OPTION) {
choice.onNext(1);
} else {
choice.onCompleted();
}
});
return choice;
});
}).subscribe(System.out::println,
Throwable::printStackTrace);
}
}
Edit:
Or use observeOn(AndroidSchedulers.mainThread()) just before onErrorResumeNext or when using retryWhen: retryWhen(o -> o.observeOn(AndroidSchedulers.mainThread())...).
Edit 2
I've rolled back the change so the answer is meaningful again.
There is a way of solving my problem using retryWhen (thanks #akarnokd):
public <T extends ApiResponse> Observable.Transformer<T, T> applyRetryLogic(Activity activity) {
return observable -> observable
.retryWhen(err -> err.flatMap(throwable -> {
L.d(Thread.currentThread() + " Error!");
if (throwable instanceof NetworkException) {
PublishSubject<Integer> choice = PublishSubject.create();
activity.runOnUiThread(() -> {
MaterialDialog dialog = retryDialog(activity);
View retry = dialog.getActionButton(DialogAction.POSITIVE);
View cancel = dialog.getActionButton(DialogAction.NEGATIVE);
RxView.clicks(retry).subscribe(o -> {
dialog.dismiss();
choice.onNext(1);
});
RxView.clicks(cancel).subscribe(o -> {
dialog.dismiss();
choice.onError(throwable);
});
dialog.show();
});
return choice;
} else {
return Observable.error(throwable);
}
}));
}