AlarmManager with Notification in Android does not show any notifications - android

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.

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.

Related

How to cancel a scheduled local notification in Kotlin?

I have an app where the user creates its own local notifications. User declares name, date and time the nofication should popup and specifies the repeating frequency.
Then the notifications are listed in a recyclerview in another fragment.
The user is able to delete notification by swiping its recyclerview item to the left.
But when I create a notification, delete it then it still pops up at the specified time.
I am storing the notificationID in SharedPreferences as a date when its created (so that I can store it in my DB). I am passing it as a string with putExtra to my BroadcastReceiver class, I am getting the notificationID as a String in my BroadcastReceiver class with getStringExtra. Then passing the same notificationID.toInt() to my pendingIntent.getActivity. Then in my Fragment with recyclerView I am passing the same notificationID for cancelling and it still doesn't cancel.
Perhaps I'm using wrong flags?
Thanks a lot for any help.
Here's my BroadcastReceiver class:
const val titleExtra = "titleExtra"
const val descriptionExtra = "descriptionExtra"
val notificationID = "notificationID"
class Notification: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val intentToRepeat = Intent(context, MainActivity::class.java)
val id = intent.getStringExtra(notificationID).toString()
val pendingIntent = PendingIntent.getActivity(context, id.toInt(), intentToRepeat, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
val notification = NotificationCompat.Builder(context, channelID)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle(intent.getStringExtra(titleExtra))
.setContentText(intent.getStringExtra(descriptionExtra))
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.build()
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (intent.action == "cancel") {
manager.cancel(id.toInt())
}
else {
manager.notify(id.toInt(), notification)
}
}
}
My AndroidManifest:
<receiver android:name=".powiadomienia.Powiadomienie" android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="cancel"/>
<action android:name="create" />
</intent-filter>
</receiver>
In my recyclerview with notifications listed:
val currentNotification: SetNotification = listAdapter.getNotificationByPosition(viewHolder.bindingAdapterPosition)
if(direction == ItemTouchHelper.LEFT) {
// CANCEL
//val manager = requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
//manager.cancel(currentPowiadomienie.notificationId!!)
val alarmManager = requireActivity().getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(requireContext(), Notification::class.java)
intent.action = "cancel"
val pendingIntent = PendingIntent.getService(requireContext(), currentNotification.notificationId!!.toInt(), intent, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE)
pendingIntent?.let { _pendingIntent ->
alarmManager.cancel(_pendingIntent)
}
Neither manager.cancel() nor alarmManager.cancel() works.
The notification creates but how to cancel it?!
I think you need to call notifydatasetchanged() method after the alarmManager.cancel() like this:
val currentNotification: SetNotification =
listAdapter.getNotificationByPosition(viewHolder.bindingAdapterPosition)
if(direction == ItemTouchHelper.LEFT) {
val alarmManager =
requireActivity().getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(requireContext(), Notification::class.java)
intent.action = "cancel"
val pendingIntent = PendingIntent.getService(requireContext(),
currentNotification.notificationId!!.toInt(), intent,
PendingIntent.FLAG_CANCEL_CURRENT or
PendingIntent.FLAG_IMMUTABLE)
pendingIntent?.let { _pendingIntent ->
alarmManager.cancel(_pendingIntent)
notifyDataSetChanged()
}
I've solved my issue for not canceling coming notifications:
I think I was passing the wrong context. Check if you're passing the right one
To cancel a notification:
private fun removeAlarm(id: Int){
val alarmManager = activity?.getSystemService(Context.ALARM_SERVICE) as AlarmManager
// Notification = BroadcastReceiver class
val intent = Intent(requireContext(), Notification::class.java)
val pendingIntent = PendingIntent.getBroadcast(requireContext(), id, intent, PendingIntent.FLAG_IMMUTABLE)
alarmManager.cancel(pendingIntent)
}

Android Alarm Manager doesn't fire alarm when application is closed

guys, am currently building an application with compose, and i need to fire a notification at a given time to users, so whenever users click on a button on my list item, i want to get the time of that item and set an alarm pending intent for it, and fire it when the times reaches,
So am working with android alarm manager, but whenever i close my application the alarm doesn't broadcast my notification, but if i leave it open, it fires the alarm notification successfully, i dont know what i am doing wrong cause from what i know my application isnt responsible for firing the alarm, cause the android system does this for us,
so here is my code that i have tried with Alarm Manager
Funtion to Schedule Notification
`
#RequiresApi(api = Build.VERSION_CODES.M)
fun scheduleNotification(calendar: Calendar, context: Context, taskInfo:FixtureAlarm) {
val alarmManager = context.getSystemService(ALARM_SERVICE) as AlarmManager
// adding intent and pending intent to go to AlarmReceiver Class in future
val intent = Intent(context, FixtureNotificationReceiver::class.java)
intent.putExtra("fixture", taskInfo)
val pendingIntent = PendingIntent.getBroadcast(context, taskInfo.id, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
// when using setAlarmClock() it displays a notification until alarm rings and when pressed it takes us to mainActivity
val mainActivityIntent = Intent(context, MainActivity::class.java)
val basicPendingIntent = PendingIntent.getActivity(context, taskInfo.id, mainActivityIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
// creating clockInfo instance
val clockInfo = AlarmManager.AlarmClockInfo(Calendar.getInstance().also { it.add(Calendar.SECOND,10) }.timeInMillis, basicPendingIntent)
// setting the alarm
alarmManager.setExact(AlarmManager.RTC_WAKEUP,Calendar.getInstance().also { it.add(Calendar.SECOND,10) }.timeInMillis, pendingIntent)
Toast.makeText(context, "Scheduled ", Toast.LENGTH_LONG).show()
}
`
My BroadCast Receiver
class FixtureNotificationReceiver() : BroadcastReceiver() {
private var notificationManager: NotificationManagerCompat? = null
override fun onReceive(p0: Context?, p1: Intent?) {
val taskInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
p1?.getParcelableExtra("fixture",FixtureAlarm::class.java)
} else {
p1?.getParcelableExtra("fixture") as? FixtureAlarm
}
Log.d("SVTRECIEVED","ALARTM RECIEVED ${taskInfo?.homeTeam.toString()}")
// tapResultIntent gets executed when user taps the notification
if(taskInfo!=null) {
val tapResultIntent = Intent(p0, MainActivity::class.java)
tapResultIntent.putExtra("fixture", taskInfo)
tapResultIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
val pendingIntent: PendingIntent =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getActivity(p0, 0, tapResultIntent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
} else {
getActivity(p0, 0, tapResultIntent, PendingIntent.FLAG_UPDATE_CURRENT)
}
val notification = p0?.let {
NotificationCompat.Builder(it, FIXTURESCHANNEL_ID)
.setContentTitle("Fixture Reminder")
.setContentText("${taskInfo?.awayTeam} VS ${taskInfo.homeTeam} is about to start, open SportVectru and get live updates ")
.setSmallIcon(R.mipmap.ic_launcher)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setDefaults(NotificationCompat.DEFAULT_SOUND)
.setDefaults(NotificationCompat.DEFAULT_VIBRATE)
.setPriority(NotificationCompat.PRIORITY_HIGH).build()}
val notificationtone = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM)
val r = RingtoneManager.getRingtone(p0, notificationtone)
r.play()
notificationManager = p0?.let { NotificationManagerCompat.from(it) }
notification?.let { taskInfo.let { it1 -> if (ActivityCompat.checkSelfPermission(
p0,
Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
Toast.makeText(p0, "Permission for showing notification is disabled", Toast.LENGTH_SHORT).show()
return
}
notificationManager?.notify(it1.id, it)
}
}
}
}
}
Added it to my Manifest
`
<receiver android:name=".dormain.notifications.pendingNotification.FixtureNotificationReceiver"
android:enabled="true"
/>
`
I even granted Permission
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
<uses-permission android:name="com.android.alarm.permission.SET_ALARM"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
But it still doesnt work, notification is never shown when application is closed.
I have also disabled battery Optimization for my application, doesnt work.

Android - AlarmManager not firing BroadcastReceiver to show Local Notifications

I am writing a prayer application which requires the application to show Local Notifications on PrayerTimes. Prayer times and different for each day, thus I am using the following bit of code to show a Location Notification from BroadcastReceiver and right after that schedule next notification.
The problem is, the application is required to open at least once a day for the notifications to keep firing on their specific timings.
Is there a way to schedule BroadcastReceiver using Alarm Manager to fire Local Notifications without opening the app?
fun MakkahPrayer.setNotificationForPrayer(prayer: Prayer, date: Date) {
val app = App.instance!!.applicationContext
val preferences = PreferenceManager.getInstance(app)
if(!preferences.isPrayerAlarmSet(prayer.name)) {
val calendar = Calendar.getInstance()
calendar.add(Calendar.DAY_OF_YEAR, 0)
val dayOfYear = calendar[Calendar.DAY_OF_YEAR]
NotificationUtils.instance.setNotification(date.time, prayer.name, dayOfYear.toString())
preferences.setPrayerIsAlarmOn(prayer.name, true)
}
}
NotificationUtils.kt
class NotificationUtils {
companion object {
val instance = NotificationUtils()
}
fun setNotification(timeInMilliSeconds: Long, name: String, day: String) {
val cal = Calendar.getInstance()
cal.time = Date()
val millis = cal.timeInMillis
if (timeInMilliSeconds > 0 && timeInMilliSeconds > millis) {
val key = name + day
val alarmManager =
App.instance?.getSystemService(Activity.ALARM_SERVICE) as AlarmManager
val alarmIntent = Intent(App.instance?.applicationContext, AlarmReceiver::class.java)
alarmIntent.putExtra("prayer", name)
alarmIntent.putExtra("timestamp", timeInMilliSeconds)
alarmIntent.putExtra("notificationID", key)
val calendar = Calendar.getInstance()
calendar.timeInMillis = timeInMilliSeconds
val pendingIntent = PendingIntent.getBroadcast(
App.instance,
timeInMilliSeconds.toInt(),
alarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeInMilliSeconds, pendingIntent)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeInMilliSeconds, pendingIntent)
} else {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeInMilliSeconds, pendingIntent)
}
}
}
}
AlarmReceiver.kt
class AlarmReceiver : BroadcastReceiver() {
companion object {
private lateinit var mNotification: Notification
const val CHANNEL_ID = "CHANNEL_ID"
const val CHANNEL_NAME = "Prayer Notification"
}
override fun onReceive(context: Context, intent: Intent) {
val manager = createChannel(context)
showNotification(context, intent, manager)
setNextPrayerAlarm(intent)
}
private fun setNextPrayerAlarm(intent: Intent) {
if (intent.extras != null) {
val prayerName = intent.extras!!.getString("prayer", "Prayer")
val prayer = Prayer.valueOf(prayerName)
MakkahPrayer.instance.removePrayerNotification(prayer)
}
val (nextPrayer, date) = MakkahPrayer.instance.nextPrayerWithTime()
MakkahPrayer.instance.setNotificationForPrayer(nextPrayer, date)
}
private fun showNotification(
context: Context,
intent: Intent,
notificationManager: NotificationManager
) {
var timestamp: Long = 0
var prayerName = "Prayer"
var mNotificationId = ""
if (intent.extras != null) {
timestamp = intent.extras!!.getLong("timestamp")
prayerName = intent.extras!!.getString("prayer", "Prayer")
mNotificationId = intent.extras!!.getString("notificationID", "")
}
if (timestamp > 0) {
val notifyIntent = Intent(context, MainActivity::class.java)
val title = capitalize(prayerName)
val message = "It is $title time"
notifyIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
val calendar = Calendar.getInstance()
calendar.timeInMillis = timestamp
val pendingIntent = PendingIntent.getActivity(
context,
0,
notifyIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
val uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
mNotification = NotificationCompat.Builder(context, NotificationService.CHANNEL_ID)
.setContentIntent(pendingIntent)
.setSmallIcon(R.drawable.ic_alarm_black_24dp)
.setLargeIcon(
BitmapFactory.decodeResource(
context.resources,
R.mipmap.ic_launcher
)
)
.setSound(uri)
.setAutoCancel(true)
.setContentTitle(title)
.setStyle(
NotificationCompat.BigTextStyle()
.bigText(message)
)
.setColor(ContextCompat.getColor(context, R.color.colorSecondary))
.setContentText(message).build()
notificationManager.notify(timestamp.toInt(), mNotification)
}
}
#SuppressLint("NewApi")
private fun createChannel(context: Context): NotificationManager {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val soundUri =
Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + App.instance?.applicationContext?.packageName + "/" + R.raw.azan)
val audioAttributes = AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.build()
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val importance = NotificationManager.IMPORTANCE_HIGH
val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, importance)
channel.enableVibration(true)
channel.setShowBadge(true)
channel.canShowBadge()
channel.enableLights(true)
channel.lightColor = context.getColor(R.color.colorSecondary)
channel.description =
context.getString(R.string.notification_channel_description)
channel.setSound(soundUri, audioAttributes)
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
notificationManager.createNotificationChannel(channel)
return notificationManager
} else {
return context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
}
}
Edit:
After using the following methods, as described by people below, it still is not working, i.e app must be open at least one time in 24 hours, for it to produce local notifications.
I am looking for a solution, where the app should not have to be open for leats say 4,5 days and the app should deliver local notifications.
For now, it works for only 24 hours, when the next day comes, notifications stop firing, requiring the app to be open for at least once a day.
You can create a PrayerWorker using Androidx Work Manager to schedule a background API/setting of notifications (all without using opening app, and instead being trigered when notification is received.
Documentation can be found here
Your setNextPrayerAlarm function will instead have the logic moved to the PrayerWorker and look something like this :
private fun setNextPrayerAlarm(intent: Intent) {
if (intent.extras != null) {
val oneTimeWorkRequestBuilder = OneTimeWorkRequest.Builder(PrayerWorker::class.java)
oneTimeWorkRequestBuilder.setInputData(`put your input data here`)
WorkManager.getInstance(context).enqueueUniqueWork("setPrayerWorker",ExistingWorkPolicy.REPLACE, oneTimeWorkRequestBuilder.build())
}
}
and the PrayerWorker may look something like this
class PrayerWorker(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
override fun doWork(): Result {
//Insert logic to determine alarms to set
return Result.success() //for success case
}
}
EDIT 1 :
Hi, i should have been clearer in the method, sorry. There's two ways you can make this a repeating alarm.
Method 1:
Modify the OneTimeWorkRequest to a PeriodicWorkRequest(refer to documentation here). Using this method, you can specify how you want the worker that sets to repeat (e.g. every 2 hours, every 24 hours). The min interval is 15 mins.
Method 2:
Modify PrayerWorker to also schedule the next worker. This will utilise the fact that you can add a delay to the triggering of the worker(refer to documentation), which in this case will be 24 hours. Below is the example
class PrayerWorker(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
override fun doWork(): Result {
//Insert logic to determine alarms to set
val oneTimeWorkRequestBuilder = OneTimeWorkRequest.Builder(PrayerWorker::class.java)
oneTimeWorkRequestBuilder.setInputData(`put your input data here`)
oneTimeWorkRequestBuilder.setInitialDelay(`initialDelay`, `timeUnit`)
WorkManager.getInstance(context).enqueueUniqueWork("setPrayerWorker",ExistingWorkPolicy.REPLACE, oneTimeWorkRequestBuilder.build())
return Result.success() //for success case
}
}
Try following steps
1. In NotificationUtils.kt add an intent Flag FLAG_RECEIVER_FOREGROUND
as like below which will do the trick for you
val alarmIntent = Intent(App.instance?.applicationContext, AlarmReceiver::class.java)
alarmIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
alarmIntent.putExtra("prayer", name)
....
...
2. Also make sure you have registered AlarmReceiver in Manifest
like below
<receiver android:name="com.myapp.receiver.AlarmReceiver">
</receiver>
I don't know which android Sdk level your app is targeting, but Google has changed it's APIs starting from O. Declaring implicit Broadcast receiver from manifest will not work.
As part of the Android 8.0 (API level 26) Background Execution Limits, apps that target the API level 26 or higher can no longer register broadcast receivers for implicit broadcasts in their manifest. However, several broadcasts are currently exempted from these limitations. Apps can continue to register listeners for the following broadcasts, no matter what API level the apps target.
more on that here: https://developer.android.com/guide/components/broadcast-exceptions

Android Kotlin Foregeground Service + Notifications, Why works wrong?

In my application, I need a foreground service that will check a certain condition every minute, and if it is correct, it triggers a notification reminder. The user determines in advance what time and day he would like to have the reminder to. Data is saved in the database. Then the service every minute checks if it has a reminder for a given hour and day and if so sends a notification. The service must work when the user uses the application, when the application runs in the background and when it is closed. Could someone tell me why this code works on one phone but not on others? So-called, if I set a reminder, up to 20 minutes, it works (in all 3 states that I wrote about earlier), but once I set the reminder for the next days, it doesn't work anymore. I am surprised that sometimes the reminder on another phone works and sometimes it doesn't. I checked, the permission for the application to run in the background is selected in the settings. Please help.
Manifest
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:allowBackup="false"
android:icon="#drawable/pills"
android:label="#string/nameOfApplications"
android:roundIcon="#drawable/icon"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<service
android:name=".ForegroundService"
android:enabled="true"
android:exported="true"/>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
ForegroundService
class ForegroundService : Service() {
companion object {
val CHANNEL_ID = "ForegroundServiceChannel"
val CHANNEL_ID_CHILD = "ForegroundServiceChannelCHILD"
private var isRunning = false
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val input = intent.getIntExtra("time",15)
createNotificationChannel()
val notificationIntent = Intent(this, Menu::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
0, notificationIntent, 0
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("MotherNotification")
.setContentText("Message")
.setOnlyAlertOnce(true)
.build()
startForeground(1, notification)
isRunning = true
val context = this
val intent = Intent(this, ShowAll::class.java)
val pendingIntentNotification = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
doAsync {
while(isRunning)
{
var message : String = createReminderMessage(context)
//SystemClock.sleep(input * 10_000L)
SystemClock.sleep(50000)
uiThread {
if(isRunning && (message != "Nadszedł czas by zażyć: ")) {
val notification = NotificationCompat.Builder(context, CHANNEL_ID_CHILD)
.setContentTitle("Title")
.setContentText(message)
.setContentIntent(pendingIntentNotification)
.setAutoCancel(true)
.build()
with(NotificationManagerCompat.from(context)) {
notificationManager.notify(2, notification)
}
}
}
}
}
return START_NOT_STICKY
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onDestroy() {
super.onDestroy()
isRunning = false
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(
CHANNEL_ID,
"Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
)
val serviceChannel2 = NotificationChannel(
CHANNEL_ID_CHILD,
"Foreground Service ChannelChild ",
NotificationManager.IMPORTANCE_DEFAULT
//NotificationManager.IMPORTANCE_LOW
)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(serviceChannel)
manager.createNotificationChannel(serviceChannel2)
}
}
fun reminderForNow(context: Context) : ArrayList<Reminder> {
var listOfReminder : ArrayList<Reminder> = ArrayList()
var timetoday = takeTimeNow()
var dateToday = takeTodayDate()
val dbHelper = SQLConector(context)
val allRemindersList = dbHelper.getAllReminders()
for (i: Reminder in allRemindersList) {
if (i.reminderDate == dateToday && i.ReminderTime == timetoday) {
var reminder = Reminder(
i.id,
i.Name,
i.reminderDate,
i.ReminderTime
)
listOfReminder.add(reminder)
}
}
return listOfReminder
}
private fun createReminderMessage(p0: Context) : String{
var message : String = "title : "
var listOfReminders = reminderForNow(p0)
if(listOfReminders.count() > 0){
for (i: Reminder in listOfReminders) {
message += i.Name + ", "
}
}
return message
}
private fun takeTodayDate():String{
val current = LocalDateTime.now()
val formatDate = DateTimeFormatter.ofPattern("yyyy-MM-dd")
var dateResult = current.format(formatDate).toString()
return dateResult
}
private fun takeTimeNow() : String{
val current = LocalDateTime.now()
val formatTime = DateTimeFormatter.ofPattern("HH:mm")
var timeResult = current.format(formatTime).toString()
return timeResult
}
}
Main Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
binding.buttonStart.setOnClickListener { startService() }
binding.buttonStop.setOnClickListener {stopService() }
startService()
}
private fun startService() {
val serviceIntent = Intent(this, ForegroundService::class.java)
serviceIntent.putExtra("time", 1)
ContextCompat.startForegroundService(this, serviceIntent)
}
private fun stopService() {
val serviceIntent = Intent(this, ForegroundService::class.java)
stopService(serviceIntent)
}
}
The correct way to handle tasks which require exact fire time is to use the AlarmManagerCompat class.
You can use setExactAndAllowWhileIdle(...) method to force the alarm to start your service even when the device is in Doze mode and you will need a BroadcastReceiver to re-schedule the alarms if the device is rebooted.
You can find some references online on how to implement that.

How to create a time triggered Notification that works on all Android Devices

I'm trying to make a notification that get triggered after a certain amount of time has passed. In an attempt to achieve this I have this AlarmReceiver class that inherits from BroadCast receiver and works on devices running up to about API 23. It doesn't work on my emulator currently running API 27. Any clue what I'm doing wrong?
class AlarmReceiver : BroadcastReceiver() {
companion object {
val PRIMARY_CHANNEL = "dj"
}
override fun onReceive(context: Context, intent: Intent) {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notificationIntent = Intent(context, NotificationActivity::class.java)
val stackBuilder = TaskStackBuilder.create(context)
stackBuilder.addParentStack(NotificationActivity::class.java)
stackBuilder.addNextIntent(notificationIntent)
val pendingIntent = stackBuilder.getPendingIntent(100, PendingIntent.FLAG_UPDATE_CURRENT)
if (android.os.Build.VERSION_CODES.O <= android.os.Build.VERSION.SDK_INT){
//I create the notification channel on the next line, but it doesn't seem to work
val notificationChannel = NotificationChannel(PRIMARY_CHANNEL,
"DailyJokes", NotificationManager.IMPORTANCE_DEFAULT)
notificationChannel.lightColor = Color.GREEN
notificationChannel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
notificationManager.createNotificationChannel(notificationChannel)
val notification = Notification.Builder(context, PRIMARY_CHANNEL)
.setContentIntent(pendingIntent)
.setChannelId("dj")
.setContentText("KDW")
.setSmallIcon(R.mipmap.ic_launcher_round)
.setContentTitle("CCC")
.build()
notificationManager.notify(1,notification)
} else {
var builder = NotificationCompat.Builder(context, "dj")
val sound = Uri.parse("android.resource://" + context.packageName + "/" + "raw/drumroll")
builder = builder
.setSmallIcon(R.mipmap.ic_launcher_round)
.setColor(Color.BLUE)
.setContentTitle("Content Title")
.setTicker("TICKER Text")
.setContentText("KDW setContentText")
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.setChannelId("dj")
.setSound(sound)
notificationManager.notify(1, builder!!.build())
}
}
}
Here's the code that triggers the notification, anything off here?
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_notification)
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val notificationIntent = Intent("android.media.action.DISPLAY_NOTIFICATION")
notificationIntent.addCategory("android.intent.category.DEFAULT")
val broadcast = getBroadcast(this, 100, notificationIntent, FLAG_UPDATE_CURRENT)
val cal = Calendar.getInstance()
cal.add(Calendar.SECOND, 5)
alarmManager.set(AlarmManager.RTC_WAKEUP, cal.timeInMillis, broadcast)
}
}
I also have a receiver in my Manifest:
<receiver android:name=".AlarmReceiver">
<intent-filter>
<action android:name="android.media.action.DISPLAY_NOTIFICATION" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
Even with all this the bug still lurks. I'm new to Kotlin, today makes a week since I started. My swift experience prepared me well but I just can't seem to get a hold on notifications :/
From what I can tell from your code , you need to register the receiver in either the Manifest or a context.
https://developer.android.com/guide/components/broadcasts.html
is your channel even created ?
your comparison is in the wrong direction ->
android.os.Build.VERSION_CODES.O <= android.os.Build.VERSION.SDK_INT it should be >= instead
and only the channel creation needs to be guarded against O, not the notification creation.

Categories

Resources