RxJava observables not emitting events - android

My android application has a FeedDetailFragment that displays Feed details. A feed has basic information and metadata, which are retrieved through two separate calls to the server. The server interface is filled in with Retrofit. I have implemented something that, to my novice Rx knowledge, looks logical. However, as you may have guessed, it doesn't work.
External classes:
FeedInfo - parcellable class that contains basic feed info
FeedMetadata - parcellable class that contains metadata about feed
Feed - auxiliary class that combines feed info and metadata, providing some hepler functions
UvClient - server interface implemented with Retrofit
Relevant FeedDetailFragment code:
public class FeedDetailFragment extends Fragment implements OnMapReadyCallback {
public static final String ARG_FEED_ID = "feed_id";
public static final String ARG_FEED_INFO = "feed_info";
public static final String ARG_FEED_METADATA = "feed_metadata";
public static final int INVALID_FEED_ID = -1;
...
private class PlaceFeedSubscriber extends Subscriber<Pair<GoogleMap, Feed>> {
#Override
public void onNext(Pair<GoogleMap, Feed> pair) {
Log.i(TAG, String.format("Placing feed %d on [%f, %f] onto map %s",
pair.second.getInfo(),
pair.second.getMetadata().getSensorLatitude(),
pair.second.getMetadata().getSensorLongitude(),
pair.first.getMapType()));
pair.first.addMarker(new MarkerOptions()
.position(new LatLng(
pair.second.getMetadata().getSensorPoint().getCoordinates()[1],
pair.second.getMetadata().getSensorPoint().getCoordinates()[0]))
.title("Marker"));
mMapAPI.moveCamera(CameraUpdateFactory.newLatLngZoom(
new LatLng(
pair.second.getMetadata().getSensorPoint().getCoordinates()[1],
pair.second.getMetadata().getSensorPoint().getCoordinates()[0])
, 15));
}
#Override
public void onCompleted() {
Log.i(TAG, "Completed drawing of feed");
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "Drawing of feed failed with: " + e);
}
}
public FeedDetailFragment() {
mMapObservable = Observable.empty().subscribeOn(Schedulers.io());
mFeedIdObservable = Observable.empty().subscribeOn(Schedulers.io());
mFeedInfoObservable = Observable.empty();
mFeedMetadataObservable = Observable.empty();
// Start fetching new feed information
mFeedIdObservable.doOnEach(new Action1<Integer>() {
#Override
public void call(Integer feedId) {
Log.d(TAG, "Got a new feed id - " + feedId);
mFeedInfoObservable.mergeWith(mUvClient.getFeed(feedId));
}
});
// Start fetching new feed metadata
mFeedInfoObservable.doOnEach(new Action1<FeedInfo>() {
#Override
public void call(FeedInfo feedInfo) {
Log.d(TAG, "Got a new feed info - " + feedInfo.getTitle());
mFeedMetadataObservable.mergeWith(mUvClient.getFeedMetadata(feedInfo.getId()));
}
});
// Produce a new feed
mFeedObservable = Observable.combineLatest(mFeedInfoObservable, mFeedMetadataObservable, new Func2<FeedInfo, FeedMetadata, Feed>() {
#Override
public Feed call(FeedInfo feedInfo, FeedMetadata feedMetadata) {
return new Feed(feedInfo, feedMetadata);
}
});
// Render the feed onto map
Observable.combineLatest(mFeedObservable, mMapObservable, new Func2<Feed, GoogleMap, Pair<GoogleMap, Feed>>() {
#Override
public Pair<GoogleMap, Feed> call(Feed feed, GoogleMap map) {
return new Pair(map, feed);
}
}).observeOn(AndroidSchedulers.mainThread())
.subscribe(new PlaceFeedSubscriber());
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle arguments = getArguments();
if (arguments.containsKey(ARG_FEED_ID)) {
setFeed(arguments.getInt(ARG_FEED_ID));
}
else if (arguments.containsKey(ARG_FEED_INFO)) {
if (arguments.containsKey(ARG_FEED_METADATA)) {
setFeed((FeedInfo)Parcels.unwrap(arguments.getParcelable(ARG_FEED_INFO)),
(FeedMetadata)Parcels.unwrap(arguments.getParcelable(ARG_FEED_METADATA)));
}
else {
setFeed((FeedInfo)Parcels.unwrap(arguments.getParcelable(ARG_FEED_INFO)));
}
}
}
...
#Override
public void onMapReady(GoogleMap googleMap) {
mMapAPI = googleMap;
mMapObservable.mergeWith(Observable.just(googleMap));
}
/**
* Sets the feed ID to be shown in the fragment. This triggers the chain of fetching feed info
* and feed metadata, finally displaying it on the map.
* #param feedId ID of the feed to display in the fragment.
*/
public void setFeed(int feedId) {
Log.d(TAG, String.format("Setting new feed ID - %d", feedId));
mFeedIdObservable.mergeWith(Observable.just(feedId));
}
/**
* Sets feed info. This triggers fetching of feed metadata, finally displaying it on the map.
* #param feedInfo Information of the feed to display on the map.
*/
public void setFeed(FeedInfo feedInfo) {
Log.d(TAG, String.format("Setting new feed info - %s", feedInfo.getTitle()));
mFeedInfoObservable.mergeWith(Observable.just(feedInfo));
}
/**
* Displays feed info on the map.
* #param feedInfo Information of the feed to display on the map.
* #param feedMetadata Metadata of the feed to display on the map.
*/
public void setFeed(FeedInfo feedInfo, FeedMetadata feedMetadata) {
Log.d(TAG, String.format("Setting new feed info and metadata - %s", feedInfo.getTitle()));
mFeedObservable.mergeWith(Observable.just(new Feed(feedInfo, feedMetadata)));
}
}
The log output I see is as follows:
Setting new feed info - SampleFeed
Completed drawing of feed
My overall idea was that the observables would emit new data when I merge it in. Some observables are created empty so that they do not emit anything but I can still work with them.
The potential flow could be as follows:
Activity gets a callback from FeedListFragment notifying the id of the feed that was clicked
Activity checks either gets and passes FeedInfo to FeedDetailFragment.setFeed or invokes FeedDetailFragment.setFeed with the feed's id (lets assume the latter for completeness)
FeedDetailFragment merges new observable with the received feed id
The merge triggers emission of new event on mFeedIdObservable
The .doOnEach of mFeedIdObservable kicks off Retrofit interface to fetch FeedInfo
The .doOnEach of mFeedInfoObservable kick off Retrofit interface to fetch FeedMetadata
The .combineLatest of mFeedObservable fires off when both mFeedInfoObservable and mFeedMetadataObservable return new data
Finally, getting the GoogleMap and Feed, the call is made to subscriber to draw the feed on the map
This is how the thought was put together in my head. Obviously, it is wrong. Where did I go wrong and how can I fix it? I'd love some pointers and maybe more general ideology/methodology approach teaching. Thanks for any advice!
UPDATE 1
So I've been trying to figure this out. Read more documents... a lot to learn. I've replaced Observable.empty() with Observable.never(). From documentation, I read that empty doesn't emit anything and completes, which is not what I want. On the other hand, never doesn't emit anything but does not complete. As such, I can use it for the purpose I'm seeking. Still not getting what I want but, I hope, one step closer.
UPDATE 2
Getting a bit more hang of it. Looking into source of .never() and .empty(), I see that the former does not call .onNext() and the latter calls .onComplete(). There is nothing in the middle I can choose. Started looking around for alternatives. Basically, my code doesn't execute because, in my previous tries, observable either completed immediately or never proceeded to call next. However, there is nothing to call .onNext() in the beginning. As such, I need a placeholder.
Reading more docs, I came across Subjects. In particular, PublishSubject doesn't emit anything until a subscriber subscribes. This seemed like a viable solution. However, the subscriber must subscribe directly to the subject. This didn't seem to work with .mergeWith() upon the subject.
Will not give up :)
UPDATE 3
Thanks to #dwursteisen, I continued with PublishSubject approach. This is the relevant code that changed:
...
private PublishSubject<GoogleMap> mMapObservable = null;
private PublishSubject<Feed> mFeedObservable = null;
private PublishSubject<Integer> mFeedIdObservable = null;
private PublishSubject<FeedInfo> mFeedInfoObservable = null;
private PublishSubject<FeedMetadata> mFeedMetadataObservable = null;
...
public FeedDetailFragment() {
mMapObservable = PublishSubject.create();
mFeedObservable = PublishSubject.create();
mFeedIdObservable = PublishSubject.create();
mFeedInfoObservable = PublishSubject.create();
mFeedMetadataObservable = PublishSubject.create();
mMapObservable.subscribe(new Action1<GoogleMap>() {
#Override
public void call(GoogleMap googleMap) {
mMapApi = googleMap;
}
});
mFeedMetadataObservable.subscribe(new Action1<FeedMetadata>() {
#Override
public void call(FeedMetadata feedMetadata) {
// no code
}
});
mFeedObservable.subscribe(new Action1<Feed>() {
#Override
public void call(Feed feed) {
// no code
}
});
// Start fetching new feed information
mFeedIdObservable.subscribe(new Action1<Integer>() {
#Override
public void call(Integer feedId) {
mUvClient.getFeed(feedId).subscribe(new Action1<FeedInfo>() {
#Override
public void call(FeedInfo feedInfo) {
mFeedInfoObservable.onNext(feedInfo);
}
});
}
});
// Start fetching new feed metadata
mFeedInfoObservable
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<FeedInfo>() {
#Override
public void call(FeedInfo feedInfo) {
mFeedTitle.setText(feedInfo.getTitle());
mUvClient.getFeedMetadata(feedInfo.getId()).subscribe(new Action1<FeedMetadata>() {
#Override
public void call(FeedMetadata feedMetadata) {
mFeedMetadataObservable.onNext(feedMetadata);
}
});
}
});
// Produce a new feed
Observable.combineLatest(mFeedInfoObservable, mFeedMetadataObservable, new Func2<FeedInfo, FeedMetadata, Feed>() {
#Override
public Feed call(FeedInfo feedInfo, FeedMetadata feedMetadata) {
Feed feed = new Feed(feedInfo, feedMetadata);
return feed;
}
}).subscribeOn(Schedulers.io()).subscribe(new Action1<Feed>() {
#Override
public void call(Feed feed) {
mFeedObservable.onNext(feed);
}
});
// Render the feed onto map
Observable.combineLatest(mFeedObservable, mMapObservable, new Func2<Feed, GoogleMap, Pair<GoogleMap, Feed>>() {
#Override
public Pair<GoogleMap, Feed> call(Feed feed, GoogleMap map) {
return new Pair(map, feed);
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(new PlaceFeedSubscriber());
}
...
#Override
public void onMapReady(GoogleMap googleMap) {
mMapObservable.onNext(googleMap);
}
/**
* Sets the feed ID to be shown in the fragment. This triggers the chain of fetching feed info
* and feed metadata, finally displaying it on the map.
* #param feedId ID of the feed to display in the fragment.
*/
public void setFeed(int feedId) {
mFeedIdObservable.onNext(feedId);
}
/**
* Sets feed info. This triggers fetching of feed metadata, finally displaying it on the map.
* #param feedInfo Information of the feed to display on the map.
*/
public void setFeed(FeedInfo feedInfo) {
mFeedInfoObservable.onNext(feedInfo);
}
/**
* Displays feed info on the map.
* #param feedInfo Information of the feed to display on the map.
* #param feedMetadata Metadata of the feed to display on the map.
*/
public void setFeed(FeedInfo feedInfo, FeedMetadata feedMetadata) {
mFeedObservable.onNext(new Feed(feedInfo, feedMetadata));
}
Obviously, now that I got the basics working, I'll go through it and do proper handling of errors, caching, and other conditions. However, I do have one question: is there any way to simplify the following code to directly use Retrofit observable instead of subscribing to it inside the subscribe... maybe Rx operator that would inject its resolution into mFeedInfoObservable?
mFeedIdObservable.subscribe(new Action1<Integer>() {
#Override
public void call(Integer feedId) {
mUvClient.getFeed(feedId).subscribe(new Action1<FeedInfo>() {
#Override
public void call(FeedInfo feedInfo) {
mFeedInfoObservable.onNext(feedInfo);
}
});
}
});
Also, I would love to hear any comments in the general approach. I'm still wrapping my head around Rx and my implementation is not best, I am sure.

mFeedInfoObservable = Observable.empty();
You build an empty Observable that will never emit value. So when you'll subscribe to this Observable, you'll be only notified of it's completion.
mFeedInfoObservable.mergeWith(Observable.just(feedInfo));
Observable are immutable. It's mean that calling a method won't change its state. mergeWith will produce a new Observable that is the result of the merge of an Observable with another.
So in your case, you build an new Observable that aren't used.
According to your code, it's seem that you need a Subject (like you mention : PublishSubject) to emit value from different user call.
private final Subject<Integer, Integer> subject = PublishSubject.create();
public void setFeed(int feedId) {
subject.onNext(feedId);
}
public FeedDetailFragment() {
subject.flatMap(feedId -> mUvClient.getFeed(feedId))
.subscribe(/**...**/);
}
Please note that doOnNext should be used for side effect call (ie: code that will change an element outside of your Observable, like logging, ...). I think in your case you may need other operators like flatMap, zip, ... in order to compose the result like what you want to achieve.

Related

Executing rx.Obseravables secuentially

I'm developing an Android App using Fernando Ceja's clean architecture. One of my Interactors or Use Cases is in charge of getting the User's feed data. In order to get the data, first I have to retrieve the User's Teams from a database table and then I have to get the Feed list from the server-side.
This is how I get the Teams from the database layer:
mTeamCache.getAllTeams().subscribe(new DefaultSubscriber<List<SimpleTeam>>() {
#Override
public void onNext(List<SimpleTeam> simpleTeams) {
super.onNext(simpleTeams);
mTeams = simpleTeams;
}
});
TeamCache is basically just another Interactor that takes care of getting all the teams that I have in the database.
Here's how I get the Feed data from the server-side:
mFeedRepository.getFeed(0, 50).subscribe(new ServerSubscriber<List<ApiFeedResponse>>() {
#Override
protected void onServerSideError(Throwable errorResponse) {
callback.onFeedFetchFailed(...);
}
#Override
protected void onSuccess(List<ApiFeedResponse> responseBody) {
//Do stuff with mTeams
callback.onFeedFetched(...);
}
});
My GetFeedInteractor class has a method called execute, where I pass through the Callback that I'm later using in the UI to handle the response. The issue with all this is that currently I'm chaining the responses like this:
#Override
public void execute(final Callback callback, String userSipId) {
mTeamCache.getAllTeams().subscribe(new DefaultSubscriber<List<SimpleTeam>>() {
#Override
public void onNext(List<SimpleTeam> simpleTeams) {
super.onNext(simpleTeams);
mTeams = simpleTeams;
getFeedFromRepository(callback);
}
});
}
public void getFeedFromRepository(final Callback callback) {
mFeedRepository.getFeedRx(0, 50).subscribe(new ServerSubscriber<List<ApiFeedResponse>>() {
#Override
protected void onServerSideError(Throwable errorResponse) {
callback.onFeedFetchFailed("failed");
}
#Override
protected void onSuccess(List<ApiFeedResponse> responseBody) {
//Do stuff with mTeams
List<BaseFeedItem> responseList = new ArrayList();
for (ApiFeedResponse apiFeedResponse : responseBody) {
responseList.add(FeedDataMapper.transform(apiFeedResponse));
}
callback.onFeedFetched(responseList);
}
});
}
As you can see, once that I get the Team collection from the Cache Interactor I call the method that gets the feed from the very same Subscriber. I don't like this. I want to be able to do something nicer, like using Observable.concat(getTeamsFromCache(), getFeedFromRepository()); chain a call to another rx.Observable inside a Subscriber is not something nice to do. I guess that my question is, how can I chain two rx.Observables that are using different Subscribers?
Update:
ServerSubscriber is a subscriber that I implemted to subscribe to Retrofit services. It simply checks the error codes and some stuff. Here is:
https://gist.github.com/4gus71n/65dc94de4ca01fb221a079b68c0570b5
Default subscriber is an empty default subscriber. Here is:
https://gist.github.com/4gus71n/df501928fc5d24c2c6ed7740a6520330
TeamCache#getAllTeams() returns rx.Observable>
FeedRepository#getFeed(int page, int offset) returns rx.Observable>
Update 2:
This is how the Interactor to get the User's feed looks like now:
#Override
public void execute(final Callback callback, int offset, int pageSize) {
User user = mGetLoggedUser.get();
String userSipid = mUserSipid.get();
mFeedRepository.getFeed(offset, pageSize) //Get items from the server-side
.onErrorResumeNext(mFeedCache.getFeed(userSipid)) //If something goes wrong take it from cache
.mergeWith(mPendingPostCache.getAllPendingPostsAsFeedItems(user)) //Merge the response with the pending posts
.subscribe(new DefaultSubscriber<List<BaseFeedItem>>() {
#Override
public void onNext(List<BaseFeedItem> baseFeedItems) {
callback.onFeedFetched(baseFeedItems);
}
#Override
public void onError(Throwable e) {
if (e instanceof ServerSideException) {
//Handle the http error
} else if (e instanceof DBException) {
//Handle the database cache error
} else {
//Handle generic error
}
}
});
}
I think you're missing the point of RxJava and reactive approach, you should not have different subscribers with OO hierarchy, and callbacks.
You should construct separated Observables that should emit the specific data it's handle, without the Subscriber, then you can chain you're Observable as needed, and at the end, you have the subscriber that react to the final result expected from the chained Observable stream.
something like this (using lambdas to have more thin code):
TeamCache mTeamCache = new TeamCache();
FeedRepository mFeedRepository = new FeedRepository();
Observable.zip(teamsObservable, feedObservable, Pair::new)
.subscribe(resultPair -> {
//Do stuff with mTeams
List<BaseFeedItem> responseList = new ArrayList();
for (ApiFeedResponse apiFeedResponse : resultPair.second) {
responseList.add(FeedDataMapper.transform(apiFeedResponse));
}
}, throwable -> {
//handle errors
}
);
I've use zip and not concat as it's seems you have 2 independent calls here that you want to wait for both to finish ('zip' them together) and then act upon, but ofcourse, as you have separated Observables stream, you can chain them together differently according to your needs.
as for your ServerSubscriber with all the response validation logic, it should be rxify too, so you can compose it along your server Observable stream.
something like this (some logic emitted to simplify, and as I'm not familiar with it...)
Observable<List<SimpleTeam>> teamsObservable = mTeamCache.getAllTeams();
Observable<List<ApiFeedResponse>> feedObservable = mFeedRepository.getFeed(0, 50)
.flatMap(apiFeedsResponse -> {
if (apiFeedsResponse.code() != 200) {
if (apiFeedsResponse.code() == 304) {
List<ApiFeedResponse> body = apiFeedsResponse.body();
return Observable.just(body);
//onNotModified(o.body());
} else {
return Observable.error(new ServerSideErrorException(apiFeedsResponse));
}
} else {
//onServerSideResponse(o.body());
return Observable.just(apiFeedsResponse.body());
}
});

How to modify subject output type?

I have the following singleton:
public abstract class Store<Input, Output> {
private BehaviorSubject<Input> subject = BehaviorSubject.create();
private Observable<Output> observable; //also a singleton
public final Subscription register(Subscriber<Output> subscriber) {
if (observable == null) {
observable = subject.compose(getTransformer()); //is this hot?
}
return observable.subscribe(subscriber);
}
public final void unregister(Subscription subscription) { //unregister }
//applies operators to the original stream to transform Input -> Output
protected abstract Observable.Transformer<Input, Output> getTransformer();
public final void onNext(Input event) { subject.onNext(event);}
}
Problem: When I rotate the device or minimise the app the observable dies (aborts network execution). Is subject.compose() returning a cold observable, if so, why?.
I tried using publish.autoConnect() / share() to make it hot, now it doesn't die upon rotation... but the BehaviourSubject breaks. When I rotate the device and I don't get the first value upon subscription.
How can I transform the output value of a subject and still have it behave as a subject? SAMPLE PROJECT
LOG:
USER: hits button to fetch from network
D: Retrieving from
network... D: Network request executed successfully
D: Caching to memory
USER: hits button to fetch from network again
D: Retrieving from network...
USER: pressed home button, app backgrounded
D:.unsubscribe()
Turns out, I've been using Subjects wrong all along. Here's the correct version of what I wanted to implement above:
public abstract class RxStore<Input, Output> {
private BehaviorRelay<Output> relay;
public final Subscription register(Subscriber<Output> subscriber) {
if (relay == null) {
relay = BehaviorRelay.create(defaultValue());
}
return relay.subscribe(subscriber);
}
public final void unregister(Subscription subscription) {
if (subscription != null && !subscription.isUnsubscribed()) {
subscription.unsubscribe();
}
}
public void execute(Input event) {
buildObservable(event).subscribe(relay);
}
/**
* #return the first or default value emitted to subscribers
*/
protected Output defaultValue() {
return null;
}
/**
* #return an buildObservable responsible of handling its own errors.
*/
protected abstract Observable<Output> buildObservable(Input event);
}
Edit:
I've found this approach very useful. I'm using this approach in prod and I've written an article about this:
https://medium.com/#FerRaviola/rxandroid-an-event-bus-on-steroids-9699e93eca98#.tqbxleo4h

Long running RxJava Subscriptions with refreshable data

I'm looking to set up a long running data subscription to a particular data object in Android/RxJava. Specifically a combination of a Retrofit REST call paired with cached data. I've done this pretty simply just wrapping an API call with data, were the API call is Retrofit returning an Observable:
class OpenWeather {
...
Observable<CurrentWeather> OpenWeather.getLocalWeather()
...
}
The simple implementation would be:
public static Observable<CurrentWeather> getWeatherOnce() {
if (currentWeather != null)
return Observable.just(currentWeather);
return OpenWeather.getLocalWeather()
.map(weather -> currentWeather = weather);
}
private static CurrentWeather currentWeather;
The problem is that there is no way to notify when the "current weather" has been updated. The simplest way to add refreshable data with long running updates between subscriptions would be to use a BehaviorSubject like such:
public class DataModel {
public enum DataState {
ANY, // whatever is available, don't require absolute newest
LATEST, // needs to be the latest and anything new
}
private final static BehaviorSubject<CurrentWeather> currentWeatherSubject = BehaviorSubject.create();
public static Observable<CurrentWeather> getCurrentWeather(DataState state) {
synchronized (currentWeatherSubject) {
if (state == DataState.LATEST || currentWeatherSubject.getValue() == null) {
OpenWeather.getLocalWeather()
.subscribeOn(Schedulers.io())
.toSingle()
.subscribe(new SingleSubscriber<CurrentWeather>() {
#Override
public void onSuccess(CurrentWeather currentWeather) {
currentWeatherSubject.onNext(currentWeather);
}
#Override
public void onError(Throwable error) {
// ?? currentWeatherSubject.onError(error);
}
});
}
}
return currentWeatherSubject.asObservable();
}
}
Using the BehaviorSubject, when getting the current weather, get either the last cached entry and any updates as they occur. Thoughts?
So I'm sure I'm doing something wrong here as there seems there should be an easier way or more elegant way.

RxAnadroid concatenating streams

I'm making a simple weather app to learn RxAndroid and I'm faced with the following issue.
I first load cities I'm interested in and then ask for the weather of each one of them.
getCitiesUseCase returns an Observable<List<City>> that I load from the data base. I send that list of cities to my view to display them and then ask for the weather individually (flatmap) inside the subscriber.
Subscription subscription = getCitiesUseCase.execute().flatMap(new Func1<List<City>, Observable<City>>() {
#Override
public Observable<City> call(List<City> cities) {
citiesView.addCities(cities);
return Observable.from(cities);
}
}).subscribe(new Subscriber<City>() {
#Override
public void onCompleted() {
subscriptions.remove(this);
this.unsubscribe();
}
#Override
public void onError(Throwable e) {
Log.e(this.getClass().getSimpleName(), e.toString());
}
#Override
public void onNext(City city) {
getCityWeatherUseCase.setLatLon(city.getLat().toString(), city.getLon().toString(), city.getId());
getCityWeather(city);
}
});
subscriptions.add(subscription);
Now the getCityWeather() method looks like this:
private void getCityWeather(final City city) {
subscriptions.add(getCityWeatherUseCase.execute().subscribe(new Subscriber<CityWeather>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
Log.e("error", e.toString());
}
#Override
public void onNext(CityWeather cityWeather) {
city.setCityWeather(cityWeather);
citiesView.updateCity(city);
}
}));
}
Everything works fine and as expected, but the fact that I'm subscribing to an observer inside a subcriber doesnt feel right. I know rxJava lets you play around with subscribers to prevent this kind of things but I really dont know how to improve my code further. Keep in mind that I need a city in order to ask for its weather.
Merry chrismas!
One approach could be the following. (I'm using retrolambda - so wherever you see ->, just replace with a new anonymous inner class).
Note that I'm using flatMap to spin up the weather data requests, rather than Observable.concat like your question suggests. The reason for this is that your scheduler (e.g. io()) will handle these in parallel and send the results through when they are available. However, with Observable.concat, these requests would be serialized so they'd be forced to happen one at a time - nullifying the benefits of a thread pool like io().
private class City {
public String name;
public City(String name) {
this.name = name;
}
public void setWeather(Weather weather) { /*...*/ }
}
private class Weather {
public String status;
public Weather(String status) {
this.status = status;
}
}
private Observable<Weather> getWeather(City city) {
// call your weather API here..
return Observable.just(new Weather("Sunny"));
}
#Test
public void test() {
Observable<List<City>> citiesObs = Observable.create(new Observable.OnSubscribe<List<City>>() {
#Override
public void call(Subscriber<? super List<City>> subscriber) {
// do work
final List<City> cities = new ArrayList<>();
cities.add(new City("Paris"));
cities.add(new City("Tokyo"));
cities.add(new City("Oslo"));
// send results
if (!subscriber.isUnsubscribed()) {
subscriber.onNext(cities);
subscriber.onCompleted();
}
}
});
Observable<City> obs = citiesObs
// inject a side effect
.doOnNext(list -> {
// pass `list` to your view here
})
// turn Observable<Iterable<T>> into Observable<T>
.flatMapIterable(list -> list)
// Map a city to an observable that fetches Weather data
// Your scheduler can take care of these at once.
.flatMap(city -> {
return getWeather(city)
// another side effect
.doOnNext(weather -> {
city.setWeather(weather);
})
// map baack to city, just for the heck of it
.map($ -> city);
});
TestSubscriber sub = TestSubscriber.create();
obs.subscribe(sub);
sub.awaitTerminalEvent();
sub.assertValueCount(3);
}
Also note that in order to take advantage of io(), you'd need to add a call to subscribeOn(Schedulers.io()) to tell the observable to begin doing work on the io thread pool. When you want to pass control to another thread, for example your view, you could insert a observeOn(AndroidSchedulers.mainThread()) before your side-effect (or mapping). If you want to bounce control back to the background thread(s) for your weather calls, you could then add another call to observeOn(Schedulers.io()) right before you flatMap to getWeather(City).

Using Observable in custom Application subclass

Is it acceptable to create Rx Observables in custom Application subclass. Reason for doing is, I can create BehaviorSubject inside the Application and will ask for changes every 10 minutes from Server, every Activity or Fragment which subscribes to this Observable will get only last state of changes.
Question is whether this architecture could be considered safe in terms of application lifecycle handling and easy to use?
class CustomApplication extends Application {
...
BehaviorSubject<Friends> mFriends = new BehaviorSubject<Friends>;
public void createObservables() {
Observable.create(new Observable.OnSubscribe<Friends>() {
public void call(Subscriber<?> s) {
while(true) {
mFriends.onNext("randomFriendN");
sleep(10sec);
}
}
})
.subscribeOn(Schedulers.newThread())
.subscribe(new Observer<List<NewsCategory>>() {
public void onNext(Friends f) { //empty }
});
}
public BehaviorSubject<Friends> getFriends() {
return mFriends;
}
}
UPDATE:
Everytime when new activity created and it wants to get data it can get it ApplicationContext's BehaviorSubject then subscribe to it, and Subject will emit last emitted value;
Why I want to do like this? E.g. Lets say you have news items, you fetched news feed and you want to start background task which fetches news item full content, in that case I can start fetching data while you are scrolling news list, and when you click detailed activity, we can show it from already fetched, or just download it.
I think this is perfectly safe as long as createObservables() is only called once during application initialization. A few suggested changes...
I wouldn't expose the BehaviorSubject part of mFriends in the returned value from getFriends(). That way callers of getFriends() will not be tempted to call onNext(). Change it to:
public Observable<Friends> getFriends() {
return mFriends;
}
If you want to be super safe use .asObservable() and callers will not even be able to cast the return value back to a BehaviorSubject.
public Observable<Friends> getFriends() {
return mFriends.asObservable();
}
I would also update your createObservable() method to call the BehaviorSubject onNext() from the subscribe callback. Here is your code slightly modified to use NewsItems.
BehaviorSubject<List<NewsItem>> mNewsItemSubject = BehaviorSubject.create();
void createObservables() {
Observable
.timer(10, 10, TimeUnit.SECONDS, Schedulers.newThread())
.flatMap(new Func1<Long, Observable<List<NewsItem>>>() {
#Override
public Observable<List<NewsItem>> call(Long aLong) {
// Normally you would create a network API that returns Observable<NewsItem>.
// For now just pretend this returned Observable makes an Observable
// network request.
return Observable.just(
Arrays.asList(
new NewsItem("fakeNewsItem"),
new NewsItem("fakeNewsItem1")
)
);
}
})
.subscribe(new Action1<List<NewsItem>>() {
#Override
public void call(List<NewsItem> newsItems) {
mNewsItemSubject.onNext(newsItems);
}
});
}
public Observable<List<NewsItem>> observeNewsItems() {
return mNewsItemSubject;
}
Your Android Activities can then call ((CustomApplication)getApplication()).observeNewsItems() to get the latest news items and any updates while the Activity is visible.
final Observable<List<NewsItem>> newsItemsObservable =
((CustomApplication) getApplication()).observeNewsItems();
newsItemsObservable
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<NewsItem>>() {
#Override
public void onCompleted() {
// All done.
}
#Override
public void onError(Throwable e) {
// Notify user of error (maybe)
}
#Override
public void onNext(List<NewsItem> newsItems) {
// Update the UI with newsItems.
}
});

Categories

Resources