0

I am new to Android and I have a kind of design question. So I understand that it is recommended to use Fragments. I also understand that an Activity "owns" a Fragment.
So my question is the following:
Assume we want to make a long running background call e.g. HTTP to a server.
The results should be displayed in the UI.
So what is the best/standard practice?
1) Should the object doing the long running calls be "owned" by the Activity and the results send back to the Activity and then from the Activity to the Fragment so that the UI is updated?
2) Or should the object doing the long running called be "owned" by the Fragment and the results are send back to the Fragment for the UI to be updated? So the Activity is completely unaware of this?

Jim
  • 18,826
  • 34
  • 135
  • 254
  • Not really related, but Fragment should not be tied to a specific activity, it must be able to live on itself. The main goal of using fragment is to be able to separate the code and easily do specific layout for tablet for instance. So if the "long" running operations is just a few seconds I would use an AsyncTask in the Fragment. – Stephane Mathis Mar 06 '15 at 21:23

3 Answers3

3

Neither, IMHO. The object doing the long running should be managed by a Service, where you are using WakeLock (and, if needed, WifiLock) while the long-running HTTP operation is running. An IntentService might be a good candidate, as you need a background thread anyway.

The service, in turn, can use some sort of in-process event bus (LocalBroadcastManager, greenrobot's EventBus, Square's Otto, etc.), to let the UI layer know about any significant events that occurred from that HTTP operation. Whether the activity or the fragment is the one to subscribe to the events on the bus depends on what needs to happen for the event. If a fragment's widgets need to be updated, the fragment would subscribe. If the mix of fragments change, or some other activity-level UI change is needed, the activity would subscribe.

All of this assumes that by "long running" you mean something taking over a second or so.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • +1. Yes over a second. I was not aware of all these constructs. I was wondering do you have an example to refer to I could study? – Jim Mar 06 '15 at 20:57
  • @Jim: Not one that completely matches what you're seeking. [This directory](https://github.com/commonsguy/cw-omnibus/tree/master/EventBus) contains samples of using the three event buses that I mentioned, but the work is triggered by `AlarmManager`. [This sample project](https://github.com/commonsguy/cw-omnibus/tree/master/Service/Downloader) demonstrates a service doing an HTTP download and using `LocalBroadcastManager` to notify a fragment about results, though it's only doing so when the work is completed. – CommonsWare Mar 06 '15 at 21:02
  • `though it's only doing so when the work is completed.` I did not get this? When else would it do it if not when completed? – Jim Mar 06 '15 at 21:03
  • @Jim: In a WebSocket or other long-polling scenario, you might raise events while the HTTP operation is still ongoing, reporting interim results. – CommonsWare Mar 06 '15 at 21:05
  • I checked the Downloader example in your link. Very interesting code. But there are no results returned by Downloader to DownloadFragment. There is only a "signal" that the download ended but no update to the UI. How would the data be sent in this example if there were any? – Jim Mar 06 '15 at 21:11
  • @Jim: The file is written to disk in that sample; the fragment only needs to know that the download is done. What goes in the event is up to you. A better version of that sample would at least signal success/failure. – CommonsWare Mar 06 '15 at 21:12
  • I understand that. I was wondering if the code as is, could be easy to adapt to pass some data back to the Fragment to update the UI. Because I like this approach of event but I need to pass data to the fragment for UI update – Jim Mar 06 '15 at 21:14
  • @Jim: Yes, though the downside of `LocalBroadcastManager` is that the data in question has to be able to go into an `Intent`. The alternative event buses are significantly more flexible. – CommonsWare Mar 06 '15 at 21:17
  • Ah you mean via extras in Intent? – Jim Mar 06 '15 at 21:19
  • 1
    @Jim: Yes, `Intent` extras. That limits your data types, and going the "well, I'll make it `Parcelable` route" is aggravating if that's not needed for other reasons. With the other event buses, the event is an arbitrary object of a class of your own design, so you can pass around whatever you want in the event. – CommonsWare Mar 06 '15 at 21:21
1

For the long running task it's recommended to implement a sticky Service that contains a thread for the ServerSocket listener. Next I'd recommend to process requests by dedicated Thread's which are managed by a thread pool (check for instance this example).

In order to display results in your activity there are several approaches possible:

  • send a local broadcast from your service or thread which gets processed by registered BroadcastReceiver's which are part of your UI component (either Fragment's or Activity's)
  • bind your Service to the Activity (which might contain further fragments) and propagate information to containing fragments. There are three ways to go.
    Note: In this post it's being said it's better to bind to the Application
  • pass data via Intent or a SQLiteDatabase

What I like and prefer is using local BroadcastReceiver's but this is just my personal preference.

It depends on your requirements, what might be the best solution.

Community
  • 1
  • 1
Trinimon
  • 13,839
  • 9
  • 44
  • 60
  • `that contains a thread for the ServerSocket listener.` Actually I am planning to use volley – Jim Mar 06 '15 at 21:21
  • That Volley library is really quite interesting - didn't hear about it before :) apropos library: some time ago I read about _Otto_ (http://square.github.io/otto/). I've no experience with that one but it was discussed as an interesting candidate for app internal communication ... – Trinimon Mar 06 '15 at 21:31
1

I'm using a variation of the design recommended by @CommonsWare. I have an ApiClient class that listens to the event bus for requests to invoke API methods asynchronously. Any parameters that are needed for the API call go into the bus request message.

The ApiClient uses Retrofit to make the async API call, and posts a 'result message' containing the result to the event bus on success, and an 'error message' if there's an error. Each API call has it's own triplet of bus messages - xxxRequest, xxxResponse, xxxError.

So, when an Activity or a Fragment (or other, non-ui class) wants to invoke the api, it registers to the bus for the xxxResponse and xxxError messages, and then posts an xxxRequest message to the bus.

The only potential down-sides are:

  • The ApiClient is a singleton, and is owned by the Application class, just so that it doesn't get garbage collected.

  • You wind up with a large number of Message classes - I deal with this by putting them into their own package.

GreyBeardedGeek
  • 29,460
  • 2
  • 47
  • 67
  • If it is owned by the Fragment is GCed? Not clear on the singleton comment – Jim Mar 06 '15 at 21:56
  • It's a singleton because it's available application-wide, and is not coupled to any of the Activities or Fragments (there are no references to the ApiClient anywhere but in the Application class, and everything is communicated via the bus). Since the ApiClass has to have an instance to be able to listen to the bus, something has to hang on to a reference to it for the life of the app, or it would be garbage collected. Since the Application class lives for the life of the app (by definition), I keep the reference there. – GreyBeardedGeek Mar 06 '15 at 22:14
  • By the way, I also use the bus to communicate between Activities and Fragments. That way, there's no need to reference each other directly, or to create interfaces for them to communicate through. Also no need to directly manage interaction with the Activity/Fragment lifecycle. Just register to and unregister from the bus at the appropriate points in your lifecycle. Bus messages having no listeners (because the intended target unregistered) just harmlessly fall on the floor as opposed to causing a crash when you get a callback after the end of your lifecycle (e.g. unattached fragment). – GreyBeardedGeek Mar 06 '15 at 22:18