Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to sync Toggle Button State with Foreground Service?

I have a Foreground Service which I start and stop with a Toggle Button in an Activity. To keep track if the Service is running or not I have used a variable which I store in SharedPreference.

Here is the flow

When a user enables toggle button

startService()     start background work  
makeForeGround()              
bindService()      to get update

When a user disables toggle button

unbindService()    to stop getting updates    
stopForeGround()          
stopService()      stop background work  

When a user leaves the Activity

unbindService(); To stop getting the update. 
                 But the service is running for background work

When a user reenters the Activity

if(Service is running)
Show toggle button state as enabled 
bindService() 

I have achieved all these.

Only problem I face is this:

Sometimes my Service gets killed by OS. In that case, I have no way to update My SharedPreference variable. Hence no way to sync my Toggle button with Service.

How do I get notified that the service has got killed by OS. (Because onDestroy() is not getting called in this case)? I have also gone through How to check if a service is running on Android?, but sadly the solutions and methods provided are deprecated.

like image 497
Rohit Singh Avatar asked Mar 21 '19 09:03

Rohit Singh


People also ask

How do I start a foreground service with Android notification channels?

Inside the service, usually in onStartCommand() , you can request that your service run in the foreground. To do so, call startForeground() . This method takes two parameters: a positive integer that uniquely identifies the notification in the status bar and the Notification object itself.

What is the difference between Startservice and startForegroundService?

NO MORE STARTSERVICE - The new context. startForegroundService() method starts a foreground service but with a implicit contract that service will call start foreground within 5 second of its creation. You can also call startService and startForegroundService for different OS version.

How does activity come to the foreground?

Activity or dialog appears in foregroundWhen the covered activity returns to the foreground and regains focus, it calls onResume() . If a new activity or dialog appears in the foreground, taking focus and completely covering the activity in progress, the covered activity loses focus and enters the Stopped state.


2 Answers

You should consider approaching your problem from a different direction.

Trying to infer when the system has killed your Service is a problematic. Apps (Services) are not notified when they are terminated, therefor detecting this condition is difficult. However, finding out when a Service has started is trivial (onCreate()). This is a clue to the right approach.

It's clear that you understand that the proper way to exchange information between an Activity and a Service is via a binding. This is the other critical piece of the solution.

SOLUTION

You should expand the lifetime of your Service. Right now, it appears that you create the Service for the express purpose of conducting a long-running task. The Service is created when you want the task to start, and terminates as soon as it ends. In other words, the scope of the Service and the "task" are the same.

Instead, you should make the "task" a child of the Service; the Service should exist for somewhat longer than the "task". In particular, the Service should be available whenever the Activity is started (if for no other reason than to communicate information about the state of the "task" to the Activity).

Whenever the Activity starts, it should bind to the Service, and register a listener with the Service. The Service calls this listener whenever A.) the listener is newly registered, or B.) the "task" changes states (starts or stops). Thus, the Activity is always informed instantly of state changes, so long as it is bound to the Service.

When the Activity wants the "task" to change states, it should inform the Service via the binding (or possibly a startService() call, even though the Service is already running). The Service would then start/stop the background thread (or whatever it is you do to accomplish the "task"), and notify any listeners.

If the Service has been running the "task" for some time, and the Activity pops up all of a sudden, then the Service clearly knows the "task" exists, and the Activity will get that information via the listener, as soon as it binds.

When the Activity stops, it should clean up by unregistering its listener, and unbinding from the Service.

This approach obviates the need for SharedPreferences, or to detect when the Service has stopped. Whenever the Service is created, obviously the "task" is not running, so the Service will always know the correct answer to give to the Activity. And since the Service must be created before it can bind to the Activity, there is no order-of-operations problem.

like image 168
greeble31 Avatar answered Oct 11 '22 13:10

greeble31


Just a workaround. Not a general solution.

I redesigned. Now the flow is

Entering the Activity

bindService()

Leaving the Activity

unbindService()

Enable Toggle

startService()

Disable Toggle

stopService()

How do I keep track if the service is running?

For that, I am using an int variable SERVICE_START_COUNTER in the service and increase its value in onStartCommand(). And I receive the SERVICE_START_COUNTER when the Activity binds to service. Based on the counter value I can differentiate if the service is running or not

public boolean isServiceRunning(int SERVICE_START_COUNTER)
{
    return SERVICE_START_COUNTER == 0 ? false : true;
}

And based on this value I have written a method which sync-up the UI.

public void syncUI(boolean isServiceRunning)
{
   setToggleState(isServiceRunning);
   setAniamtionState(isServiceRunning);
}

SERVICE_START_COUNTER value in different cases?

1) Enter the Activity for the first time (Toggle is still disabled)

onBind() // SERVICE_START_COUNTER = 0

2) Enter the Activity for the first time (User enables the toggle)

onBind()           // SERVICE_START_COUNTER = 0
onStartCommand()   // SERVICE_START_COUNTER = 1

3) Re-enter the Activity for the second time (Toggle is enabled)

onBind()           // SERVICE_START_COUNTER = 1

4) Service is killed by OS. Re-enter the Activity

onBind()           // SERVICE_START_COUNTER = 0

Why it's not a general solution?

Since binding takes time (because onServiceConnected()) is called asynchronously. Hence the Result might not be available right away. For this, I first check the status of service in the Splash Activity and update a similar counter in a Utility class.

like image 2
Rohit Singh Avatar answered Oct 11 '22 13:10

Rohit Singh