Google has its clock app, which includes its stopwatch. I'm currently trying to create in my app a (count-up) timer, or you can call it a stopwatch, that will be able to run in the background, and when it runs in the background I want it to also show a notification, that displays the time it counts and a "Stop" button (all of this happens in google clock app (see here)). For the timer in my app, I'm using a Handler that posts a Runnable, which is posting itself. I'm writing my app in Java.
the code defining the 'timer' (Handler and Runnable):
Handler timerHandler = new Handler();
Runnable timerRunnable = new Runnable() {
@Override
public void run() {
long millis = System.currentTimeMillis() - startTime;
seconds = (millis / 1000) + PrefUtil.getTimerSecondsPassed();
timerHandler.postDelayed(this, 500);
}
};
my onPause function:
@Override
public void onPause() {
super.onPause();
if (timerState == TimerState.Running) {
timerHandler.removeCallbacks(timerRunnable);
//TODO: start background timer, show notification
}
PrefUtil.setTimerSecondsPassed(seconds);
PrefUtil.setTimerState(timerState);
}
How can I implement the background service and the notification in my app?
Edit
I've managed to succeed in creating a foreground service that runs my timer, but I have two problems:
- When I run the app after something like 5 minutes, the notification shows up in a 10-second delay.
- the notification stops updating after around 30 seconds from the time it starts/resumes (The timer keeps running in the background, but the notification won't keep updating with the timer).
Here's my Service
s code:
public class TimerService extends Service {
Long startTime = 0L, seconds = 0L;
boolean notificationJustStarted = true;
Handler timerHandler = new Handler();
Runnable timerRunnable;
NotificationCompat.Builder timerNotificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID);
public static final String TIMER_BROADCAST_ID = "TimerBroadcast";
Intent timerBroadcastIntent = new Intent(TIMER_BROADCAST_ID);
@Override
public void onCreate() {
Log.d(TAG, "onCreate: started service");
startForeground(1, new NotificationCompat.Builder(this, CHANNEL_ID).setSmallIcon(R.drawable.timer).setContentTitle("Goal In Progress").build());
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String goalName = intent.getStringExtra(PublicMethods.getAppContext().getString(R.string.timer_notification_service_current_goal_extra_name));
startTime = System.currentTimeMillis();
notificationJustStarted = true;
timerRunnable = new Runnable() {
@Override
public void run() {
long millis = System.currentTimeMillis() - startTime;
seconds = (millis / 1000) + PrefUtil.getTimerSecondsPassed();
updateNotification(goalName, seconds);
timerHandler.postDelayed(this, 500);
}
};
timerHandler.postDelayed(timerRunnable, 0);
return START_STICKY;
}
public void updateNotification(String goalName, Long seconds) {
try {
if (notificationJustStarted) {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
timerNotificationBuilder.setContentTitle("Goal In Progress")
.setOngoing(true)
.setSmallIcon(R.drawable.timer)
.setContentIntent(pendingIntent)
.setOnlyAlertOnce(true)
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_MAX);
notificationJustStarted = false;
}
timerNotificationBuilder.setContentText(goalName + " is in progress\nthis session's length: " + seconds);
startForeground(1, timerNotificationBuilder.build());
} catch (Exception e) {
Log.d(TAG, "updateNotification: Couldn't display a notification, due to:");
e.printStackTrace();
}
}
@Override
public void onDestroy() {
timerHandler.removeCallbacks(timerRunnable);
PrefUtil.setTimerSecondsPassed(seconds);
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
And here is how I start it in my fragment:
private void startTimerService() {
Intent serviceIntent = new Intent(getContext(), TimerService.class);
serviceIntent.putExtra(getString(R.string.timer_notification_service_current_goal_extra_name), "*Current Goal Name Here*");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Objects.requireNonNull(getContext()).startForegroundService(serviceIntent);
}
}
UPDATE
When I run the app on google pixel emulator, I don't face any of the issues listed