I am facing a problem when scheduling multiple (up to 14) notifications using the AlarmManager. The problem is that the notifications are randomly missing (especially when the debug cable isn’t plugged). For example the first three notifications are triggered and after these notifications none of the scheduled notifications get triggered anymore, until you reschedule the notifications. It seems it’s because of the amount of notifications, but I am not 100% sure.
Below you can find my functions for scheduling notifications (alarm and bedtime notifications);
private fun createNotificationChannel(name: String, descriptionText: String, id: String, setSound:Boolean)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel(id, name, importance).apply {
description = descriptionText
}
val audioAttributes = AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.build()
if (setSound)
{
channel.setSound(Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + m_context.packageName + "/" + m_ringtone), audioAttributes)
}
(m_context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).createNotificationChannel(channel)
}
}
private fun createBedTimeNotificationChannel()
{
createNotificationChannel(BEDTIME_NOTIFICATION_CHANNEL_NAME, BEDTIME_NOTIFICATION_CHANNEL_DESCRIPTION, BEDTIME_NOTIFICATION_CHANNEL, false)
}
private fun createWakeUpNotificationChannel()
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
for (channel in (m_context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).notificationChannels)
{
if (channel.id.contains(WAKE_UP_ALARM_NOTIFICATION_CHANNEL))
{
(m_context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).deleteNotificationChannel(channel.id)
}
}
m_randomNumber = Random().nextLong()
createNotificationChannel(WAKE_UP_NOTIFICATION_CHANNEL_NAME, WAKE_UP_NOTIFICATION_CHANNEL_DESCRIPTION, WAKE_UP_ALARM_NOTIFICATION_CHANNEL + m_randomNumber, true)
}
}
fun configureAlarmNotification(ringtone: Int, alarmTime: Int, snooze: Boolean, days: BooleanArray)
{
m_ringtone = ringtone
createWakeUpNotificationChannel()
for ((index, alarmOn) in days.withIndex())
{
if (alarmOn)
{
val builder = getWakeUpAlarmBuilder()
val intent = Intent(m_context, CMainActivity::class.java)
val activity = PendingIntent.getActivity(m_context, WAKE_UP_NOTIFICATION_ID_START + index + 1, intent, PendingIntent.FLAG_UPDATE_CURRENT)
builder.setContentIntent(activity)
if (snooze)
{
val snoozeIntent = Intent(m_context, CSleepPlannerService::class.java).apply {
putExtra(ACTION, ACTION_SNOOZE)
putExtra(NOTIFICATION_ID, WAKE_UP_NOTIFICATION_ID_START + index + 1)
putExtra(NOTIFICATION, builder.build())
}
val snoozePendingIntent: PendingIntent = PendingIntent.getBroadcast(m_context, SNOOZE_PRESSED_NOTIFICATION_ID + index + 1, snoozeIntent, 0)
builder.addAction(R.drawable.alarm_bel_icon, m_context.getString(R.string.snooze), snoozePendingIntent)
}
val notification = builder.build()
val calendar = Calendar.getInstance()
val currentTimeInMillis = calendar.timeInMillis
calendar[Calendar.DAY_OF_WEEK] = convertWeekday(index)
calendar[Calendar.HOUR_OF_DAY] = alarmTime / 60
calendar[Calendar.MINUTE] = alarmTime % 60
calendar[Calendar.SECOND] = 0
if (calendar.timeInMillis <= currentTimeInMillis)
{
calendar.timeInMillis += NUMBER_OF_MILLIS_IN_WEEK
}
val pendingIntent = getPendingIntent(calendar.timeInMillis, WAKE_UP_NOTIFICATION_ID_START + index + 1, notification, ACTION_NOTIFICATION, PendingIntent.FLAG_UPDATE_CURRENT)
(m_context.getSystemService(ALARM_SERVICE) as AlarmManager).setExact(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, pendingIntent)
}
}
val calendar = Calendar.getInstance()
m_alarmWithSoundEnabledWithinADay.postValue((m_preferences.isAlarmWithSoundEnabled() && isAlarmEnabled(calendar[Calendar.HOUR_OF_DAY] * 60 + calendar[Calendar.MINUTE], m_preferences.getAlarmTime(), calendar[Calendar.DAY_OF_WEEK])))
}
fun configureBedTimeNotification(bedTime: Int, bedTimeSetting: Int, days: BooleanArray)
{
for ((index, alarmOn) in days.withIndex())
{
if (alarmOn)
{
val builder = NotificationCompat.Builder(m_context, BEDTIME_NOTIFICATION_CHANNEL).apply {
setContentTitle(m_context.getString(R.string.bedtime_notification_tile))
when (BedTimeNotificationSetting_e.fromInt(bedTimeSetting))
{
BedTimeNotificationSetting_e.AT_BED_TIME -> setContentText(m_context.getString(R.string.bedtime_notification_at_bedtime))
BedTimeNotificationSetting_e.TEN_MINUTES_BEFORE -> setContentText(m_context.getString(
R.string.bedtime_notification_description_ten
))
BedTimeNotificationSetting_e.THIRTY_MINUTES_BEFORE -> setContentText(m_context.getString(R.string.bedtime_notification_description_thirty))
BedTimeNotificationSetting_e.FORTYFIVE_MINUTES_BEFORE -> setContentText(m_context.getString(R.string.bedtime_notification_description_fortyfive))
BedTimeNotificationSetting_e.SIXTY_MINUTES_BEFORE -> setContentText(m_context.getString(R.string.bedtime_notification_description_sixty))
}
setAutoCancel(true)
setSmallIcon(R.drawable.alarm_icon)
priority = NotificationCompat.PRIORITY_HIGH
}
val intent = Intent(m_context, CMainActivity::class.java)
val activity = PendingIntent.getActivity(m_context, BEDTIME_NOTIFICATION_ID_START + index + 1, intent, PendingIntent.FLAG_UPDATE_CURRENT)
builder.setContentIntent(activity)
val notification = builder.build()
val calendar = Calendar.getInstance()
val currentTimeInMillis = calendar.timeInMillis
calendar[Calendar.DAY_OF_WEEK] = convertWeekday(index)
calendar[Calendar.HOUR_OF_DAY] = bedTime / 60
calendar[Calendar.MINUTE] = bedTime % 60
calendar[Calendar.SECOND] = 0
var newBedTime = calendar.timeInMillis - (bedTimeSetting * 60 * 1000)
if (newBedTime <= currentTimeInMillis)
{
newBedTime += NUMBER_OF_MILLIS_IN_WEEK
}
val pendingIntent = getPendingIntent(newBedTime, BEDTIME_NOTIFICATION_ID_START + index + 1, notification, ACTION_NOTIFICATION, PendingIntent.FLAG_UPDATE_CURRENT)
(m_context.getSystemService(ALARM_SERVICE) as AlarmManager).setExact(AlarmManager.RTC_WAKEUP, newBedTime, pendingIntent)
}
}
}
private fun getPendingIntent(calendarTime: Long = 0, notificationId: Int, notification: Notification? = null, action: String, flag: Int) : PendingIntent?
{
val notificationIntent = Intent(m_context, this::class.java)
notificationIntent.putExtra(ACTION, action)
notificationIntent.putExtra(NOTIFICATION_ID, notificationId)
notificationIntent.putExtra(NOTIFICATION, notification)
notificationIntent.putExtra(ALARM_TIME, calendarTime)
return PendingIntent.getBroadcast(m_context, notificationId, notificationIntent, flag)
}
What could be the cause of this?
It is difficult to know exactly what could be wrong. Depending on the version of the system, Android might decide to wait / delay / group notifications. The amount of battery left also impact the decision to show a notif.
I see that you use setPriority on the notification builder with a HIGH value. This should work for android < api 26. For newer versions, it is the setImportance in the notification channel that will be taken in account
see https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder?hl=en#setPriority(int)
In the creation of the channel, try using High importance
val importance = NotificationManager.IMPORTANCE_HIGH // edit your code like this in createNotificationChannel, you currently use DEFAULT
make sure that there is no option like battery saver or whatever in the phone you use to test.
I fixed the issue for setting the wakeup notifications by using the function setAlarmClock from AlarmManager.
Related
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
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
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
I am new to android studio. I was trying to create a notification when user click on a button embedded in recyclerView
user has to give time in Hours and Minutes only. I take user input as a string. And input is stored in SQLite database.
notifyMeUpdate function is working fine but Toast in Receiver class is not shown(I think Receiver class is not triggered).
Please ignore typo
Here is my code:
class User{
var id = 0
var time = ""
var amp = ""
var text = ""
var notifyMe = false
}
My User Class:
fun notifyMeUpdate(user: User) {
alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
var hour = 0
var min = 0
for(i in 0 until user.time.length) {
if (user.time[i] == ':'){
var str = user.time.subSequence(0, i).toString()
var minStr = user.time.subSequence(i+1, user.time.length).toString()
if(user.amp == "AM"){
hour = str.toInt()
min = minStr.toInt()
}
else{
hour = str.toInt() + 12
min = minStr.toInt()
}
}
}
val cur_cal: Calendar = GregorianCalendar()
cur_cal.timeInMillis = System.currentTimeMillis()
val cal: Calendar = GregorianCalendar()
cal.add(Calendar.DAY_OF_YEAR, cur_cal[Calendar.DAY_OF_YEAR])
cal[Calendar.HOUR_OF_DAY] = hour
cal[Calendar.MINUTE] = min
cal[Calendar.SECOND] = 0
cal[Calendar.MILLISECOND] = cur_cal[Calendar.MILLISECOND]
cal[Calendar.DATE] = cur_cal[Calendar.DATE]
cal[Calendar.MONTH] = cur_cal[Calendar.MONTH]
val intent = Intent(context, Receiever::class.java)
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
alarmManager.set(AlarmManager.RTC_WAKEUP, cal.timeInMillis, pendingIntent)
}
And here is my Receiever class
class Receiever: BroadcastReceiver(){
lateinit var notificationManager: NotificationManager
lateinit var notificationChannel: NotificationChannel
lateinit var builder: Notification.Builder
private var channelId = "package com.example.recyclerpractise"
private var description = "Text Notification"
override fun onReceive(context: Context?, intent: Intent?){
Toast.makeText(context, "Triggred", Toast.LENGTH_SHORT).show()
val intent = Intent(context, LauncherActivity::class.java)
val pintent = PendingIntent.getActivity(context, 0 , intent, PendingIntent.FLAG_UPDATE_CURRENT)
if (context != null) {
notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationChannel =
NotificationChannel(channelId, description, NotificationManager.IMPORTANCE_HIGH)
notificationChannel.enableLights(true)
notificationChannel.lightColor = Color.GREEN
notificationChannel.enableVibration(true)
notificationManager.createNotificationChannel(notificationChannel)
if (context != null) {
builder = Notification.Builder(context, channelId)
.setContentTitle("Hello Moto")
.setContentText("Test Notification")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setLargeIcon(BitmapFactory.decodeResource(context.resources, R.drawable.ic_launcher_foreground))
.setContentIntent(pintent)
}
else{
builder = Notification.Builder(context)
.setContentTitle("Hello Moto")
.setContentText("Test Notification")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setLargeIcon(BitmapFactory.decodeResource(context?.resources, R.drawable.ic_launcher_foreground))
.setContentIntent(pintent)
}
notificationManager.notify(19867, builder.build())
}
}
}
Here is my onBindViewHolder of recycler list
override fun onBindViewHolder(p0: ViewHolder, p1: Int) {
val user: User = userList[p1]
p0.time.text = user.time
p0.text.text = user.text
p0.img.setOnClickListener{
val popup = PopupMenu(activity, p0.img)
popup.inflate(R.menu.schedulemenu)
popup.setOnMenuItemClickListener {
when(it.itemId){
R.id.menu_delete->{
activity.dbHelper.deleteData(userList[p1].id.toLong())
activity.refreshList()
}
R.id.menu_update->{
activity.updateUser(userList[p1])
}
R.id.notify->{
activity.notifyMeUpdate(userList[p1])
}
}
true
}
popup.show()
}
}
Thanks for Help
Thanks for all who tried. After several hours of debugging. I found that I did a very stupid mistake that haven't register my receiver in manifest file now it's working fine.
I'm currently building an app that starts a session by sending a request to the backend. After that, the app has to send a heartbeat request every 4.5 minutes. If the app does not send a request to the backend after 4.5 minutes since the last successful request, the session will get terminated. The heartbeat request can be sent earlier which also means that the next heartbeat has to be sent 4.5 minutes after that request.
Once the user has started the session, he should be able to put the app to the background to use the device for other things.
I'm struggling with coming up with a solution that works with the background restrictions (doze mode, etc).
I'm currently running the app with a foreground service. But the requests stop after a couple of minutes if I don't use the device actively. I tried the WorkManager and the AlarmManager. But the requests keep getting delayed.
I played around with REQUEST_IGNORE_BATTERY_OPTIMIZATIONS and this seems to work but I don't want to use that approach since Google seems to really dislike apps using this permission.
I created a test app to play around with different approaches. Maybe I'm doing something completely wrong?
Service:
class MainService : Service() {
private lateinit var wakeLock: PowerManager.WakeLock
override fun onBind(intent: Intent?): IBinder? = null
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= 26) {
val appName = getString(R.string.app_name)
val channelName = "$appName channel name"
val channelImportance = NotificationManager.IMPORTANCE_HIGH
val channelDescription = "$appName channel description"
createNotificationChannel(this, NOTIFICATION_CHANNEL_ID, channelName, channelImportance, channelDescription)
}
val notification = createOngoingNotification(this, NOTIFICATION_REQUEST_CODE, R.mipmap.ic_launcher_round, "Content Text")
startForeground(1000, notification)
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::MyWakelockTag").apply {
acquire(1 * 60 * 60 * 1000L)
}
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
intent?.action?.let {
if (it == "Heartbeat") {
val v = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
v.vibrate(VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE))
}
}
}
setNext()
return START_STICKY
}
private fun setNext() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val intent = Intent(applicationContext, MainService::class.java)
intent.action = "Heartbeat"
val pendingIntent = PendingIntent.getService(applicationContext, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 5 * 1000, pendingIntent)
}
}
override fun onDestroy() {
stopForeground(true)
wakeLock.release()
super.onDestroy()
}
companion object {
const val REQUEST_CODE = 101
const val NOTIFICATION_REQUEST_CODE = 100
const val NOTIFICATION_CHANNEL_ID = "notification_channel_id"
fun createOngoingNotification(context: Context, requestCode: Int, icon: Int, text: String): Notification {
val contentIntent = Intent(context, MainActivity::class.java)
.setAction(Intent.ACTION_MAIN)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
val contentPendingIntent = PendingIntent.getActivity(context, requestCode, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT)
return NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setOngoing(true)
.setSmallIcon(icon)
.setContentTitle("Test Notification")
.setContentText(text)
.setContentIntent(contentPendingIntent)
.build()
}
#RequiresApi(api = 26)
fun createNotificationChannel(context: Context,
id: String, name: String, importance: Int,
description: String) {
val channel = NotificationChannel(id, name, importance)
channel.description = description
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
}
I think that foreground service may be stopped as you are not executing anything inside it after you set the alarm. Foreground service keeps running if you are using it, so for making it work periodically i would put an observable that emits a value every 5 seconds for example. You only need the one emmited after 4.5 minutes but that will keep the foreground service active until you need it. Using rxjava:
Observable.intervalRange( 0 , 54, 0, 5, TimeUnit.SECONDS )
.doOnNext{
//You may not need this
}
.doOnComplete {
//heartbeat
//start this observable again for other 4.5 minutes
}
You will emit a value every 5 seconds 54 times. 54x5 = 270 seconds (what is 4.5 minutes)