How to update all live data the same time? - android

I want to update my live data(s) in my ViewModel when the app detects the user's filters changed on a drawer layout close listener. I have created an update all live data method in my ViewModel, but it doesn't seem to work.
Here's my ViewModel:
public class ReleasesViewModel extends ViewModel {
private HashMap<String, MutableLiveData<List<_Release>>> upcomingReleasesListMap = new HashMap<>();
private ReleasesRepository releasesRepository;
private ArrayList<Integer> platforms;
private ArrayList<Integer> platformsCopy;
private String region;
public ReleasesViewModel() {
upcomingReleasesListMap = new HashMap<>();
// Shared to all fragments : User settings region & platforms
region = SharedPrefManager.read(SharedPrefManager.KEY_PREF_REGION, "North America");
Set<String> defaultPlatformsSet = new HashSet<>();
platforms = SharedPrefManager.read(SharedPrefManager.PLATFORM_IDS);
if (platformsCopy == null) {
// Set only once [past copy of platforms]
platformsCopy = SharedPrefManager.read(SharedPrefManager.PLATFORM_IDS);
}
}
public MutableLiveData<List<_Release>> getUpcomingReleases(String filter) {
// ReleasesRepository takes a different monthly filter
if (upcomingReleasesListMap.get(filter) == null) {
// we don't have a mapping for this filter so create one in the map
MutableLiveData<List<_Release>> releases = new MutableLiveData<>();
upcomingReleasesListMap.put(filter, releases);
// also call this method to update the LiveData
loadReleases(filter);
} else if (upcomingReleasesListMap.containsKey(filter)) {
// Double check if it isn't null, just in case
if (upcomingReleasesListMap.get(filter) == null || isPlatformsUpdated()) {
// if null; try again to update the live data or if platforms filter changed
loadReleases(filter);
} // else just don't do anything, the list is already in the Map
}
return upcomingReleasesListMap.get(filter);
}
private void loadReleases(final String filter) {
releasesRepository = new ReleasesRepository(region, filter, platforms);
releasesRepository.addListener(new FirebaseDatabaseRepository.FirebaseDatabaseRepositoryCallback<_Release>() {
#Override
public void onSuccess(List<_Release> result) {
// sort by release date
if (platforms.size() > 1) {
// Will only sort for multiple platforms filter
Collections.sort(result);
}
// just use the previous created LiveData, this time with the data we got
MutableLiveData<List<_Release>> releases = upcomingReleasesListMap.get(filter);
releases.setValue(result);
}
#Override
public void onError(Exception e) {
// Log.e(TAG, e.getMessage());
MutableLiveData<List<_Release>> releases = upcomingReleasesListMap.get(filter);
releases.setValue(null);
}
});
}
// Detects when user added/removed platform to update lists based on platforms
private boolean isPlatformsUpdated() {
Collections.sort(platforms);
Collections.sort(platformsCopy);
if (platforms.equals(platformsCopy)) {
// nothing new added since past update
return false;
}
// something new added or removed, change
platformsCopy = SharedPrefManager.read(SharedPrefManager.PLATFORM_IDS);
return true;
}
public void updateAllReleasesList() {
// update all releases live data lists
for (String filter : upcomingReleasesListMap.keySet()) {
loadReleases(filter);
}
}
}
The updateAllReleasesList is the method I created to update all my livedata lists, but in calling this method it will call the loadReleases method again and inside this method, it will skip the entire listener addListener code for some reason.
In my fragments where I listen to the data changes I have this following simple observer:
mReleasesViewModel = ViewModelProviders.of(getActivity()).get(ReleasesViewModel.class);
mReleasesViewModel.getUpcomingReleases(filter).observe(this, new Observer<List<_Release>>() {
#Override
public void onChanged(#Nullable List<_Release> releases) {
// whenever the list is changed
if (releases != null) {
mUpcomingGamesAdapter.setData(releases);
mUpcomingGamesAdapter.notifyDataSetChanged();
Toast.makeText(getContext(), "Updated", Toast.LENGTH_SHORT).show();
}
mDatabaseLoading.setVisibility(View.GONE);
}
});
And when I call my update all method in my ViewModel on drawer close, the code inside the observer in my fragment gets called (the list returned is empty), but I want it to call getUpcomingReleases to update everything.. Any ideas on how to update all my current livedatas at the same time and reflect it on the UI?

Related

Realm add item to RealmList that is added to RealmObject

I need to solve putting data to a realm database like this:
I have an object called obtained_code;
I have a realmList of Obtained codes in an object called Offer;
I download obtained codes separately, and by their offer id assign them to the lists of each object. The problem is that I can't add them because when I check the size, it's always 0.
Here is the code:
ObtainedCodes codes = response.body();
for (ObtainedCode c : codes.getObtainedCodes()) {
Offer offer = RealmController.with(SplashActivity.this).getOffer(c.getOffer_id());
if (offer != null) {
Log.d("Size", "Offer not null");
realm1.beginTransaction();
RealmList<ObtainedCode> list = offer.getObtained_codes();
if (!list) { // if the 'list' is managed, all items in it is also managed
RealmList<ObtainedCode> managedImageList = new RealmList<>();
for (ObtainedCode item : list) {
if (item) {
managedImageList.add(item);
} else {
managedImageList.add(realm1.copyToRealm(item));
}
}
list = managedImageList;
}
offer.setObtained_codes(obtainedCodes);
Log.d("Size", String.valueOf(offer.getObtained_codes().size()));
realm1.copyToRealmOrUpdate(offer);
realm1.commitTransaction();
}
offer = RealmController.with(SplashActivity.this).getOffer(c.getOffer_id());
Log.d("Size", String.valueOf(offer.getObtained_codes().size()));
}
1.) the Ravi Tamada tutorial on InfoHive is a terrible mess, please refer to my remake of that example instead.
If you managed to start using 0.82.1 because Ravi Tamada claimed that a 4 years old version is "stable", well I know that it's not. Use 1.2.0 instead (or the latest version which is 3.4.1)
And if you see a RealmController.with(), run, because it ignores thread-confinement. The moment you try to access it from a background thread, it'll crash.
On background threads, you'd need to do
#Override
public void run() {
try(Realm realm = Realm.getDefaultInstance()) {
repository.whatever(realm); // pass Realm instance to database methods
} // auto-close
// end of thread
}
2.) you are executing writes on the UI thread, that is bad, from UI thread you should use realm.executeTransactionAsync(), but in your case you should actually execute the Retrofit call on a background thread using Ęxecutors.newSingleThreadedPool() and call it with call.execute() instead of call.enqueue().
3.) You should write to Realm on the background thread, and on the UI thread you should use RealmChangeListener to listen to writes.
4.) your code doesn't work because you're setting an unmanaged list to a managed RealmObject.
You should modify the existing RealmList inside the RealmObject, and add only managed objects to it.
Executor executor = Executors.newSingleThreadExecutor(); // field variable
// ...
void someMethod() {
executor.execute(new Runnable() {
#Override
public void run() {
Response<ObtainedCodes> response = retrofitService.getObtainedCodes().execute(); // run on current thread
ObtainedCodes codes = response.body();
if(codes == null) return;
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
for(ObtainedCode obtainedCode : codes.getObtainedCodes()) {
Offer offer = realmRepository.getOffer(realm, obtainedCode.getOfferId());
if(offer == null) {
offer = realm.createObject(Offer.class, obtainedCode.getOfferId());
// map properties to offer if possible
}
RealmList<ObtainedCode> offerCodes = offer.getObtainedCodes();
ObtainedCode managedObtainedCode = realm.where(ObtainedCode.class).equalTo("obtainedCodeId", obtainedCode.getId()).findFirst();
if(managedObtainedCode == null) {
managedObtainedCode = realm.createObject(ObtainedCode.class, obtainedCode.getId());
// map properties from obtained code to managed obtained code
}
if(!offerCodes.contains(managedObtainedCode)) {
offerCodes.add(managedObtainedCode);
}
}
}
});
}
}
});
}

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.

RxJava operator for cached item provider

I'm trying to implement a provider that looks for items in memory, disk, network, in this order. The main purpose of this is to avoid network calls if I have the right local cache. There is a catch, since my calls to the network use filters to get items, I could have 10 items from the local query, but still need to go to the network because those items come from a different network call, with different query parameters.
Right now I'm using a concat with a firstOrDefault, checking that the list isn't null or empty. I've implemented a way to check if I already called the server with the specific query and I use it to return null when reading from disk.
I now need to refine the provider, so that it:
emits local items
go online if needed
emits online items
(Right now it stops at the first good list of items).
I'm trying with takeWhile, using a method that returns true if data is null or empty or if I didn't already call the server for that query. The problem is that takeWhile doesn't emit the item if the check for that item is false, meaning I won't get the last good item (which is also the best one).
The best solution I can think of is an operator that emits items until a certain condition comes up and then unsubscribe itself. I can't find one.
EDIT: SOME CODE
Solution 1) using firstOrDefault: will not emit local items if !DiskService.wasDownloaded(), because DiskService return a null List<Item> with !DiskService.wasDownloaded()
public Observable<List<Item>> items() {
List<Observable> obs = new ArrayList<>();
Observable<List<Item>> memoryObs = Observable.defer(this::getMemoryItems);
Observable<List<Item>> diskObs = Observable.defer(this::getDiskItems);
Observable<List<Item>> networkObs = Observable.defer(this::getNetworkItems);
Observable<List<Item>> concat = Observable.concat(memoryObs, diskObs, networkObs;
return concat.firstOrDefault(new ArrayList<>(), this::canAccept);
}
private boolean canAccept(List<Item> data) {
return data != null && data.size() > 0;
}
//Method in DiskService
public boolean wasDownloaded(){
return true if the query was performed on the server, false otherwise.
}
Solution 2) Using takeWhile. The problem with takeWhile is the Observable will not emit the item that doesn't check its condition, meaning I won't get the best List. The hacky solution is to defer the false check to the next item, but this way a network request will be fired even when not necessary. With this solution I'm using a TrustedItemList that just contains the List and a boolean that tells the Observable if he can trust a non-empty list of items (always true for memory and network, true if wasDownloaded() for disk)
public Observable<List<Item>> items() {
List<Observable> obs = new ArrayList<>();
Observable<TrustedItemList> memoryObs = Observable.defer(this::getMemoryItems);
Observable<TrustedItemList> diskObs = Observable.defer(this::getDiskItems);
Observable<TrustedItemList> networkObs = Observable.defer(this::getNetworkItems);
Observable<TrustedItemList> concat = Observable.concat(memoryObs, diskObs, networkObs;
return concat.takeWhile(this::shouldContinueSearching)
.filter(trustedItemList -> trustedItemList.items != null && !trustedItemList.items.isEmpty())
.map(trustedItemList -> trustedItemList.items);
}
private boolean shouldContinueSearching(TrustedPoiList data) {
return data == null || data.items == null || data.items.isEmpty() || !data.canTrustIfNotEmpty;
}
I ended up using a custom Observable.Operator, shamelessly copied from OperatorTakeWhile, with the sole change of calling subscriber.onNext(t) just before subscriber.onCompleted() in the onNext method. This way the last item, the one that returns false on the boolean check, is emitted.
public final class OperatorTakeWhileWithLast<T> implements Observable.Operator<T, T> {
private final Func2<? super T, ? super Integer, Boolean> predicate;
public OperatorTakeWhileWithLast(final Func1<? super T, Boolean> underlying) {
this((input, index) -> {
return underlying.call(input);
});
}
public OperatorTakeWhileWithLast(Func2<? super T, ? super Integer, Boolean> predicate) {
this.predicate = predicate;
}
#Override
public Subscriber<? super T> call(final Subscriber<? super T> subscriber) {
Subscriber<T> s = new Subscriber<T>(subscriber, false) {
private int counter = 0;
private boolean done = false;
#Override
public void onNext(T t) {
boolean isSelected;
try {
isSelected = predicate.call(t, counter++);
} catch (Throwable e) {
done = true;
Exceptions.throwIfFatal(e);
subscriber.onError(OnErrorThrowable.addValueAsLastCause(e, t));
unsubscribe();
return;
}
if (isSelected) {
subscriber.onNext(t);
} else {
done = true;
subscriber.onNext(t); //Just added this line
subscriber.onCompleted();
unsubscribe();
}
}
#Override
public void onCompleted() {
if (!done) {
subscriber.onCompleted();
}
}
#Override
public void onError(Throwable e) {
if (!done) {
subscriber.onError(e);
}
}
};
subscriber.add(s);
return s;
}
}
My items() method (solution 2) now ends with:
return concat.lift(new OperatorTakeWhileWithLast<TrustedItemList>(this::shouldContinueSearching))
.filter(trustedItemList -> trustedItemList.items != null && !trustedItemList.items.isEmpty())
.map(trustedItemList -> trustedItemList.items);

RxJava observables not emitting events

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.

clearCachedResult() not working as expected

I have this query located in my ParseQueryBuilder object:
public ParseQuery<Event> eventsTypes() {
ParseQuery<Event> query = Event.getQuery();
query.setCachePolicy(ParseQuery.CachePolicy.CACHE_ELSE_NETWORK);
query.setMaxCacheAge(TimeUnit.DAYS.toMillis(1));
query.whereEqualTo(Event.owner, parse.getParseUser());
query.orderByDescending(Event.timesUsed);
return query;
}
I use it to populate a ParseQueryAdapter
and at some point I would like to add an Event and immediately show it:
#OnClick(R.id.add)
public void add(Button button) {
final Event new_type = new Event();
new_type.setOwner(parse.getParseUser());
new_type.setName("atest");
new_type.saveEventually(new SaveCallback() {
#Override
public void done(ParseException e) {
if (e == null) {
// on successfull save, clear cache
parseQueryBuilder.eventsTypes().clearCachedResult();
// and show newly added object
mAdapter.loadObjects();
Toast.makeText(getActivity(), new_type.getName(), Toast.LENGTH_SHORT).show();
}
}
});
}
I expected clearing the cache would result in a new network query, revealing the newly added item but no matter what I try, it seems it will only show the initially cached result.
Even if I try to restart my app, it shows the result from the first cache.

Categories

Resources