How to schedule recurring notifications in kotlin? - android

I've been looking for this for a couple of hours but I haven't found anything helpful. Right now, I have an activity (called other_recurringreminder) that sets a time (fomartted calendar, HH:mm; string), repetition frequency (int), repetition unit (like minutes, hours, days; string), whether it's on or off (bool), and a name (string)
In other_recurringreminder.kt, I have this function:
fun sendnotification()
{
val channelID = "channel1"
val notificationID = 1
val sharedPreferences: SharedPreferences = getSharedPreferences("sharedPrefs", Context.MODE_PRIVATE)
val builder = NotificationCompat.Builder(this, "channelID")
.setSmallIcon(R.drawable.centr)
.setColor(resources.getColor(R.color.purple))
.setContentTitle(sharedPreferences.getString("Alarm1Name_USERPREFS", "Reminder 1"))
.setContentText(getString(R.string.notif_recurringreminders))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setAutoCancel(true)
with(NotificationManagerCompat.from(this)) {
notify(notificationID, builder.build())
}
}
Which sends the notification when called.
How can I make it so that my app sends this notification at a time from SharedPreferences, with a title from SP, and repeats every x units from SP?
Should this function be in another kotlin file?
Can this work even after a restart, when my app hasn't yet been opened?If I want to schedule more than one notif with different values, do I need to duplicate anything?
Sorry if it's a dumb question, I'm fairly new to kotlin. and thanks!

you can use alarm manager for it.
first create a class with broadcast receiver like
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
Log.d("this", "notify")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val intent = Intent(context, AlarmActivity2::class.java)
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
val builder = NotificationCompat.Builder(context, "111")
.setSmallIcon(R.drawable.blue_stringart)
.setContentTitle("Alarm is running")
.setAutoCancel(true)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setDefaults(NotificationCompat.DEFAULT_SOUND)
.setDefaults(NotificationCompat.DEFAULT_VIBRATE)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.addAction(R.drawable.ic_baseline_stop_24,"Stop",pendingIntent)
.setContentIntent(pendingIntent)
val notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM)
val r = RingtoneManager.getRingtone(context, notification)
r.play()
val notificationManagerCompat = NotificationManagerCompat.from(context)
notificationManagerCompat.notify(123, builder.build())
}
}
}
after that go to your activity class make 2 method and call in oncreate
private fun setAlarm1() {
var calender: Calendar
calender = Calendar.getInstance()
calender.set(Calendar.HOUR_OF_DAY, PUT_YOUR_ALARM HOUR)
calender.set(Calendar.MINUTE, PUT_YOUR_ALARM MINUTE)
calender.set(Calendar.SECOND, 0)
alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val thuReq: Long = Calendar.getInstance().timeInMillis + 1
var reqReqCode = thuReq.toInt()
if (calender.timeInMillis < System.currentTimeMillis()) {
calender.add(Calendar.DAY_OF_YEAR, 1)
}
val alarmTimeMilsec = calender.timeInMillis
val intent = Intent(this, AlarmReceiver::class.java)
intent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
val pendingIntent = PendingIntent.getBroadcast(this, reqReqCode, intent, 0)
alarmManager.setRepeating(
AlarmManager.RTC_WAKEUP,
calender.timeInMillis,
HERE_PUT_YOUR_HOUR * 60 * 60 * 1000,
pendingIntent
)
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = "Alarmclock Channel"
val description = " Reminder Alarm manager"
val importance = NotificationManager.IMPORTANCE_HIGH
val notificationChannel = NotificationChannel(CHANNELID, name, importance)
notificationChannel.description = description
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(notificationChannel)
}
}
Note - Must to do(go to your app setting and give notification permission on) 1.alarmManager.setRepeating here you can use your alarm type as your wish. 2.requestcode must be unique for each alarm. 3. you must take a alarm time and keep in calender.timeInMillis which is you expecting alarm time.
still problem comments below

Related

Repeating notifications issue

I'm struggling with notifications.
I want to randomly select text to display in the notification (using random() method on the array of String objects from separate object file NotificationData where I only had one immutable variable called notificationData), which should appear once a day at a specific time (but for now, for the purpose of the test, I wrote in the code to appear every hour). I'm using AlarmManager for scheduling. The first thing is the notifications does not appear at the specified hour when the app is not currently working (or during the device sleep mode), but after launching the app. And second thing is that after launching the app, notifications appear almost one by one within few seconds. I don't really understand why it happens in this way. It's quite strange for me.
Here's the NotificationUtils class, where I create my notification and put one String from Array inside setContentText() method:
package com.example.quit.notification
class NotificationUtils(context: Context) {
private var mContext = context
private lateinit var notificationBuilder: NotificationCompat.Builder
val notificationManager = NotificationManagerCompat.from(mContext)
private val CHANNEL_ID = "Notification_Channel"
init {
createNotificationChannel()
initNotificationBuilder()
}
fun launchNotification() {
with(NotificationManagerCompat.from(mContext)) {
notificationManager.notify(0, notificationBuilder.build())
}
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = "Rady"
val description = "Codzienne rady dla zdrowiejących osób uzależnionych"
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
this.description = description
}
val notificationManager: NotificationManager = mContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
private fun initNotificationBuilder() {
val sampleIntent = Intent(mContext, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(mContext, 0, sampleIntent, 0)
val data = NotificationData.notificationData.random()
notificationBuilder = NotificationCompat.Builder(mContext, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_support)
.setContentTitle("Rada na dziś")
.setContentText(data)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
}
}
Second class, where I create repeating alarm:
package com.example.quit.notification
#SuppressLint("UnspecifiedImmutableFlag")
class AlarmUtils(context: Context) {
private var mContext = context
private var alarmManager: AlarmManager? = null
private var alarmIntent: PendingIntent
init {
alarmManager = mContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmIntent = Intent(mContext, AlarmReceiver::class.java).let { mIntent ->
PendingIntent.getBroadcast(mContext, 100, mIntent, PendingIntent.FLAG_UPDATE_CURRENT)
}
}
fun initRepeatingAlarm(calendar: Calendar) {
calendar.apply {
set(Calendar.HOUR_OF_DAY, 20)
set(Calendar.MINUTE, 30)
set(Calendar.SECOND, 0)
}
alarmManager?.set(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
alarmIntent
)
}
}
Here's my AlarmReceiver, where I set next alarm after one hour in this case (:
package com.example.quit.notification
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val notificationUtils = NotificationUtils(context!!)
notificationUtils.launchNotification()
val calendar = Calendar.getInstance()
calendar.add(Calendar.HOUR_OF_DAY, 1)
val alarmUtils = AlarmUtils(context)
alarmUtils.initRepeatingAlarm(calendar)
}
}
In AndroidManifest.xml file I only added one line in tag:
<receiver android:name=".notification.AlarmReceiver" />
And the last thing - in MainActivity class I put those three lines just after setContentView() method:
val calendar = Calendar.getInstance()
val alarmUtils = AlarmUtils(this)
alarmUtils.initRepeatingAlarm(calendar)
I'll be very grateful for any help and explanations.
It is obvious why your notifications appear immediately. In AlarmReceiver you do this:
val calendar = Calendar.getInstance()
calendar.add(Calendar.HOUR_OF_DAY, 1)
val alarmUtils = AlarmUtils(context)
alarmUtils.initRepeatingAlarm(calendar)
You want to take the current time, add one hour and schedule an alarm at that time (1 hour in the future). However, look at what initRepeatingAlarm() is doing:
calendar.apply {
set(Calendar.HOUR_OF_DAY, 20)
set(Calendar.MINUTE, 30)
set(Calendar.SECOND, 0)
}
in initRepeatingAlarm() you are setting the hour, minute and seconds to a fixed value (which is the time that the alarm originally went off). You are overwriting the time that was set in the Calendar that was passed in. In this case, when the alarm is set, the time is in the past so the alarm triggers immediately.
Regarding why the alarms do not work when the device is sleeping, the call to AlarmManager.set() is not considered "exact" and the system may delay the alarm to ensure that the device is not constantly awakened (preservation of battery). See this explanation found in the documentation.
If you want the alarm to go off at exactly this time, you need to use a different method, such as setExact(), and you will need to declare an additional permission SCHEDULE_EXACT_ALARM (if you target API level 31 or higher)

How to create multiple notifications in Kotlin in foreground service

I am working on a parental control app which notify parent multiple times but when I try to create notification with a background service it generates only one 1.
Here is how I do it:
fun createNotification(parent_name: String, notificationText:String, id: Int){
val MchannelId = channelId+id.toString()
if (Build.VERSION.SDK_INT >= 26) {
val channel = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel(
MchannelId,
"Channel human readable title",
NotificationManager.IMPORTANCE_DEFAULT
)
} else {
TODO("VERSION.SDK_INT < O")
}
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager).createNotificationChannel(
channel
)
}
val notificationIntent = Intent(this, TabbedActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
id, notificationIntent, 0
)
val notification: Notification = NotificationCompat.Builder(this, "$MchannelId")
.setContentTitle("Hi $parent_name")
.setContentText(notificationText)
.setSmallIcon(R.drawable.icon_child)
//.setContentIntent(pendingIntent)
.build()
startForeground(random_number, notification)
}
My Full-Service Class:
const val TAG2 = "Child Service"
class ParentService: Service() {
val db = FirebaseFirestore.getInstance()
private val channelId = "Notification from Service"
var parent_name = userName
override fun onBind(intent: Intent?): IBinder? = null
//OnBind Function Implementation
init {
Log.d(TAG2, "Started Service!")
}
//onCreate Method Implementation
override fun onCreate() {
super.onCreate()
}
//OnStartCommand Override
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Thread{
while (true){
checkStatus()
Thread.sleep(PARENT_CHECK_TIME)
}
}.start()
return START_STICKY
}
private fun checkStatus() {
var listOfNames = ""
var i = 1
val calendar: Calendar = Calendar.getInstance()
var list = ArrayList<String>()
db.collection(LINKED_CHILDS)
.whereEqualTo(USER_PHONE, userPhone)
.get()
.addOnSuccessListener { documents ->
for (document in documents){
val startTime: Long = calendar.getTimeInMillis()
val diff = startTime - (document.data[ACTIVE_STATUS] as Long)
Log.d("TAG", "Time Difference : $diff")
Log.d("TAG", "${document.data[USER_NAME].toString()}")
if (diff> MAX_GAP_TIME){
Log.d("TAG", "Entered IFF")
list.add(document.data[USER_NAME].toString())
}
}
for (name in list){
listOfNames = listOfNames + "$i. Your child $name is not active\n"
i++
createNotification(parent_name, listOfNames, i)
Log.d("TAG Notification ID:", "ID: $i")
}
Log.d("TAG: ", "$listOfNames")
}
}
fun createNotification(parent_name: String, notificationText:String, id: Int){
val MchannelId = channelId+id.toString()
if (Build.VERSION.SDK_INT >= 26) {
val channel = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel(
MchannelId,
"Channel human readable title",
NotificationManager.IMPORTANCE_DEFAULT
)
} else {
TODO("VERSION.SDK_INT < O")
}
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager).createNotificationChannel(
channel
)
}
val notificationIntent = Intent(this, TabbedActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
id, notificationIntent, 0
)
val notification: Notification = NotificationCompat.Builder(this, "$MchannelId")
.setContentTitle("Hi $parent_name")
.setContentText(notificationText)
.setSmallIcon(R.drawable.icon_child)
//.setContentIntent(pendingIntent)
.build()
startForeground(id, notification)
}
}
Kinldy let me know how I can create multiple Notifications using this background service. Thank You so much in advance!
Kinldy let me know how I can create multiple Notifications using this background service. Thank You so much in advance!
Kinldy let me know how I can create multiple Notifications using this background service. Thank You so much in advance!
If you create a non-persistent notification, it will show your notifications. The permanent notification will be used for your service to run in the background.
#RequiresApi(Build.VERSION_CODES.O)
private fun createNotification() {
val intent = Intent(this, TabbedActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent =
PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)
val notification = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.icon_child)
.setContentTitle("Hi $parent_name")
.setContentText(notificationText)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent)
with(NotificationManagerCompat.from(this)) {
notify(notifManagerId, notification.build())
notifManagerId++
}
parmanentNotification()
}
this is a permanent notification will not be lost and destroyed will keep the service running permanently
private fun parmanentNotification() {
val notification=NotificationCompat.Builder(this,channelId)
.setSmallIcon(R.drawable.icon_child)
.setContentTitle("Hi $parent_name")
.setContentText("Application service running in the background")
.build()
startForeground(1,notification)
}
you aren't creating a common Notification in this scenario, you are running a Service, which must have a foreground representation on screen. So Activity visible or sticked, fixed Notification, and you are showing it
Now you can have much Notifications using similar code, but don't show them using startForeground, instead use NotificationManager, preferably compat version
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.notify(uniqueId, notification);
or just like you are using it already when creating channel inside if: (getSystemService(NOTIFICATION_SERVICE) as NotificationManager).notify(...)
foreground-related Notification is sticky and lives as long as Service works in background, they are "tied". other Notifications may be configured to be sticky or swipeable, also should be posted on some own Channel (per child? per action?). Note that if you show yet another sticky Notification then you have to release it by own through code, just killing Service won't dismiss it as it does with foreground-related Notification
some DOC in here, read carefully, all answers are there

Testing Intent that will be triggered by an alarm

I'm trying to set unit test for my android app. I have to test a notifications services that looks like that :
This class provide function to set notification in a context
class DeadlineNotification {
companion object {
private lateinit var alarmManager: AlarmManager
private lateinit var pendingIntent: PendingIntent
/*
Create a notification channel for reminder notifications
Creating an existing notification channel with its original values performs no operation,
so it's safe to call this code when starting an app.
*/
fun createNotificationChannel(context: Context){
val channelName :CharSequence = "reminders channel"
val description = "channel for reminders notifications"
val channel = NotificationChannel("remindersChannel", channelName, NotificationManager.IMPORTANCE_DEFAULT)
val notificationManager = context.getSystemService(NotificationManager::class.java)
channel.description=description
notificationManager.createNotificationChannel(channel)
}
/*
Set a notification that will be triggered in a given time in ms.
you can pass a title/description and Id in parameter
*/
fun setNotification(timeMS: Long, title: String, description: String, id: Int, context: Context){
alarmManager = context.getSystemService(AppCompatActivity.ALARM_SERVICE) as AlarmManager //this get an service instance of AlarmManager
val intent = Intent(context, ReminderBroadcastReceiver::class.java) //this create an intent of broadcast receiver
//Adding extra parameter that will be used in the broadcase receiver to create the notification
intent.putExtra("title", title)
intent.putExtra("description", description)
intent.putExtra("id", id)
//set the receiver as pending intent
pendingIntent = PendingIntent.getBroadcast(context, id, intent, PendingIntent.FLAG_IMMUTABLE)
//set an alarm that will wake up the pending intent (receiver)
alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeMS, pendingIntent)
}
fun getAlarmManager():AlarmManager = this.alarmManager
}
}
setNotification will set an Alarm that will launch an intent of ReminderBroadcastReceiver :
//this class is a BroadcastReceiver that will send a notification when it is triggered
class ReminderBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
var channelId = "remindersChannel"
//retrieving some parameters for the notification
var title = intent!!.getStringExtra("title")
var content = intent!!.getStringExtra("description")
var notifId = intent!!.getIntExtra("id", 0)
val intent2 = Intent(context, MainActivity::class.java)
intent2!!.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
//this is the intent where the user will be send when clicking on the notification
val pendingIntent = PendingIntent.getActivity(context, 0, intent2, PendingIntent.FLAG_IMMUTABLE)
//Builder of the notification
val notifBuilder = NotificationCompat.Builder(context!!, channelId)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle(title + notifId.toString())
.setContentText(content)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setCategory(NotificationCompat.CATEGORY_REMINDER)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
//send the notification
val notificationManager = NotificationManagerCompat.from(context)
notificationManager.notify(notifId, notifBuilder.build())
}
}
This will create a NotificationManager and send the notification.
The code above is working as expected.
Now I'm trying to write unit test for it, I tried this :
#RunWith(AndroidJUnit4::class)
class DeadlineNotificationTest {
#Test
fun `notification is triggered at currentTime and redirect to MainActivity`() {
val activity: MainActivity = Robolectric.setupActivity(MainActivity::class.java) //main Activity
DeadlineNotification.setNotification(System.currentTimeMillis(), "title", "empty description", 1, activity) //this create an alarm
//here we're looking for the notificationManager that have been
val notificationService: NotificationManager = activity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val shadowNotificationManager =
shadowOf(notificationService)
assertEquals(1, shadowNotificationManager.size())
val notification: Notification = shadowNotificationManager.allNotifications[0]
val contentIntent: PendingIntent = notification.contentIntent
val nextIntent = shadowOf(contentIntent).savedIntent
val nextClassName = nextIntent.component!!.className
assertEquals(nextClassName, MainActivity::class.java.getName())
}
}
But the first assertion fails, so I assume the shadowNotificationManager cannot see the notification.
Do you have any Idea how I should proceed to have a working test?

Prevent old notifications from firing

I'm making an app that i schedule notifications for a day say morning , evening and night - the problem is if i opened the app at night the old notifications of morning and evening shows . i use below code
val alarmMgr = MyApplication.appContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager?
val intent = Intent(MyApplication.appContext, MyAlarmReceiver::class.java)
intent.putExtra("Title",title)
intent.putExtra("Body",body)
intent.putExtra("Id",id)
intent.putExtra("Sound",sound)
val pendingIntent = PendingIntent.getBroadcast(MyApplication.appContext, id, intent, 0)
alarmMgr!!.setRepeating(AlarmManager.RTC_WAKEUP,time,AlarmManager.INTERVAL_DAY,pendingIntent)
With this part inside onReceive of alarm
private fun sendNotification( intent: Intent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationID = intent.extras!!.getInt("Id")!!
val title = intent.extras!!.getString("Title")
val body = intent.extras!!.getString("Body")
val sound = intent.extras!!.getString("Sound")
val channelID = "com.ebookfrenzy.notifydemo.news"
val resultIntent = Intent(MyApplication.appContext, MainActivity::class.java)
resultIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
val pendingIntent = PendingIntent.getActivity(
MyApplication.appContext,
0,
resultIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
val notification = Notification.Builder(MyApplication.appContext,
channelID)
.setContentTitle(title)
.setContentText(body)
.setSmallIcon(android.R.drawable.ic_dialog_info)
.setChannelId(channelID)
.setContentIntent(pendingIntent)
.build()
sound?.let {
notification.sound = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + MyApplication.appContext.getPackageName() + "/raw/$it")
}
var notificationManager = MyApplication.appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager?.notify(notificationID, notification)
} else {
}
}

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

Categories

Resources