4

I'm using RoboSpice and want to have the following behavior in my app:

  • User starts an activity that needs data from server
  • spiceManager checks if data is cached, returning it if so.
  • regardless of wether cached data was returned, a request to the server is made
  • when fresh data arrives from server, I update UI with it (if activity is still active)

It should be something like facebook app: when you open it, you instantly see an outdated timeline, and receive an update eventually.

At first I thought spiceManager.getFromCacheAndLoadFromNetworkIfExpired() was a good way to achieve this, but if data is cached and valid, it justs returns cache without making a network request right after it. I've tried it both with DurationInMillis.ALWAYS_EXPIRED and DurationInMillis.ALWAYS_RETURNED.

Should I use getFromCache() to retrieve cached data and then, from within onRequestSuccess(), call spiceManager.execute() with always_expired as parameter? Or there is a better/simpler way to do this?

Thanks in advance for any help!

[edit] these links may add to the discussion: https://groups.google.com/forum/#!topic/robospice/n5ffupPIpkE/discussion https://groups.google.com/forum/#!topic/robospice/LtoqIXk5JpA

Lucas Jota
  • 1,863
  • 4
  • 24
  • 43

3 Answers3

3

I have had same task and used approach from here:

  1. Load view data from cache if exists
  2. In the meantime, fire request to load new version from network. If the data has changed, update the view.

Here is sample code

SpecialOffersRequest request = new SpecialOffersRequest();
spiceManager.getFromCache(SpecialOffer.List.class, request.getCacheKey(), ALWAYS_RETURNED, new SpecialOffersRequestListener());
spiceManager.execute(request, request.getCacheKey(), request.getCacheExpiryDuration(), new SpecialOffersRequestListener());

As you see, one SpecialOffersRequestListener is used for both get-from-cache and get-from-network requests. However, I had to make a little trick (see dataInCache usage below) to handle offline case and not to worry user about "No connection" if there is somethin in cache to display:

private final class SpecialOffersRequestListener implements RequestListener<SpecialOffer.List> {
    @Override
    public void onRequestFailure(SpiceException spiceException) {
        if (spiceException instanceof NoNetworkException && dataInCache) {
            // Ignore network problems if there is some data in the cache.
            return;
        }

        ActionHelper.showError(getActivity(), "Failed to load special offers.", spiceException);
    }

    @Override
    public void onRequestSuccess(SpecialOffer.List result) {
        dataInCache = true;
        ...
    }
}
aka_sh
  • 549
  • 1
  • 7
  • 18
2

Aka_sh's approach has one important drawback: when your app is restarted (either by a user or Android) your dataInCache flag will get resetted and will return false though there may be data cached in a permanent storage (like a file or a DB).

Much better solution is to use SpiceManager's isDataInCache and getDateOfDataInCache introduced in 1.4.6-SNAPSHOT. These methods check if the data is available in the cache with a given expiration period and return the date when data were placed in cache.

One thing to remember: both methods may be processed asynchronously and they return Future<Boolean>/Future<Data>, so if you want to just wait for a result (it should not take long to get it), use Future.get(), e.g.:

spiceManager.isDataInCache(MyCachedObj.class, cacheKey, DurationInMillis.ALWAYS_RETURNED).get();
javaxian
  • 1,815
  • 1
  • 21
  • 26
0

I used the following methods to solve this problem. In addition, the code below will solve the issue when your activity A is loading and your user decides to navigate to activity B in the meantime while robospice is loading in the background. When the user navigates back to activity A, your information will be ready for consumption (Issue 2).

STEP 1: Add the following code to add a PendingRequestListener in the onStart method. This will allow you to solve issue 2 as the PendingRequestListener will listen to all pendingRequest in robospice and deal with them appropriately.

@Override
public void onStart() {
    super.onStart();
    FragmentPostActivity.spiceManagerPlaces.addListenerIfPending(Post.class, "POSTS", pendingRequestListener);
}

STEP 2: Create your PendingRequestListener.

PendingRequestListener pendingRequestListener = new PendingRequestListener() {
    @Override 
    public void onRequestNotFound() {
    //This is when no request is found at all - this method will be triggered. Notice that I have
    //placed this method within the onStart method which means that when your app onStarts - when you 
    //click on the app icon on your phone, this method will actually be triggered so therefore it will first
    //get the data from cache by using the cacheKey "POSTS", then it will trigger a separate spiceRequest for network
    //operations to fetch the data from the server. Notice that the second spiceRequest does not have a cacheKey.
    //The reason is that we are not going to return back the results of the network operations - we will return
    //the results of our cache that we will manually create at a later stage
        PostSpiceRequest postSpiceRequest = new PostSpiceRequest(getActivity(), postid);
        FragmentPostActivity.spiceManagerPlaces.getFromCache(EmbedPost.class, "POSTS", DurationInMillis.ONE_WEEK, new PostListener());
        FragmentPostActivity.spiceManagerPlaces.execute(PostSpiceRequest,new PostListener());
    }

    @Override
    public void onRequestFailure(SpiceException spiceException) {
    //This is if your request failed when you navigate back to Activity A from Activity B
        Log.e("PendingRequestRS", "request failed for pending requests");

    }

    @Override
    public void onRequestSuccess(Object o) {
        //This is if your request succeed when you navigate back to Activity A from Activity B
        Log.e("PendingRequestRS", "pending request successful");
        FragmentPostActivity.spiceManagerPlaces.getFromCache(Post.class, "POSTS", DurationInMillis.ONE_WEEK, new PostListener());
    }
};

STEP 3: Add a manual method to cache your data so that it can be retrieved when your app starts up again. The method below counts up to 20 posts and then puts them into the cache. If it is less than 20 posts, it will cache up to that amount.

@Override
public void onPause() {
    super.onPause();
    //If you are using a spiceManager from your activity class, you will needs to put this code into onPause and not in onStop. 
    //Inside your activity, you would have stopped the spicemanager in onStop, which means that your cache would not be stored
    //at all.
    //If you are using a spiceManager in your fragment class, you are free to put this code within your onStop method but make
    //sure that it is place in front of the spiceManager.onStop() so that the method will execute before it spicemanager is stopped

    LinkedList<Post> cachedPosts = new LinkedList<>();
    if (posts.size() > 20) {
        for (int i = 0; i <= 20; i++) {
            cachedPosts.add(posts.get(i));
        }
    } else {
        for (int i = 0; i < posts.size(); i++) {
            cachedPosts.add(posts.get(i));
        }
    }
    PostActivity.spiceManager.removeDataFromCache(Post.class, "POSTS");
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                FragmentPostActivity.spiceManagerPlaces.putDataInCache("POSTS", cachedPosts);
            } catch (CacheSavingException e) {
                e.printStackTrace();
            } catch (CacheCreationException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

Hopefully I have shed some light on this topic of using Robospice as there is very little documentation as to how this is done.

Simon
  • 19,658
  • 27
  • 149
  • 217