Hi guys, when foreground service stops. Notificati...
# android
t
Hi guys, when foreground service stops. Notification doesn’t cancel. To see the source code, go to the thread
😶 5
Copy code
class TimerServiceManager @Inject constructor(
     @ApplicationContext private val applicationContext: Context,
 ) {
     fun startTimerService() {
         val serviceIntent = Intent(applicationContext, TimerService::class.java)
         ContextCompat.startForegroundService(applicationContext, serviceIntent)
     }
 
     fun stopTimerService() {
         val serviceIntent = Intent(applicationContext, TimerService::class.java)
         applicationContext.stopService(serviceIntent)
 
     }
 }
When TimerService stops. it invokes removeTimerNotificationService()
Copy code
@AndroidEntryPoint
 class TimerService : Service() {
 
     private val serviceScope = CoroutineScope(SupervisorJob())
 
     private var stopService = false
     private var isServiceRunning = false
 
     @Inject
     lateinit var timerManager: TimerManager
 
     @Inject
     lateinit var notificationHelper: NotificationHelper
 
     override fun onCreate() {
         super.onCreate()
         stopService = false
         isServiceRunning = true
     }
 
     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
         startForeground(TIMER_SERVICE_NOTIFICATION_ID, notificationHelper.getBaseNotification().build())
 
         serviceScope.launch {
             timerManager.timerState.collectLatest {
                 if (!it.isDone && it.progress != 1.0F) {
                     notificationHelper.updateTimerServiceNotification(
                         timerRunning = it.isPlaying,
                         time = it.time,
                     )
                 }
                 if (stopService) {
                     stopForeground(true)
                     if (isServiceRunning) {
                         stopSelf()
                     }
                 }
 
 
             }
         }
 
 
         return START_STICKY
     }
 
 
     override fun onBind(p0: Intent?): IBinder? = null
 
     override fun onDestroy() {
         super.onDestroy()
         serviceScope.cancel()
         timerManager.onChangeDone()
        notificationHelper.removeTimerNotificationService()
         stopService = true
         isServiceRunning = false
     }
 }
NotificationHelper manages timer service notification and completed notification
Copy code
@Singleton
 class NotificationHelper @Inject constructor(
     @ApplicationContext private val applicationContext: Context
 ) {
     private val  notificationManager = NotificationManagerCompat.from(applicationContext)
 
     private val pendingIntentFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
         PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
     } else {
         PendingIntent.FLAG_UPDATE_CURRENT
     }
 
     private val openTimerIntent = Intent(
         Intent.ACTION_VIEW,
         "<https://www.clock.com/Timer|https://www.clock.com/Timer>".toUri(),
         applicationContext,
         MainActivity::class.java
     )
 
     private val openTimerPendingIntent = PendingIntent.getActivity(
         applicationContext, 0, openTimerIntent, pendingIntentFlags
     )
 
     init {
         createNotificationChannels()
     }
 
     fun getBaseNotification() = NotificationCompat.Builder(applicationContext, TIMER_SERVICE_CHANNEL_ID)
         .setContentTitle("Timer")
         .setSmallIcon(R.drawable.ic_hourglass_empty)
         .setContentIntent(openTimerPendingIntent)
         .setAutoCancel(true)
         .setColor(ContextCompat.getColor(applicationContext, R.color.blue))
         .setColorized(true)
         .setSilent(true)
         .setOnlyAlertOnce(true)
 
      fun updateTimerServiceNotification(
          time: String,
          timerRunning: Boolean
      ) {
          val actionIntent = getTimerNotificationActionIntent(time, timerRunning)
 
 
          val remove =  remove()
          val startStopIcon = if (timerRunning) R.drawable.ic_stop else R.drawable.ic_play
          val startStopLabel = if (timerRunning) "Pause" else "Resume"
 
          val notificationUpdate = getBaseNotification()
              .setContentText(time)
              .addAction(R.drawable.ic_close, "Cancel", remove)
              .addAction(
                  startStopIcon,
                  startStopLabel,
                  actionIntent
              )
              .build()
 
          notificationManager.notify(TIMER_SERVICE_NOTIFICATION_ID, notificationUpdate)
 
     }
 
     private fun remove() : PendingIntent {
         val broadcastIntent =
             Intent(applicationContext, TimerNotificationBroadcastReceiver::class.java).apply {
                 action = ACTION_DELETE
             }
         return PendingIntent.getBroadcast(
             applicationContext,
             0,
             broadcastIntent,
             pendingIntentFlags
         )
     }
 
 
 
     private fun getTimerNotificationActionIntent(
         time: String,
         timerRunning: Boolean,
     ): PendingIntent {
         val broadcastIntent =
             Intent(applicationContext, TimerNotificationBroadcastReceiver::class.java).apply {
                 putExtra(EXTRA_TIME, time)
                 putExtra(EXTRA_TIMER_RUNNING, timerRunning)
             }
         return PendingIntent.getBroadcast(
             applicationContext,
             0,
             broadcastIntent,
             pendingIntentFlags
         )
     }
 
      fun showTimerCompletedNotification(time: String) {
             val timerCompletedNotification = NotificationCompat.Builder(applicationContext, TIMER_COMPLETED_CHANNEL_ID)
                 .setContentTitle("Time's up")
                 .setContentText(time)
                 .setContentIntent(openTimerPendingIntent)
                 .setSmallIcon(R.drawable.ic_hourglass_empty)
                 .build()
             notificationManager.notify(TIMER_COMPLETED_NOTIFICATION_ID, timerCompletedNotification)
     }
 
     fun removeTimerNotificationService() {
         notificationManager.cancel(TIMER_SERVICE_NOTIFICATION_ID)
     }
 
     fun removeTimerCompletedNotification() {
         notificationManager.cancel(TIMER_COMPLETED_NOTIFICATION_ID)
     }
 
     private fun createNotificationChannels() {
         val timerServiceChannel = NotificationChannelCompat.Builder(
             TIMER_SERVICE_CHANNEL_ID,
             NotificationManagerCompat.IMPORTANCE_DEFAULT
         )
             .setName(applicationContext.getString(R.string.timer_service_channel_name))
             .setDescription(applicationContext.getString(R.string.timer_service_channel_description))
             .setSound(null, null)
             .build()
 
         val timerCompletedChannel = NotificationChannelCompat.Builder(
             TIMER_COMPLETED_CHANNEL_ID,
             NotificationManagerCompat.IMPORTANCE_HIGH
         )
             .setName(applicationContext.getString(R.string.timer_completed_channel_name))
             .setDescription(applicationContext.getString(R.string.timer_completed_channel_description))
             .build()
 
         notificationManager.createNotificationChannelsCompat(
             listOf(
                 timerServiceChannel,
                 timerCompletedChannel
             )
         )
     }
 }
 
 private const val TIMER_SERVICE_CHANNEL_ID = "timer_service_channel"
 private const val TIMER_COMPLETED_CHANNEL_ID = "timer_completed_notification_channel"
 const val TIMER_SERVICE_NOTIFICATION_ID = -1
 private const val TIMER_COMPLETED_NOTIFICATION_ID = -2
TimerNotificationBroadcastReceiver is called when the user presses the notification actions buttons pause, resume and cancel
Copy code
@AndroidEntryPoint
 class TimerNotificationBroadcastReceiver : BroadcastReceiver() {
 
     @Inject
     lateinit var timerManager: TimerManager
 
     @Inject
     lateinit var notificationHelper: NotificationHelper
 
     override fun onReceive(p0: Context?, intent: Intent?) {
         val timerRunning = intent?.getBooleanExtra(EXTRA_TIMER_RUNNING, false)
         val time = intent?.getStringExtra(EXTRA_TIME)
         val action = intent?.action
         timerManager.handleCountDownTimer()
         if (timerRunning == true) {
             if (time != null) {
                     notificationHelper.updateTimerServiceNotification(
                         timerRunning = false,
                         time = time
                     )
                 Log.d(TAG, "onReceive() called with: p0 = $p0, intent = $intent")
             }
         }
         if (action.equals(ACTION_DELETE)) {
                 timerManager.onChangeDone()
                 timerManager.stopService()
         }
 
     }
 }
 
 
 const val EXTRA_TIME = "EXTRA_TIME"
 const val EXTRA_TIMER_RUNNING = "EXTRA_TIMER_RUNNING"
 const val ACTION_DELETE = "ACTION_DELETE"