I have a project with 2 types of appwidgets each of which has own AppWidgetProvider.
All widgets show current date, therefore have to be updated every midnight at exact time.
I've tested the situation when there are two appwidgets on Home Screen: one instance of each widget type.
On my Nokia with Android 10 everthing works fine: both of widgets are updated at every midnight stable. Action is repeated.
But on my OnePlus with Android 12 based on Oxygen OS I observe very strange behaviour: at the first midnight after putting widget on the home screen both of widgets were updated (date of the day was actual), but at the second midnight only one of the widgets was updated, and the second one stopped updating.
Important note: before tests on OnePlus I canceled battery optimization from OnePlus for the app - to avoid stopping process.
My code is below...
Android Manifest
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
// Android 12 requires for exact methods of AlarmManager
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
Every receiver for every type of appwidgets includes
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.DATE_CHANGED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.TIME_SET"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.TIMEZONE_CHANGED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.LOCALE_CHANGED" />
</intent-filter>
<intent-filter>
<action android:name="ACTION_AUTO_UPDATE_TODAY" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
</intent-filter>
<intent-filter>
<action android:name="android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED" />
</intent-filter>
In every AppWidgetProviderInfo (xml files) for each widget type i have a string
android:updatePeriodMillis="0"
AppWidgetProvider for the first widget
class WidgetPeriodProvider : AppWidgetProvider() {
private var APPWIDGET_CONFIGURE = "android.appwidget.action.APPWIDGET_CONFIGURE"
// For AlarmManager
private val ACTION_AUTO_UPDATE_PERIOD = "ACTION_AUTO_UPDATE_PERIOD"
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
...
// There may be multiple widgets active, so update all of them
for (appWidgetId in appWidgetIds) {
updateWidgetPeriod(context, appWidgetManager, appWidgetId)
}
super.onUpdate(context, appWidgetManager, appWidgetIds)
}
override fun onEnabled(context: Context) {
// Set Auto Update with AlarmManager
setAlarmManagerPeriod(context)
super.onEnabled(context)
}
override fun onDisabled(context: Context) {
// Disable Auto Update with AlarmManager
disableAlarmManagerPeriod(context)
super.onDisabled(context)
}
override fun onReceive(context: Context, intent: Intent) {
if (
intent.action.equals(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED) ||
intent.action.equals(ACTION_AUTO_UPDATE_PERIOD) ||
intent.action.equals(Intent.ACTION_DATE_CHANGED) ||
intent.action.equals(Intent.ACTION_TIME_CHANGED) ||
intent.action.equals(Intent.ACTION_TIMEZONE_CHANGED) ||
intent.action.equals(Intent.ACTION_LOCALE_CHANGED) ||
intent.action.equals(Intent.ACTION_PROVIDER_CHANGED) ||
intent.action.equals(Intent.ACTION_BOOT_COMPLETED) ||
intent.action.equals(AppWidgetManager.ACTION_APPWIDGET_UPDATE) ||
intent.action.equals(Intent.ACTION_MY_PACKAGE_REPLACED)
) {
val thisAppWidget = ComponentName(
context.packageName, javaClass.name
)
val appWidgetManager = AppWidgetManager
.getInstance(context)
val ids = appWidgetManager.getAppWidgetIds(thisAppWidget)
// Update widgets
for (appWidgetId in ids) {
updateWidgetPeriod(context, appWidgetManager, appWidgetId)
}
// AlarmManager Auto Update Reschedule
setAlarmManagerPeriod(context)
}
super.onReceive(context, intent)
}
AppWidgetProvider for the second widget
class WidgetTodayProvider : AppWidgetProvider() {
private var APPWIDGET_CONFIGURE = "android.appwidget.action.APPWIDGET_CONFIGURE"
// For AlarmManager
private val ACTION_AUTO_UPDATE_TODAY = "ACTION_AUTO_UPDATE_TODAY"
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
...
// There may be multiple widgets active, so update all of them
for (appWidgetId in appWidgetIds) {
updateWidgetToday(context, appWidgetManager, appWidgetId)
}
super.onUpdate(context, appWidgetManager, appWidgetIds)
}
override fun onEnabled(context: Context) {
// Set Auto Update with AlarmManager
setAlarmManagerToday(context)
super.onEnabled(context)
}
override fun onDisabled(context: Context) {
// Disable Auto Update with AlarmManager
disableAlarmManagerToday(context)
super.onDisabled(context)
}
override fun onReceive(context: Context, intent: Intent) {
if (
intent.action.equals(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED) ||
intent.action.equals(ACTION_AUTO_UPDATE_TODAY) ||
intent.action.equals(Intent.ACTION_DATE_CHANGED) ||
intent.action.equals(Intent.ACTION_TIME_CHANGED) ||
intent.action.equals(Intent.ACTION_TIMEZONE_CHANGED) ||
intent.action.equals(Intent.ACTION_LOCALE_CHANGED) ||
intent.action.equals(Intent.ACTION_PROVIDER_CHANGED) ||
intent.action.equals(Intent.ACTION_BOOT_COMPLETED) ||
intent.action.equals(AppWidgetManager.ACTION_APPWIDGET_UPDATE) ||
intent.action.equals(Intent.ACTION_MY_PACKAGE_REPLACED)
) {
val thisAppWidget = ComponentName(
context.packageName, javaClass.name
)
val appWidgetManager = AppWidgetManager
.getInstance(context)
val ids = appWidgetManager.getAppWidgetIds(thisAppWidget)
// Update widgets
for (appWidgetId in ids) {
updateWidgetToday(context, appWidgetManager, appWidgetId)
}
// AlarmManager Auto Update Reschedule
setAlarmManagerToday(context)
}
super.onReceive(context, intent)
}
Function for setting alarm - the first widget type
private fun setAlarmManagerPeriod(context: Context) {
val intent = Intent(context, WidgetPeriodProvider::class.java)
intent.action = ACTION_AUTO_UPDATE_PERIOD
val pIntent = PendingIntent.getBroadcast(context, 2, intent, PendingIntent.FLAG_IMMUTABLE)
val c = Calendar.getInstance()
c[Calendar.HOUR_OF_DAY] = 0
c[Calendar.MINUTE] = 0
c[Calendar.SECOND] = 3
val alarmManager = context
.getSystemService(Context.ALARM_SERVICE) as AlarmManager
// For Android 12 and higher check permission for AlarmManager at exact time
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val hasPermission: Boolean = alarmManager.canScheduleExactAlarms()
if(hasPermission) {
// Permission granted - use exact method
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
c.timeInMillis,
pIntent
)
} else {
// Permission is not granted - use inexact method
alarmManager.setRepeating(
AlarmManager.RTC_WAKEUP,
c.timeInMillis,
AlarmManager.INTERVAL_DAY,
pIntent
)
}
} else {
// For Android below 12 use exact method
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
c.timeInMillis,
pIntent
)
}
}
Function to disable alarm - the first widget type
private fun disableAlarmManagerPeriod(context: Context) {
val intent = Intent(context, WidgetPeriodProvider::class.java)
intent.action = ACTION_AUTO_UPDATE_PERIOD
val pIntent = PendingIntent.getBroadcast(context, 2, intent, PendingIntent.FLAG_IMMUTABLE)
val alarmManager = context
.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.cancel(pIntent)
}
Function for setting alarm - the second widget type
private fun setAlarmManagerToday(context: Context) {
val intent = Intent(context, WidgetTodayProvider::class.java)
intent.action = ACTION_AUTO_UPDATE_TODAY
val pIntent = PendingIntent.getBroadcast(context, 1, intent, PendingIntent.FLAG_IMMUTABLE)
val c = Calendar.getInstance()
c[Calendar.HOUR_OF_DAY] = 0
c[Calendar.MINUTE] = 0
c[Calendar.SECOND] = 1
val alarmManager = context
.getSystemService(Context.ALARM_SERVICE) as AlarmManager
// For Android 12 and higher check permission for AlarmManager at exact time
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val hasPermission: Boolean = alarmManager.canScheduleExactAlarms()
if(hasPermission) {
// Permission granted - use exact method
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
c.timeInMillis,
pIntent
)
} else {
// Permission is not granted - use inexact method
alarmManager.setRepeating(
AlarmManager.RTC_WAKEUP,
c.timeInMillis,
AlarmManager.INTERVAL_DAY,
pIntent
)
}
} else {
// For Android below 12 use exact method
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
c.timeInMillis,
pIntent
)
}
}
Function to disable alarm - the first widget type
private fun disableAlarmManagerToday(context: Context) {
val intent = Intent(context, WidgetTodayProvider::class.java)
intent.action = ACTION_AUTO_UPDATE_TODAY
val pIntent = PendingIntent.getBroadcast(context, 1, intent, PendingIntent.FLAG_IMMUTABLE)
val alarmManager = context
.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.cancel(pIntent)
}
Could you help me, please, to answer the question? - Why not all the widgets are updated at every midnight? What's wrong with my code?
Related
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)
}
None of the other Stackoverflow code I found works. All is either Java, or I'm too dumb to make it work.
How to fire notification at the same time every day? So basic thing and I can't find anything for Kotlin.
Use this code to schedule showing notification each day at 22:00 (or any other hour in HOUR_TO_SHOW_PUSH):
private val alarmManager = context.getSystemService(ALARM_SERVICE) as AlarmManager
private val alarmPendingIntent by lazy {
val intent = Intent(context, AlarmReceiver::class.java)
PendingIntent.getBroadcast(context, 0, intent, 0)
}
private const val HOUR_TO_SHOW_PUSH = 22
fun schedulePushNotifications() {
val calendar = GregorianCalendar.getInstance().apply {
if (get(Calendar.HOUR_OF_DAY) >= HOUR_TO_SHOW_PUSH) {
add(Calendar.DAY_OF_MONTH, 1)
}
set(Calendar.HOUR_OF_DAY, HOUR_TO_SHOW_PUSH)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
alarmManager.setRepeating(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
AlarmManager.INTERVAL_DAY,
alarmPendingIntent
)
}
It will trigger BroadcastReceiver called AlarmReceiver, so you'll have to implement it too:
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
showPushNotification() // implement showing notification in this function
}
}
Don't forget to register it in your AndroidManifest.xml:
<receiver android:name="com.your-package-name.AlarmReceiver" android:enabled="true"/>
Also note that to schedule these notifications you have to call schedulePushNotifications(), meaning that the app has to be launched at least once after each reboot. If you want your notifications to be shown after a reboot without launching your app, consider implementing BootReceiver that will be triggered immediately after reboot:
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == "android.intent.action.BOOT_COMPLETED") {
schedulePushNotifications()
}
}
}
Don't forget to register it in AndroidManifest.xml too:
<receiver android:name="com.your-package-name.BootReceiver" android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
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.
I can't seem to get the AlarmManager to work inside a Fragment. My receiver's onReceive() method never gets executed. I assume that I might use context in a wrong way but then again I also couldn't get it to work inside an Activity. I've also registered the receiver in my manifest.
MyFragment.kt
class MyFragment : Fragment() {
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
var alarmMgr: AlarmManager? = null
lateinit var alarmIntent: PendingIntent
alarmMgr = context!!.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmIntent = Intent(context, Receiver::class.java).let { intent ->
PendingIntent.getService(context, 0, intent, 0)
}
val calendar: Calendar = Calendar.getInstance().apply {
timeInMillis = System.currentTimeMillis()
// The EditText includes a time in 24-hour format (e.g. 12:34)
set(Calendar.HOUR_OF_DAY, editText.text.toString().substringBefore(":").toInt())
set(Calendar.MINUTE, editText.text.toString().substringAfter(":").toInt())
}
Log.d("ALARM", "CREATED")
alarmMgr?.set(
AlarmManager.RTC,
calendar.timeInMillis,
alarmIntent
)
}
}
Receiver.kt
class Receiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.d("ALARM", "RECEIVED")
}
}
AndroidManifest.xml
<application
...
<receiver android:name="com.example.name.Receiver" />
</application>
First things first:
AndroidManifest.xml
<receiver
android:name="com.example.name.Receiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
</intent-filter>
</receiver>
Then, in this case in your Fragment, however, I suggest doing this somewhere else:
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, Receiver::class.java)
// Used for filtering inside Broadcast receiver
intent.action = "MyBroadcastReceiverAction"
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0)
// In this particular example we are going to set it to trigger after 30 seconds.
// You can work with time later when you know this works for sure.
val msUntilTriggerHour: Long = 30000
val alarmTimeAtUTC: Long = System.currentTimeMillis() + msUntilTriggerHour
// Depending on the version of Android use different function for setting an
// Alarm.
// setAlarmClock() - used for everything lower than Android M
// setExactAndAllowWhileIdle() - used for everything on Android M and higher
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) {
alarmManager.setAlarmClock(
AlarmManager.AlarmClockInfo(alarmTimeAtUTC, pendingIntent),
pendingIntent
)
} else {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
alarmTimeAtUTC,
pendingIntent
)
}
In your Broadcast Receiver, we then do the following:
class Receiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
// We use this to make sure that we execute code, only when this exact
// Alarm triggered our Broadcast receiver
if (intent?.action == "MyBroadcastReceiverAction") {
Log.d("ALARM", "RECEIVED")
}
}
}
I want to notify at midnight. So I used alrammanager. the alrammanager is working well when it is in foreground or background. But the problem is occured when my app is killed. The alrammanager sometimes works or not. I don't understand this weird happening...
For example, when the notification occurs every three minutes, the app works well even if it is in the foreground or background. But when the app kiiled, the notification is occured or not even though there is no change in code. Please help me...
MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val picker = findViewById(R.id.timePicker) as TimePicker
picker.setIs24HourView(true)
val nextNotifyTime: Calendar = GregorianCalendar()
val nextDate: Date = nextNotifyTime.getTime()
val currentTime: Date = nextNotifyTime.getTime()
val HourFormat = SimpleDateFormat("kk", Locale.getDefault())
val MinuteFormat = SimpleDateFormat("mm", Locale.getDefault())
val pre_hour: Int = HourFormat.format(currentTime).toInt()
val pre_minute: Int = MinuteFormat.format(currentTime).toInt()
if (Build.VERSION.SDK_INT >= 23) {
picker.hour = pre_hour
picker.minute = pre_minute
} else {
picker.currentHour = pre_hour
picker.currentMinute = pre_minute
}
val button: Button = findViewById(R.id.button) as Button
button.setOnClickListener {
val hour: Int
val hour_24: Int
val minute: Int
if (Build.VERSION.SDK_INT >= 23) {
hour_24 = picker.hour
minute = picker.minute
} else {
hour_24 = picker.currentHour
minute = picker.currentMinute
}
if (hour_24 > 12) {
hour = hour_24
}
else {
hour = hour_24- 12
}
val calendar: Calendar = Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, hour)
set(Calendar.MINUTE, minute)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
if(calendar.after(Calendar.getInstance())) {
//calendar.add(Calendar.DATE, 1)
}
diaryNotification(calendar)
}
}
fun diaryNotification(calendar: Calendar) {
val pm = this.packageManager
val receiver = ComponentName(this, DeviceBootReceiver::class.java)
val alarmIntent = Intent(this, AlarmReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent, 0)
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, 60*1000, pendingIntent)
pm.setComponentEnabledSetting(receiver,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP)
}
AlarmReceiver
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notificationIntent = Intent(context, MainActivity::class.java)
notificationIntent.flags = (Intent.FLAG_ACTIVITY_CLEAR_TOP
or Intent.FLAG_ACTIVITY_SINGLE_TOP)
var notification = CustomNotification.getNotificationBuilderInstance(context)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channelName = "channel name"
val importance = NotificationManager.IMPORTANCE_LOW
val channel = NotificationChannel(CHANNEL_ID, channelName, importance)
notificationManager.createNotificationChannel(channel)
}
notificationManager.notify(NOTIFICATION_ID, notification.build())
}
}
DeviceBootReceiver
class DeviceBootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (Objects.equals(intent.action, "android.intent.action.BOOT_COMPLETED")) {
val alarmIntent = Intent(context, AlarmReceiver::class.java)
val pendingIntent =
PendingIntent.getBroadcast(context, 0, alarmIntent, 0)
val manager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val millis = Calendar.getInstance().timeInMillis
manager.setRepeating(
AlarmManager.RTC_WAKEUP, millis,
60*1000, pendingIntent
)
}
}
}
Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.haii.alarmdemo">
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:name=".GlobalApplication"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".DeviceBootReceiver"
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".AlarmReceiver" />
</application>
</manifest>
go to settings -> app -> YOUR APK NAME -> enable AUTOSTART (or something similar)
now even if your app is killed you will receive your notification
Also I suggest you to take a look at this link: LINK
Hope this "solution" can help you, cya ;)