Search code examples
androidandroid-notifications

How To schedule and a send Notification even after user removes app from recent apps?


My app is simple

  1. Take Time from the user(I know how to do it)

  2. schedule A notification(I know how to send notification)

  3. Now I just want to know that How can I send this notification even user removes it from recent apps.

Tried Solutions- AlarmManager, BroudcastReceiver, Service, Intent Service,

But all are work only when the app is present in RAM (recent apps list Of Mobile).

How can I do that just tell me the topic. I don't need code. I will study that.


Solution

  • As you have mentioned that AlarmManager and others did not work for you, I tested what you are trying to achieve with JobScheduler.

    It worked for me, and it worked even after removing the app from the recent apps list. I tried it on Android 10 emulator.

    You have asked for topics / references to study as the answer. So first I'll mention the references I used.

    Here is the code lab of JobScheduler and it's really helpful : https://codelabs.developers.google.com/codelabs/android-training-job-scheduler/

    Here is a good reference about creating multiple scheduled jobs : https://android-developers.googleblog.com/2017/10/working-with-multiple-jobservices.html

    Here is what I tried.

    NotificationJobService.kt

    private const val NOTIF_CHANNEL_ID = "primary_notification_channel"
    private const val NOTIF_CHANNEL_NAME = "Job Service notification"
    
    class NotificationJobService : JobService() {
    
        override fun onStartJob(params: JobParameters?): Boolean {
    
            // Get Notification Manager
            val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    
            // Create Notification Channel if device OS >= Android O
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                NotificationChannel(NOTIF_CHANNEL_ID, NOTIF_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT).let {
                    notificationManager.createNotificationChannel(it)
                }
            }
    
            // Create PendingIntent with empty Intent
            // So this pending intent does nothing
            val pendingIntent = PendingIntent.getActivity(this, 0, Intent(), PendingIntent.FLAG_ONE_SHOT)
    
            // Configure NotificationBuilder
            val builder = NotificationCompat.Builder(this, NOTIF_CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_launcher_foreground)
                .setContentTitle("Title")
                .setContentText("Message")
                .setAutoCancel(true)
                .setContentIntent(pendingIntent)
    
            // Make the Notification
            notificationManager.notify(0, builder.build())
    
            // False to let system know that the job is completed by the end of onStartJob(),
            // and the system calls jobFinished() to end the job.
            return false
    
        }
    
        override fun onStopJob(params: JobParameters?): Boolean {
            // True because if the job fails, you want the job to be rescheduled instead of dropped.
            return true
        }
    
    }
    

    MainActivity.kt

    class MainActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            // Job ID must be unique if you have multiple jobs scheduled
            var jobID = 0
    
            // Get fake user set time (a future time 1 min from current time)
            val ( userSetHourOfDay, userSetMinute ) = getMockUserSetTime()
    
            val timeToWaitBeforeExecuteJob = calculateTimeDifferenceMs(userSetHourOfDay, userSetMinute)
    
            (getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler).run {
                schedule(
                    JobInfo.Builder(
                        jobID,
                        ComponentName(baseContext, NotificationJobService::class.java)
                    )
                    // job execution will be delayed by this amount of time
                    .setMinimumLatency(timeToWaitBeforeExecuteJob)
                    // job will be run by this deadline
                    .setOverrideDeadline(timeToWaitBeforeExecuteJob)
                    .build()
                )
            }
        }
    
        // Returns a pair ( hourOfDay, minute ) that represents a future time,
        // 1 minute after the current time
        private fun getMockUserSetTime() : Pair<Int, Int> {
            val calendar = Calendar.getInstance().apply {
                // add just 1 min from current time
                add(Calendar.MINUTE, 1)
            }
            return Pair(calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE))
        }
    
        // Calculate time difference relative to current time in ms
        private fun calculateTimeDifferenceMs(hourOfDay: Int, minute: Int) : Long {
            val now = Calendar.getInstance()
            val then = (now.clone() as Calendar).apply {
                set(Calendar.HOUR_OF_DAY, hourOfDay)
                set(Calendar.MINUTE, minute)
            }
            return then.timeInMillis - now.timeInMillis
        }
    
    }
    

    I used setMinimumLatency(timeToWaitBeforeExecuteJob) and setOverrideDeadline(timeToWaitBeforeExecuteJob) constraints when scheduling the job so that, the job will be executed at exact time we want it to run.

    I ran the app once, go back and removed the app from recent apps list. Then I suspended the device, and after 1 minute, I heard the notification sound. When I resumed the device and checked, the expected notification was there.

    You should consider Remon Shehatta's answer as well. Because it seems WorkManager sits on top of JobScheduler and AlarmManager, and picks the right one based on device's API level. So it may be better to use WorkManager if you are targeting API levels older than 21. But as you mentioned that AlarmManager did not work for you, you should experiment and choose the right one.

    Please update us with your findings.