In last December ('13) I was asked to submit a talk proposal for the first Italian Droidcon, which I did. It got accepted, so in Febraury I gave my first public speech speaking of "(O)Authenticated Rest Interaction in Android" in front of real people.
This post is meant to be a follow up with the information that can't be found in the slides
This post is meant to be a follow up with the information that can't be found in the slides
I hoped to sum all the talk up in a post, but it got too long so I am going to split it in several pieces. This one will cover the asynchronous interaction with a remote webservice.
Disclaimer: I am not inventing anything new here, most of the content can be found on the internet. A great source of inspiration is this talk, from where I took the inspiration for the approach I am going to explain, probably in a worse way.
Rule #1: decouple the interaction with the web service from UI components (activities / fragment)
This means that cut and pasting that AsyncTask that contains an HttpUrlConnection code you found on google directly inside your activity is a bad idea (I don't even want to remind you that you should NOT host the HttpUrlConnection directly inside your activity, if you are doing that you probably won't understand the rest of this post and you should restart your android journey here).
There are several reasons for that.
You should start thinking that any network operation has a cost, in terms of bandwith and cpu power. Every time you turn the radio on involves consuming (a lot of) battery power, performing the request and parsing the result involves cpu (and therefore) battery power, so any network operation is precious and you do not want to waste any performed request result.
Said that, you should (must?) be aware that your activity might be killed at any time.. well, not at any time but as soon as it's not foregrounded anymore, which is something that is likely to happen if your beloved user chooses to leave your application in favour of candy crush, or if he receives an incoming phone call.
BUT, the result of a request started inside that activity might still be useful for a latter use, so you do not want your request to be killed together with the activity that hosted it.
Moreover, you should be aware that every time a "configuration change" occurs, which is what commonly happens when the device gets rotated, the current activity gets killed and recreated.
Well, guess what? That ongoing request that costed you a lot of power / cpu is lost, and its result with that. Even worse, it's not completely lost, but it might be notified to a leaked activity that nobody will look at anymore because the current one is the one which was recreated. There are ways to circumvent this, like hosting the components that you want to survive to a configuration change inside a headless fragment, but again, you should be very careful.
Finally, given that you want to host the interaction with the service inside the activity, and because the activity is guaranteed to be active only as long as it is in foreground, it is obvious that in this way is impossible to schedule requests to be performed while the application is not being used.
Malus point: if you have a god activity that handles the UI interaction, a component like a AsyncTask or a Thread that is in charge of performing the request, the real interaction with the service AND the interaction with the activity in order to update the UI, well, you are likely to have a poor encapsulation of your code and you might have some issues while testing your code.
So where am I supposed to perform the calls?
The answer is pretty simple. Use a Service, which is "is an application component that can perform long-running operations in the background and does not provide a user interface", so it's just right for our purpouse. The idea here is to host that rest call inside a service, so it can survive to any activity lifecycle event.
There are a couple of ways to interact with a service, you can throw intents at it, or you can bind your activities to it. In the latter case, you'll get back a reference to the whole service object, and so you are able to implement a rich api your activity can interact with.
On the other hand, you should be careful and stop your service when you do not need it anymore (ie when all the requests are satisfied), and you should also remember that you (still) need to host your asynchronous interaction inside of some kind of threaded component.
IntentService!
Are my favourite flavour of services when speaking of rest interaction. Why is that?
- IntentService(s) provide only a onHandleIntent method, which is performed inside a different thread. No need to spawn an asynctask
- IntentService(s) get destroyed as soon as onHandleIntent returns. No need to understand (or risk to forget) when the service needs to be shut down.
Cool! Now I have the data I retrieved from the service, what should I do with that?
Store it! The two most common alternatives are plain sqllite or using a content provider. The first benefit of using some kind of persistant storage is that you don't want to perform the same costly request twice in order to retrieve the same data.
Moreover, storing the data enforces the decoupling of your service with the activity, since the activity will fetch the data from the storage, regarless of when the storage was filled. This is also better in terms of user experience, because it's better to find some kind of data instead of an empty listview if the device is offline.
Bonus point! If you want to perform the inserts a bit faster, check if you can fetch only new data, if the apis you are consuming are offering any kind of timestamp parameter. Plus, remember to use transactions and bulkinserts in order to speed up your inserts.
Anything missing?
Ok, your activity asked to the service to perform the request, the request was performed, the result parsed and stored in your content provider. You should now notify the activity that the storage contains something new.
You have some options:
- Bind the activity to the service and pass a callback
- Use (local) broadcasts
- Use a message bus (Otto, eventbus)
You just need to remember to add a couple of lines of code:
Final result:
The final picture of this kind of architecture is the following (please forgive my extremely bad design skills):
Please note how the the activity asks the Service to perform the request with the webservice and then forgets about it. Any time the data needs to be displayed, it's always fetched from the storage.
The only other interaction happens when the service tells the activity it needs to reload the data.
In this way, any ongoing request will survive even if the activity gets killed. When the user will return to your application, the newly created activity will load the latest fetched data from the storage.
This was the first post related to the Rest Interaction in Android. I really hope I did not say too many wrong things...
If you liked this post, consider following me on twitter @fedepaol.
Nice article.
I got a question about one of the common situations, when you get some data from server, that is already sorted on the server, and we don't have enough fields to sort it on the client. Should we persist this data? And what should we do on another request? Clear table?
Thanks
Let's distinguish between notifying the activity and then having it update itself. ContentProviders plus loaders offer a full solution via observers, so you don't have to worry about anything.
On the other hand, if you want to notify the activity in some other way, you can:
- use a messagebus
- use (local) broadcasts
Once the activity knows that the underlying storage has new content, again, you have a lot of ways depending on how you stored the data.
Again, you can use custom loaders (my favourite one is https://github.com/commonsguy/cwac-loaderex . Alternatively, you can use asynctask, native threads or any async way to interact with your storage.
Gave a quick look at your groundy lib, it looks like it's a slick way to perform async interaction inside a service. My only concern is about passing a callback object instead of registering / unregistering a listener. By doing that, you can only return the result to the calling activity. Worse than that, it looks like (if I understood correctly, sorry it's a bit late here) you are not using weak references, so you might leak the activity in case the reference is being held but the activity is not active anymore.
Have you taken a look at github.com/telly/groundy ?
It makes interaction between Service and Activity easier.
Disclamer: I wrote the library.
I don't think you can "natively" stop an intentservice once you launched it. However, what you can do is letting it discover that you are not interested in it anymore. This http://stackoverflow.com/a/7893398/504596 sounds like a reasonable solution. However, despite being a big fan of them, I admit that this is a big limit of IntentService. Another limitation is that you cannot prioritize requests. This is because you are implicitly relying on the Intent queue instead of explicitly using one (or a library that use a queue). In any case, I would also say that in most common use cases, once you start a request you are unlikely to want to delete it. Still, think about updating your twitter feed. If you launch an update, you want the request to be completed even if the user leaves the application.
Hope my reply makes sense :-)
I assume you are talking about how to handle the endless data, not how to display it by using some kind of endless listview. In any case, I'd say that how you want to handle it depends on the data you want to display and on the kind of your application. If you really have an "endless" == almost infinite amount of content, what you can do is just provide a "window" on the data that is held on the server, the same way any twitter client does by showing only the latest tweets and by performing a new get whenever you approach the end of the list. If, on the other hand, you have a HUGE amount of content you always want to be synched on your device, this is a more complex topic to discuss. The "easiest" way would be letting the remote server be the master of your data, and perform a full delete before you download all the new data. From that, several optimizations can be implemented. One can be having a timestamp related to the whole resource, and not performing the sync if your device already has the latest version. More complex optimizations are harder to implement by using a restful interface, because you can't tell it "just send me the data changed since xxx or even the data deleted since xxx" in order to allow you to apply those differences (at least, not that I am aware of). Sorry for the delay in the reply, but I have been a bit busy in the latest couple of days.
What could you tell in regards to requests cancelation while using IntentService?