Android: Robospice with OkHttp
What is Robospice?
Taken from the projects Github
RoboSpice is a modular android library that makes writing asynchronous long running tasks easy. It is specialized in network requests, supports caching and offers REST requests out-of-the box using extension modules.
Essentially is makes it easier to persist ongoing network connection overs Activity and fragment lifecycle events i.e. config changes like rotation. For apps that dont want to restart the request each time this can be a really nice architecture approach and is putting into practise the concepts outlined in the getting-older but great talk Google I/O 2010 - Android REST client applications
Note: Robospice is great and I have used in a lot of apps I have built. There seems to be quite a trend of moving towards RxJava approaches to network comms which seems to make it easier to work with async opps and relativly simple to allow re-connection to existing calls etc. This is something I am playing with at the moment and will probably write a post about in time.
What is OkHttp
OkHttp
is another lib from the fantastic dev team at Square. It offers a stable replacement for the UrlConnection
and now pretty much deprecated HttpClient
. It allows you to stop worrying about platform version specific issues with both the the Android supplied http classes and have the same code running on all targeted platform versions. Equally cool is that it conforms to the HttpClient
and UrlConnection
interfaces so its pretty easy to drop in if you are putting into an existing app.
How to use
I would advise reading the Robospice docs / wiki but below are a few notes to get you up and running quickly
Add to gradle build file
Create your own SpiceService subclass
In the Manifest
<service android:name=".remote.spice.MySpiceService" android:exported="false" />
Class
Create a base Activity / Fragment
Making a non-cached call
You can just leave the empty CacheManager
and as long as you dont pass in a call id when calling execute
(i.e. use the two arg constructor instead) the cache will be bypassed.
Its important to note that your app will not receive callbacks after SpiceManager.shouldStop()
is called so if your locking the SpiceManager
lifecycle calls to onStart
& onStop
your code should be able to handle a missed response (due to activity / fragment being in the background OR recreated)
In most cases you will want to be using cached calls / pick up the response to the last call made regardless of lifecycle events.
You may want to use caching in your http layer based upon http headers as opposed to in RoboSpice - which may be another reason to use non-caching execute() methods.
Making a Cached call
Good for reconnecting to pending requests / operations after lifecycle events i.e. activity restarted) and / or not hitting the network for saved data.
*A note on call types and ids
In my mind there are two general types of calls in regards to caching. “One-time” calls (like login / submitting something etc) where the response is specific to what args / data was sent and “updating” calls where you may be grabbing the newest set of data for something (say tweets).
One-time For the call ids in regard to caching I generally use generating
UUID
s for one-time calls (see code below) which would need to be in the savedStateBundle. Combining with an in-mem LRU cache of size 1 (see below) would be a sensible strategy here.
Updating For updating calls a static final cache id may make more sense (or the call URL). You may want to combine this with a persistent file cache.
Updating caching example
This can be pretty simple, basically by
- checking if the call is pending already (using a static final cache key)
- if not pending start request
- if is pending reconnect to request
The important code for the above would be something like
One-time caching example
For “one-time” calls this is made up of the below steps (explained below)
- Generate a unique
id
for the api call * - persist and load that
id
in your saves state bundle - pass the call id and cache duration check into the
SpiceManager.execute(...)
method - Check if need to reconnect with existing/previous call
- Make sure the
CacheManager
is setup correctly
Creating Your CacheManager
For each object / result that you want to cache you will need to set this up via the CacheManager
instance you create inside your SpiceService subclass.
See Design of RoboSpice and this Robospice wiki link for a good overview to this.
You create/use an ObjectPersister
subclass for each. The main subclasses of this are
InDatabaseObjectPersister
InFileObjectPersister
LruCacheObjectPersister
(memory)
which in turn have many useful subclasses (like JsonObjectPersister
)
For the below example I am using a GsonObjectPersister. The linked code is just pulled from one of the Spring extensions but can be used standalone with the gist (tested with RS 1.4.12).
Common Gotyas
RequestListener.OnRequestSuccess(Object)
passingnull
. This can happen when checking the cache usingSpiceManager.getFromCache()
and the cache is empty - make sure to account for this. Solution - check or wrap the callback with your own to add a more descriptive callback.- Default auto-retries will be 3. Disable per request with
setRetryPolicy(null);
- Be careful if placing the
SpiceManager
start
andstop
calls in theActivities
/Fragments
onStart
&onStop
methods thats you dont instigate the request before this point, as this can result in edge cases where the response is lost and the UI is not updated. You can mitigate this with a combo of checking for cached / executing calls before starting your api call in onStart. - If dont specify a class to your cache manager the cache will not be checked and the normal callbacks wont be called - this can be with a silent log if you have disabled log so watch out.
- Offline operations - you will still place the code in the network method override so this wont by default work when the device has no network connection. See here for more.