0

I try to create daily notification for my application. In order to test the application I set the interval for the notifications to 15 minutes. Unfortunately, no notification is shown. Neither when the application is running nor when it is closed. I tried to inspire myself with these solutions:

https://stackoverflow.com/questions/33055129/how-to-show-a-notification-everyday-at-a-certain-time-even-when-the-app-is-close

https://developer.android.com/codelabs/android-training-alarm-manager#0

https://github.com/google-developer-training/android-fundamentals-apps-v2/tree/master/StandUp (repository for the previous link)

I added uses-permissions and receiver to the manifest file.

manifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.ovu">

    <uses-permission android:name = "android.permission.VIBRATE" />
    <uses-permission android:name="com.android.alarm.permission.SET_ALARM"/>
    <!-- Permission to start Alarm on device reboot -->
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity
          ...
        </activity>

        <receiver android:name= ".DailyNotificationReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
            </intent-filter>
        </receiver>

    </application>

</manifest>

This class extends BroadcastReceiver class and overrides onReceive method.

DailyNotificationReceiver

class DailyNotificationReceiver : BroadcastReceiver() {
    companion object {

        // Notification ID.
        private const val NOTIFICATION_ID = 0

        // Notification channel ID.
        private const val PRIMARY_CHANNEL_ID = "primary_notification_channel"

    }
    private lateinit var mNotificationManager: NotificationManager
    private val notificationContent = "Please measure your temperature and cervical mucus"
    private val contentTitle = "Provide me your temperature and cervical mucus!"

    override fun onReceive(context: Context, intent: Intent) {
        mNotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        // Deliver the notification.
        deliverNotification(context);

    }

    private fun deliverNotification(context: Context) {
        val contentIntent = Intent(context, MainActivity::class.java)

        val contentPendingIntent = PendingIntent.getActivity(context, NOTIFICATION_ID, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT)
        // Build the notification
        val builder = NotificationCompat.Builder(context, PRIMARY_CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_dialog_alert)
                .setContentTitle(contentTitle)
                .setContentText(notificationContent)
                .setContentIntent(contentPendingIntent)
                .setPriority(NotificationCompat.PRIORITY_HIGH)
                .setAutoCancel(true)
                .setDefaults(NotificationCompat.DEFAULT_ALL)
        // Deliver the notification
        mNotificationManager.notify(NOTIFICATION_ID, builder.build())
    }


}

In this activity, instance of AlarmManager should set daily repeating.

ActivityNotification

class ActivityNotification : AppCompatActivity() {

    companion object {
        // Notification ID.
        private const val NOTIFICATION_ID = 0

        // Notification channel ID.
        private const val PRIMARY_CHANNEL_ID = "primary_notification_channel"
    }

    private lateinit var mNotificationManager: NotificationManager

    private fun notifyUser(){

        val notifyIntent = Intent(this, DailyNotificationReceiver::class.java)

        val notifyPendingIntent = PendingIntent.getBroadcast(this, NOTIFICATION_ID, notifyIntent,
                PendingIntent.FLAG_UPDATE_CURRENT)

        val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager

        val repeatInterval = AlarmManager.INTERVAL_FIFTEEN_MINUTES

        val triggerTime = (SystemClock.elapsedRealtime()
                + repeatInterval)

        alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                triggerTime, repeatInterval,
                notifyPendingIntent)

        // Create the notification channel.
        createNotificationChannel();

        Toast.makeText(this, "Alarm's set", Toast.LENGTH_LONG).show()
    }

    private fun createNotificationChannel() {

        // Create a notification manager object.
        mNotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager

        // Notification channels are only available in OREO and higher.
        // So, add a check on SDK version.
        if (Build.VERSION.SDK_INT >=
                Build.VERSION_CODES.O) {

            // Create the NotificationChannel with all the parameters.
            val notificationChannel = NotificationChannel(PRIMARY_CHANNEL_ID,
                    "Stand up notification",
                    NotificationManager.IMPORTANCE_HIGH)
            notificationChannel.enableLights(true)
            notificationChannel.lightColor = Color.RED
            notificationChannel.enableVibration(true)
            notificationChannel.description = "Notifies every 15 minutes to " +
                    "stand up and walk"
            mNotificationManager.createNotificationChannel(notificationChannel)
        }
    }
     override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_notification)
        editTextNotificationHour = findViewById(R.id.editTextChangedNotification)
        mNotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
    }

  fun changeNotificationHour(view: View){
        notifyUser()
        val intent = Intent(this, ProfileActivity::class.java)
        startActivity(intent)
        finish()
    }


I'd be really happy if you guys can help me to find the solution.

Remi
  • 99
  • 1
  • 3
  • 13

1 Answers1

0

Firstly, let's start by enabling our BroadcastReceiver in Manifest by changing our code to this:

<receiver 
  android:name= ".DailyNotificationReceiver"
  android:enabled="true"
  android:exported="true">
  <intent-filter>
    <action android:name="android.intent.action.BOOT_COMPLETED"/>
  </intent-filter>
</receiver>

Secondly, I am a bit unsure of the purpose of the ActivityNotification Activity. It would be more than enough to create a simple class like Alarms.

From there you can define all the methods you need to set up an alarm. Something like this:

class InternetDaysLeftAlarm @Inject constructor(
    @ApplicationContext val context: Context
) {
    /**
     * Function is used to schedule one-time Alarm that will trigger on specific time.
     *
     * @param hourOfDay -> parameter defines what o'clock should the alarm be triggered at. (24-hour)
     *      default value is:
     * @see INTERNET_DAYS_LEFT_ALARM_DEFAULT_TRIGGER_HOUR
     */
    fun scheduleAlarm(hourOfDay: Int = INTERNET_DAYS_LEFT_ALARM_DEFAULT_TRIGGER_HOUR) {
        val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
        val intent = Intent(context, InternetDaysLeftReceiver::class.java)
        intent.action = INTENT_ACTION_INTERNET_DAYS_LEFT_ALARM
        val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0)

        val msUntilTriggerHour: Long = TimeUnit.MINUTES.toMillis(minutesUntilOClock(hourOfDay))

        // Calculating and adding jitter in order to ease load on server
        val jitter: Long = TimeUnit.MINUTES.toMillis(Random.nextInt(0, 420).toLong())

        val alarmTimeAtUTC: Long = System.currentTimeMillis() + msUntilTriggerHour + jitter

        // Enabling BootReceiver
        val bootReceiver = ComponentName(context, BootReceiver::class.java)
        context.packageManager.setComponentEnabledSetting(
            bootReceiver,
            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
            PackageManager.DONT_KILL_APP
        )

        /**
         * Schedules the Alarm based on Android Version.
         *
         * As per AlarmManager documentation to guarantee Alarm execution at specified time we use following methods:
         *
         * @see AlarmManager.setExactAndAllowWhileIdle -> Android.M [API 23] and above.
         * @see AlarmManager.setAlarmClock -> Everything below Android M.
         */

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
            alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTimeAtUTC, pendingIntent)
        } else {
            alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(alarmTimeAtUTC, pendingIntent), pendingIntent)
        }
    }

Notifications can be handle by:

  • Creating a separate Notifications class

or

  • Handling all the notification things like creating channel and sending them inside BroadcastReceiver when you receive an Alarm.
Vitaliy-T
  • 733
  • 6
  • 23
  • I omitted pasting the code from this Activity to my question in order to make it more readable. In this activity user can change the hour of the daily notification. I've just added the code to the manifest file and to the activity as you advised. I run the application on an emulator and suprisingly it works. I tried to debug it on my phone (Xiaomi Redmi) but it does not work. I checked permissions of the application and the notification permission is granted. Do you maybe know what can cause this? – Remi Nov 12 '20 at 17:07
  • Did you schedule Alarm with the methods that I wrote? (setExactAndAllowWhileIdle and setAlarmClock)? – Vitaliy-T Nov 12 '20 at 17:17
  • Yes I did. I have also a problem with bootReceiver. It throws an exception "component hasn't been found in the package" – Remi Nov 12 '20 at 17:39
  • It does only display the notification once. – Remi Nov 12 '20 at 17:41
  • Boot receiver should be created like any other broadcast receiver. The code I have is simply for enabling it programmatically. You should receive it only once. All you’d need to do is reschedule from broadcast receiver. The reason u dont use repeated alarms is because those can be delay with Android battery saving features. If u dont care about exact execution, then u should replace methods, else just reschedule it on each receive. I will look into receiving it on xiaomi tomorrow, however, in my app this works on every device that I tested: Xiaomi, Moto etc... – Vitaliy-T Nov 12 '20 at 17:45
  • Unfortunately, this code throws an exception: `val bootReceiver = ComponentName(applicationContext, com.example.ovu.DailyNotificationDeviceBootReceiver::class.java) applicationContext.packageManager.setComponentEnabledSetting( bootReceiver, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP )` – Remi Nov 12 '20 at 18:19
  • I tried this solution: `alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, triggerTime,repeatInterval, notifyPendingIntent)` where: `var repeatInterval = 1000L * 60L val calendar = Calendar.getInstance() // input hour calendar[Calendar.HOUR_OF_DAY] = hour calendar[Calendar.MINUTE] = 0 val date = calendar.time val triggerTime = date.time` This solution works only on the emulator - it does not work on my physical device – Remi Nov 12 '20 at 19:56