0

In my Android app there is a Service performing long-running task (e.g. playing music) which can be in one of two states: running or paused. There is a single 'pause/resume' image button to pause/resume the task. And also the task can be paused due to other reasons (not from UI). The button should look differently depending on the current state of the task (running or paused). So I need to sync the button image with the actual state of the task.

Now I've come up with the following solution: My Service has static pause and resume methods which send intents to itself like this:

public static void pause(Context context) {
    Intent intent = new Intent(context, MyService.class);
    intent.putExtra("PAUSE", true);
    context.startService(intent);
}

public static void resume(Context context) {
    Intent intent = new Intent(context, MyService.class);
    intent.putExtra("RESUME", true);
    context.startService(intent);
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if (intent.getBooleanExtra("PAUSE", false)) {
        doPause();
    } else if (intent.getBooleanExtra("RESUME", false)) {
        doResume();
    }
    return START_STICKY;
}

Since doPause() and doResume() can also be called from other places, I can't just set image to the ImageButton when calling MyService.pause() / MyService.resume(): the button image and the actual state of the task may become out of sync. Instead I use LocalBroadcastManager to notify activity when the button should be updated:

public void doPause() {
    paused = true;

    ... // Do some stuff to pause the service

    // Notify about state change
    Intent intent = new Intent("SERIVCE_STATE_CHANGED");
    intent.putExtra("PAUSED", true);
    LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}

Code for doResume() is analogous. So, my activity registers the receiver and sets the image in onReceive().

The solution seems to work. But my question is whether there is a better/simpler way to achieve the same goal? Any best practices?

Mikhail
  • 844
  • 8
  • 18

1 Answers1

1

I don't think your solution is bad. However, another way that is arguably better would be to use Android data binding.

The way this would work is, make a model object that represents the service state. E.g. Player.isPlaying, representing the service state. Then in the service, you would set the isPlaying state.

Then in your layout, you would use data binding to link that model object isPlaying state to the UI, however you want to set it, for example:

android:background="@{player.isPlaying ? @drawable/pause : @drawable/play}"

The reason I think this might be a better solution is, it links up the UI state directly 1:1 with the service. Conceptually I imagine a broadcast as being able to come from multiple sources. I don't see a problem with only having one source of the broadcast, though, which is why I don't see your current solution as bad.

You can view a detailed video about Data Binding from Google I/O 2016 here: https://www.youtube.com/watch?v=DAmMN7m3wLU

Matthew Horst
  • 1,991
  • 15
  • 18
  • Thanks, I was not aware about this possibility, I'll certainly try it. It seems to be much less code! – Mikhail Sep 12 '16 at 14:31
  • I think it's a very cool feature. It lets a lot of the boilerplate code linking the state/model be moved from code into xml and its even possible to link actions too to avoid having to make all those anonymous inner OnClickListener classes. – Matthew Horst Sep 12 '16 at 14:39