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?
I want to be able to have Espresso monitor Picasso as an IdlingResource so that I can run ViewMatchers once the image has been successfully loaded.
From navigating through the Picasso source code, I don't see why this isn't working. Here's what I tried:
Picasso picasso = new Picasso.Builder(context).build();
Field dispatcherField = Picasso.class.getDeclaredField("dispatcher");
dispatcherField.setAccessible(true);
try {
Dispatcher dispatcher = (Dispatcher) dispatcherField.get(picasso);
Espresso.registerLooperAsIdlingResource(dispatcher.dispatcherThread.getLooper());
} catch (NoSuchFieldException e) {
throw new PicassoHasBeenRefactoredException();
} catch (Exception e) {
e.printStackTrace();
}
onView(withId(R.id.image_view)).check(matches(withImage(R.drawable.drawable)));
(yes, I know, reflecting is icky, but I couldn't find another way of getting a handle on the Looper)
But it results in this error when trying to get the Bitmap from the ImageView:
java.lang.NullPointerException: Attempt to invoke virtual method 'android.graphics.Bitmap android.graphics.drawable.BitmapDrawable.getBitmap()' on a null object reference
To check that the test is running as expected once an image has been loaded, I tried introducing a Thread.sleep(1000) in lieu of the IdlingResource check and it passed.
Is it safe to assume that the IdlingResource hasn't been set up correctly, and, more importantly, what would be the correct way of waiting for Picasso to finish loading before checking views with Espresso?
I'm using an IdlingResource that checks if there are actions left.
Note that the IdlingResource must live in the same package as Picasso to gain access to a package-protected variable
package com.squareup.picasso;
public class PicassoIdlingResource implements IdlingResource, ActivityLifecycleCallback {
protected ResourceCallback callback;
WeakReference<Picasso> picassoWeakReference;
#Override
public String getName() {
return "PicassoIdlingResource";
}
#Override
public boolean isIdleNow() {
if (isIdle()) {
notifyDone();
return true;
} else {
return false;
}
}
public boolean isIdle() {
return picassoWeakReference == null
|| picassoWeakReference.get() == null
|| picassoWeakReference.get().targetToAction.isEmpty();
}
#Override
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
this.callback = resourceCallback;
}
void notifyDone() {
if (callback != null) {
callback.onTransitionToIdle();
}
}
#Override
public void onActivityLifecycleChanged(Activity activity, Stage stage) {
switch (stage) {
case CREATED:
picassoWeakReference = new WeakReference<>(Picasso.with(activity));
break;
case STOPPED:
// Clean up reference
picassoWeakReference = null;
break;
default: // NOP
}
}
}
I don't think using WeakReference is needed, but it doesn't hurt either.
Also, I've identified one case where it doesn't wait until Picasso finishes (when using .load(null)). So, use at your own risk and please come back if you improve it.
See gist for full details and usage (https://gist.github.com/Maragues/0c0db81a137c8d067396)
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.
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.
I've run into this error before, but thought it was some mistake by the strict mode system. However, it apparently was right as I sadly found out now. :(
My programm is made of one Activity and loads of Fragments. I have a NetworkWorker fragment, which starts URL requests like this:
public void startURLRequest(Fragment target, String url, String message)
{
if (asyncTask != null) asyncTask.cancel(true);
asyncTask = new FragmentHttpHelper(url, message, target);
asyncTask.doInBackground();
return;
}
FragmentHttpHelper is a custom inner class derived from AsyncTask:
private class FragmentHttpHelper extends AsyncTask<Void, Void, String>
{
//...
#Override
protected String doInBackground(Void... params)
{
if (CheckInternet())
{
try
{
URL myURL = new URL(url);
httpClient = new DefaultHttpClient();
if (this.message == null)
{
httpRequest = new HttpGet(myURL.toExternalForm());
}
else
{
httpRequest = new HttpPost(myURL.toExternalForm());
HttpEntity myEntity = new StringEntity(message, "UTF-8");
((HttpPost) httpRequest).setEntity(myEntity);
}
// and so on...
}
//catches
finally
{
// auf jeden Fall Verbindung beenden
if (httpRequest != null) httpRequest.abort();
// if (httpClient != null) httpClient.close();
}
}
else
{
showDialog(getString(R.string.net_notify_no_network), target);
}
//...
}
/**
* gets called after AsyncTask has finished
*/
protected void onPostExecute(String result)
{
if (target == null)
{
((NetworkWorkerListener) getActivity()).onDownloadHasFinished((!result.contentEquals(ERROR)), result);
}
else
{
((NetworkWorkerListener) target).onDownloadHasFinished((!result.contentEquals(ERROR)), result);
}
}
}
NetworkWorkerListener is just an interface for a callback on the Fragment which started the URL request. This class has always worked fine when I used it in my 2.2 app. I would derive it in my Activities then.
Now, if a menu item is selected, another worker Fragment starts the URL request via the above method and opens a loading dialog:
FragmentManager fragmentManager = getFragmentManager();
NetworkWorker network = (NetworkWorker) fragmentManager.findFragmentByTag(TabletMain.NETWORK);
if (network == null) return WorkerFeedback.NO_NETWORK_WORKER;
myDialog = LoadingDialog.createInstance(getString(R.string.notify_download), this);
myDialog.show(fragmentManager, TabletMain.ONETIME);
network.startURLRequest(this, someurl, null);
At least that's what supposed to happen.
Instead, when I click the menu item, my app freezes and no loading dialog is shown until. Next happening is the reaction to the end of the download (or, in my case an error message, as I am sending nonsense strings). Meaning onPostExecute() was reached.
I feel really stuck now - is it not possible to use AsyncTask with Fragments? Or did I do something wrong?
Thanks for your help,
jellyfish
Don't call doInBackground directly, call execute instead (on the async task)