Kotlin AlarmManager and BroadcastReceiver not working - android

I'm trying to set an alarm with AlarmManager, but my BroadcastReceiver never gets called. Here is my snippet.
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
//Never gets hit
}
}
context.registerReceiver(receiver, IntentFilter(LOCAL_NOTIFICATION))
val intent = Intent()
intent.action = LOCAL_NOTIFICATION
val alarmManager = context.getSystemService(ALARM_SERVICE) as? AlarmManager
val pendingIntent = PendingIntent.getService(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val calendar = Calendar.getInstance()
calendar.add(Calendar.SECOND, 10)
alarmManager?.set(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, pendingIntent)
I've tried registering a broadcast receiver in AndroidManifest.xml but nothing seems to be working.

I just noticed that I was calling getService() on PendingIntent instead of getBroadcast()
After changing that, it works perfectly!

In addition to the preferred answer, I set the class of the intent before it worked. See the example below:
val intent = Intent()
intent.action = LOCAL_NOTIFICATION
intent.setClass(context, MyBroadCastReceiver::class.java) //this line
before passing the intent to the pendintIntent.
hope it helps

Related

How to cancel a scheduled local notification in Kotlin?

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

AlarmManager set is not firing broadcast

I'm trying to schedule notifications using the AlarmManager but for whatever reason, the onReceive method on my receiver isn't firing.
Here's how I'm scheduling the alarm
val intent = Intent(this, ReminderReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)
val cal: Calendar = Calendar.getInstance()
cal.add(Calendar.SECOND, 5)
val amanager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
amanager.set(AlarmManager.RTC_WAKEUP, cal.timeInMillis, pendingIntent)
and here's the onReceive method
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context,"notification", Toast.LENGTH_SHORT).show()
}
I tried manually sending a broadcast to see if it works and there's no problem there so the issue shouldn't be related to the manifest.
I'm running this on MIUI12 android 10
I see no issue with your logic if your problem still prevails, try the following as a workaround
val intent = Intent(this, ReminderReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)
// val cal: Calendar = Calendar.getInstance()
// cal.add(Calendar.SECOND, 5)
val triggerTime = SystemClock.elapsedRealtime() + 5_000 // five seconds from now
val amanager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
// amanager.set(AlarmManager.RTC_WAKEUP, cal.timeInMillis, pendingIntent)
AlarmManagerCompat.setExactAndAllowWhileIdle(
amanager,
AlarmManager.ELAPSED_REALTIME_WAKEUP,
triggerTime,
pendingIntent
)

How to use Android AlarmManager in Fragment in Kotlin?

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")
}
}
}

How to Repeat a function every Hour in Android Studio using Kotlin?

I want to repeat a function( or any action ) every one Hour ( for example ) even if the app is not running.
I've created a demo project so you can take a look at it :
https://github.com/joancolmenerodev/BroadcastReceiverAndAlarmManagerInKotlin
You first have to create a BroadcastReceiver, and then using AlarmManager you can decide the interval of time you want to be called.
Create a BroadcastReceiver you can do it as follows :
val broadCastReceiver = object : BroadcastReceiver() {
override fun onReceive(contxt: Context?, intent: Intent?) {
toast("This toast will be shown every X minutes")
}
}
And then you have this method to start the job :
val mIntent = Intent(context, broadCastReceiver)
val mPendingIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, mIntent, 0)
val mAlarmManager = context
.getSystemService(Context.ALARM_SERVICE) as AlarmManager
mAlarmManager.setRepeating(
AlarmManager.ELAPSED_REALTIME_WAKEUP, System.currentTimeMillis(),
CHANGETOYOURDESIREDSECONDS, mPendingIntent
)
And then you'll be able to see the Toast even if the app is closed.
Edit
You can register your BroadcastReceiver using context.registerReceiver(receiver, IntentFilter("something"))
and then adding to the mIntent and action for "something".
If you don't like this way, you can create a new class named MyReceiver that extends BradcastReceiver as follows :
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context,"This toast will be shown every X minutes", Toast.LENGTH_SHORT).show()
}
}
And then start the alarm doing this :
val mIntent = Intent(this, MyReceiver::class.java)
val mPendingIntent = PendingIntent.getBroadcast(this, 0, mIntent, PendingIntent.FLAG_UPDATE_CURRENT)
val mAlarmManager = this
.getSystemService(Context.ALARM_SERVICE) as AlarmManager
mAlarmManager.setRepeating(
AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
WHATEVERYOUWANT, mPendingIntent
)
Note: By default is set to 60000
Value will be forced up to 60000 as of Android 5.1; don't rely on this to be exact
If you are using AndroidX(JetPack) library then concider to use Workmanager
Simple example:
public class MyWorker extends Worker {
static final String TAG = "workmng";
#NonNull
#Override
public WorkerResult doWork() {
Log.d(TAG, "doWork: start");
//Do your job here
Log.d(TAG, "doWork: end");
return WorkerResult.SUCCESS;
}
}
And start like this, to do your job each hour :
PeriodicWorkRequest myWorkRequest = new PeriodicWorkRequest.Builder(MyWorker.class, 60, TimeUnit.MINUTES)
.build();
Add in app gradle file:
dependencies {
def work_version = 2.0.0
// (Java only)
implementation "androidx.work:work-runtime:$work_version"
}

How to check if alarm does not have a pending intent?

After I call schedulePing my alarm fires at set time period. With pingScheduled I can see if pending intent exists. However, if I cancel the alarm with cancelPing the pingScheduled still return true.
fun schedulePing(context: Context) {
val am = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, EventReceiver::class.java)
intent.action = EventReceiver.PING
val pendingIntent = PendingIntent.getBroadcast(context, PING, intent,
PendingIntent.FLAG_CANCEL_CURRENT)
val s = SettingsUtil.load(context)
am.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + s.periodPing, s.periodPing,
pendingIntent)
}
fun cancelPing(context: Context) {
if (pingScheduled(context).not()) return
val am = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, EventReceiver::class.java)
intent.action = EventReceiver.PING
val pendingIntent = PendingIntent.getBroadcast(context, PING, intent,
PendingIntent.FLAG_CANCEL_CURRENT)
am.cancel(pendingIntent)
}
fun pingScheduled(context: Context): Boolean {
val intent = Intent(context, EventReceiver::class.java)
intent.action = EventReceiver.PING
return PendingIntent.getBroadcast(context, PING,
intent, PendingIntent.FLAG_NO_CREATE) != null
}
am.cancel(pendingIntent) cancel alram, but does not discard the pending intent. So it is important to call
pendingIntent.cancel after am.cancel to be able to check correct presence with PendingIntent.FLAG_NO_CREATE

Categories

Resources