Today, I ran into an unexpected, metaphorical wall: apparently, an Activitiy embedded into another through TabHost can’t properly bind services.

I did a little googling research, and found that there’s a lot of confusion in the Interwebs on how to get around this limitation. Alleged solutions were mostly based on completely breaking the separation of concerns between the host Activity and its children, passing references around and defeating the purpose of using tabs instead of just exchanging views.

In the end, I switched back from Chrome to Eclipse. After looking hard at the screen for a couple of minutes, smoking a cigarette and then staring at my code some more, I finally found a way around it (and, as so often happens, wondered why I didn’t try that from the beginning).

How to do it wrong

Let’s say you use the following code to bind a Service to an Activity.

anActivity.bindService(
    new Intent(anActivity, MyService.class),
    aServiceConnection,
    Context.BIND_AUTO_CREATE
);

It will work as long as anActivity is not an embedded child. Note that the bindService() call is made upon the same Activity object that intends to use the connection, also passed as Context to the Intent.

Why doesn’t it work?

I have absolutely no idea. I intend to take a look at the source and figure it out when I have the time, but seeing as it’s Saturday night, a blind fix will just have to do =).

How to do it right

Simple enough: bind the connection to the host Activity (or maybe the global ApplicationContext), instead of the child. There’s no need to change the Context passed to the Intent or to lose control over the ServiceConnection.

anActivity.getParent().bindService(
    new Intent(anActivity, MyService.class),
    aServiceConnection,
    Context.BIND_AUTO_CREATE
);

The only catch is that, even though the ServiceConnection can be 100% handled from the child Activity, the Service is bound to another Context. This means that, when you no longer need it, you need to invoke unbindService() from the same Context:

anActivity.getParent().unbindService(mConnection)

I haven’t fully experimented with this yet, but I like the approach much better than sharing IBinder instances or exchanging references. It preserves the encapsulation around the activities and allows each child to have its own ServiceConnection. Also, the same Activity can be hosted anywhere without modification, or even started independently: just checking the return of getParent() for null should be enough to decide which Context to use.

That’s it for today. Not a great post, but hopefully it will save someone’s time. Have a good weekend!

Advertisements