I want to repeat an action every day; it must continue working even if the app is not running or the device has been restarted (rebooted).
In my code I'm trying to show a TOAST message every 1 minute (as a test); it's working fine in the emulator but on a real device it doesn't work( i tried to do some changes to fixed as i see in some answers but still the same thing)
MyReceiver
class MyReceiver : BroadcastReceiver() {
private val channelId = "com.medanis.hikamwahimam"
override fun onReceive(context: Context, intent: Intent) {
Log.i("TAG","/////////////////// SHOW NOTIFICATION NOW //////////////////////")
val builder = NotificationCompat.Builder(context, channelId)
.setSmallIcon(R.drawable.ic_stat_name)
.setLargeIcon(BitmapFactory.decodeResource(context.resources,R.mipmap.ic_launcher_round))
.setContentTitle("My notification")
.setContentText("Much longer text that cannot fit one line...")
.setStyle(
NotificationCompat.BigTextStyle()
.bigText("Much longer text that cannot fit one line...Much longer text that cannot fit one line..."))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
with(NotificationManagerCompat.from(context)) {
notify(12345, builder.build()) }
Toast.makeText(context,"This toast will be shown every X minutes", Toast.LENGTH_LONG).show()
}
}
MainActivity
class MainActivity : AppCompatActivity() {
private var mAlarmManager : AlarmManager? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// showNotification()
val mIntent = Intent(this, MyReceiver::class.java)
val mPendingIntent = PendingIntent.getBroadcast(this, 0, mIntent, PendingIntent.FLAG_UPDATE_CURRENT)
mAlarmManager = this
.getSystemService(Context.ALARM_SERVICE) as AlarmManager
mAlarmManager!!.setRepeating(
AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
60000, mPendingIntent
)
}
}
AndroidManifest.xml
<receiver android:name=".MyReceiver" >
</receiver>
The Issues are :
1/- This code does not work with REAL DEVICE.
2/- This code does not work if the user restart his device.
A sample on GitHub (i had made some changes as my friend had suggested but still the same errors)
1/- This code does not work with REAL DEVICE.
I've downloaded your project, run on my device and it works, it shows the Toast when I click start, and every minute is showing.
I recommend you to take a look on this question
2/- This code does not work if the user restart his device.
If you want to restart your BroadcastReceiver once the device is rebooted or shut downed, you may want to add this code :
Add this in your manifest.xml
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
Create another receiver in manifest.xml
<receiver android:name=".BootCompletedIntentReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
And then create a BroadcastReceiver as you did previously for showing the Toast
class BootCompletedIntentReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if ("android.intent.action.BOOT_COMPLETED" == intent.action) {
//Start your old broadcastreceiver
}
}
}
For more information you can take a look at this post
Hope it helps.
Related
I have implemented alarm manager to trigger every 5 minutes in my application. But when using it on my Redmi note 8 device, alarm manager not triggered in wifi enabled mode and at the same time it works for mobile data enabled mode. For other device's it works fine in both wifi and mobile data.
I know that wifi or mobile-data is not related to alarm manager triggering process. But, I'm facing this weird issue.
Could anyone help me out ?
This is my alarm triggering code.
private var alarmManager: AlarmManager? = null
private lateinit var pendingIntent: PendingIntent
override fun startAlarm(url: String, status: String) {
alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(this, AlarmReceiver::class.java).apply {
action = Constants.ACTION_ALARM_MANAGER
putExtra(KeyConstants.STATUS, status)
putExtra(KeyConstants.URL, url)
}
pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val timeInterval = TimeUnit.MINUTES.toMillis(5)
alarmManager?.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), timeInterval, pendingIntent)
}
override fun cancelAlarm() {
alarmManager?.cancel(pendingIntent)
}
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// Is triggered when alarm goes off, i.e. receiving a system broadcast
if (intent.action == Constants.ACTION_ALARM_MANAGER) {
val tripStatus = intent.getStringExtra(KeyConstants.STATUS)
val directionUrl = intent.getStringExtra(KeyConstants.URL)
// my logic here
}
}
}
and also added receiver in Manifest.xml
<receiver android:name=".AlarmReceiver" />
Every OEM has made it worse to use Alarm manager, mostly it's their battery saving app which kills the app in the background thus no triggering of alarm events.
Android's system makes sure that no alarm should trigger very often, for saving battery and minimizing wakeups, android shifts these alarms for future for this.
As your alarm goes off every 5 minutes, most probably android is shifting your alarm too.
You can check all the scheduled alarm events of the device using following command adb shell dumpsys alarm
When an application receives push notification from FCM, it calls onMessageReceived. (See 1, 2 or 3.)
When a user taps the notification, it launches the applications, then it sends a request to a server that the user has read the notification.
I want to know when a device received a push notification, but the user swiped it (or cleared all notifications). I want to send a request to the server that the user simply cancelled the notification.
I tried to send BroadcastReceiver and show logs (see 4 or 5), but it works when the application was opened when the notification delivered. I suppose, that
MyFirebaseMessagingService:
override fun onMessageReceived(remoteMessage: RemoteMessage) {
...
// An event when a user swipes a notification.
val intent = Intent(this, NotificationBroadcastReceiver::class.java)
intent.action = "notification_cancelled"
val deleteIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)
notificationBuilder.setDeleteIntent(deleteIntent)
// Navigation to an activity when a user taps the notification.
// It doesn't matter to this question.
val intent2 = Intent(this, MainActivity::class.java)
val navigateIntent = PendingIntent.getActivity(this, notificationId, intent2,
PendingIntent.FLAG_UPDATE_CURRENT)
notificationBuilder.setContentIntent(navigateIntent)
...
}
NotificationBroadcastReceiver:
class NotificationBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Timber.d("NotificationBroadcastReceiver onReceive")
Toast.makeText(context, "Notification dismissed", Toast.LENGTH_LONG).show()
// Send a request to the server.
}
}
AndroidManifest:
<uses-permission android:name="com.uremont.NOTIFICATION_PERMISSION" />
<receiver
android:name=".receiver.NotificationBroadcastReceiver"
android:exported="true"
android:permission="NOTIFICATION_PERMISSION"
>
<intent-filter>
<action android:name="notification_cancelled" />
</intent-filter>
</receiver>
works only when the application is opened. But when the application is in background or killed, it doesn't react to swipe. Probably we shouldn't use BroadcastReceiver, for instance, use PendingIntent.getService or PendingIntent.getForegroundService.
Can we send a request to the server?
After a short time it worked right (though I hardly changed much). After a lot of research I made this solution. Tested on several Android emulators and devices from API 19 to API 30.
Because using BroadcastReceiver is not safe, add in AndroidManifest:
<receiver
android:name=".NotificationBroadcastReceiver"
android:exported="false"
/>
NotificationBroadcastReceiver:
class NotificationBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Timber.d("NotificationBroadcastReceiver onReceive")
if (intent.extras != null) {
// Receive parameters of a cancelled notification.
val authToken = intent.getStringExtra(EXTRA_TOKEN)
val code = intent.getStringExtra(EXTRA_CODE)
Timber.d("token = $authToken, code = $code")
// We can access context even if the application was removed from the recent list.
Toast.makeText(context, "Notification $code was cancelled", Toast.LENGTH_SHORT).show()
// Send data to a server.
}
}
companion object {
const val EXTRA_TOKEN = "EXTRA_TOKEN"
const val EXTRA_CODE = "EXTRA_CODE"
}
}
MyFirebaseMessagingService:
override fun onMessageReceived(remoteMessage: RemoteMessage) {
...
// Get notification code from data.
val code = remoteMessage.data["code"]
val notificationBuilder = NotificationCompat.Builder(this,
...
val notificationId = Random.nextInt()
val intent = Intent(this, NotificationBroadcastReceiver::class.java).apply {
putExtra(EXTRA_TOKEN, authToken)
putExtra(EXTRA_CODE, code)
}
val deleteIntent = PendingIntent.getBroadcast(this, notificationId, intent,
PendingIntent.FLAG_CANCEL_CURRENT)
notificationBuilder.setDeleteIntent(deleteIntent)
val notification = notificationBuilder.build()
notificationManager.notify(notificationId, notification)
}
Send a push message to Android device with it's push token, for instance:
{
"to": "ddSOGiz4QzmY.....:APA91bHgoincFw.......",
"data": {
"title": "Test",
"message": "Test",
"code": "ABCDEF"
}
}
You can see different schemes of delivering push messages here. If a user presses "Force stop" at the application, it won't receive push messages (except "AliExpress", ha-ha).
When the user dismisses push notification, NotificationBroadcastReceiver::onReceive() is called. An application gets parameters of the push message. Then we can see a toast message and send these parameters to the server.
When the user presses "Clear all" notifications, all dismiss events are fired. So, you will see a sequence of toasts. The server will receive several requests simultaneously (check that it can handle, for instance, 10 requests in 0.01 second).
So i'm trying to run a service that sends certain room data to a server every midnight(roughly) and deletes it from the local db. Reading the android docs i came to the conclusion that the best approach currently was to setup an alarm that triggers at midnight and starts a Service that executes the syncing). While i'm not entirely sure of what type of Service i should i use my files look like this:
MainActivity.kt
alarmMgr = applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager?
alarmIntent = Intent(this, SendTakesReceiver::class.java).let { intent ->
PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
// Set the alarm to send takes at approximately 00:00 a.m.
val calendar: Calendar = Calendar.getInstance().apply {
timeInMillis = System.currentTimeMillis()
set(Calendar.HOUR_OF_DAY, 0)
}
// With setInexactRepeating(), you have to use one of the AlarmManager interval
// constants--in this case, AlarmManager.INTERVAL_DAY.
alarmMgr?.setInexactRepeating(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
AlarmManager.INTERVAL_DAY,
alarmIntent
)
SendtakesReceiver.kt
class SendTakesReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
//these following logs are always shown
Log.d("TAKES INTENT", intent.toString())
if (intent != null) {
context?.run {
Log.d("TAKES SERVICE", intent.toString())
startService(intent)
}
}
}
}
SendTakesService.kt
class SendTakesService : Service() {
val sp: SharedPreferences = getSharedPreferences("login", Context.MODE_PRIVATE)
private var prescriptionTakeDao =
AppDatabase.getDatabase(applicationContext).prescriptionTakeDao()
override fun onCreate() {
super.onCreate()
Log.d("TakesService", "onCreate()")
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}
And finally in my manifest file i have (inside the application tag):
<receiver
android:name=".src.ui.prescriptions.SendTakesReceiver"
android:enabled="true" />
<service
android:name="com.glik.glik.src.services.SendTakesService"
android:enabled="true"
android:process=":SendTakes">
</service>
But the problem is that none of the service Classes(Service, IntentService, JobIntentService) that i've tried using are calling their main method. For example here the onCreate() log is never shown. I've seen countless of similar questions regarding services not being started but i can't seem to pinpoint the exact mistake that i am making here that causes the service to not be started. If any one could help me find it or maybe recommend a more elegant approach to do the db sync i would be really grateful. Thanks in advance.
Depending on the OEM of the phone you are on, you will face various challenges with this approach. See https://dontkillmyapp.com/
You should use WorkManager in order to decrease battery usage and to avoid having to manually handle many corner cases: https://developer.android.com/topic/libraries/architecture/workmanager/basics
I followed this tutorial: https://developer.android.com/training/location/geofencing and works fine on Android < 8, but in Oreo i have problems due to new OS background limitations.
How can I get geofence transition triggers when app is in background?
I also tried to use a BroadcastReceiver instead of IntentService, but the result is the same.
Pending Intent:
private val geofencePendingIntent: PendingIntent by lazy {
val intent = Intent(context, GeofenceBroadcastReceiver::class.java)
intent.action = "com.example.GEOFENCE_TRANSITION"
PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
Register geofence:
geofencingClient.addGeofences(request, geofencePendingIntent).run {
addOnSuccessListener {
Log.d(TAG, "Geofence added")
}
addOnFailureListener {
Log.e(TAG, "Failed to create geofence")
}
}
Broadcast Receiver:
class GeofenceBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(p0: Context?, p1: Intent?) {
Log.d(TAG, "onReceive")
}
}
Receiver in Manifest:
<receiver android:name=".GeofenceBroadcastReceiver">
<intent-filter>
<action android:name="com.example.GEOFENCE_TRANSITION"/>
</intent-filter>
</receiver>
Thanks
EDIT: IntentService version
Pending Intent:
private val geofencePendingIntent: PendingIntent by lazy {
val intent = Intent(context, GeofenceIntentService::class.java)
PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
Intent Service:
class GeofenceIntentService : IntentService("GeofenceIntentService") {
override fun onHandleIntent(p0: Intent?) {
Log.d(TAG, "onHandleIntent")
}
}
Service in Manifest:
<service android:name=".GeofenceIntentService"/>
You should get an Intent every couple of minutes on Android 8 when your geofence transition is reached in background.
See: https://developer.android.com/training/location/geofencing#java
Handle geofence transitions
When Location Services detects that the user has entered or exited a geofence, it sends out the Intent contained in the PendingIntent you included in the request to add geofences. This Intent is received by a service like GeofenceTransitionsIntentService, which obtains the geofencing event from the intent, determines the type of Geofence transition(s), and determines which of the defined geofences was triggered. It then sends a notification as the output.
Note: On Android 8.0 (API level 26) and higher, if an app is running in the background while monitoring a geofence, then the device responds to geofencing events every couple of minutes. To learn how to adapt your app to these response limits, see Background Location Limits.
Once the geofence service is registered it´s still there and you have nothing else to do and only check your IntentService for the specific PendingIntent, exclude when the device is rebooted you need to reregister your geofence service.
Also check: https://developer.android.com/about/versions/oreo/background-location-limits
i use dexter library for permission geofence and this work for android 8 9 10 and above you must add background permission
Dexter.withActivity(this#Activity_Map)
.withPermissions(
Manifest.permission.ACCESS_COARSE_LOCATION
,Manifest.permission.ACCESS_FINE_LOCATION
,Manifest.permission.ACCESS_BACKGROUND_LOCATION
)
.withListener(object: MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let {
if(report.areAllPermissionsGranted()){
//put your code here
}
}
I want to use AlarmService to trigger a notification at a certain time. Think of it as something similar as a calendar app that is showing a reminder as notification for an upcoming event.
The code to schedule the intent (via alarm service) looks like this:
fun scheduleNotification(event : CalendarEvent)
val startTime : Instant = event.startTime
val intent = buildPendingIntent(event)
val notificationTime = startTime.minusMillis(TimeUnit.MINUTES.toMillis(10)) // 10 Minutes earlier
if (Build.VERSION.SDK_INT < 23) {
alarmService().setExact(AlarmManager.RTC_WAKEUP,
notificationTime.toEpochMilli(), intent)
} else {
alarmService().setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,
notificationTime.toEpochMilli(), intent)
}
}
fun buildPendingIntent(event : CalendarEvent){
val intent = Intent(context, NotificationReceiver::class.java)
intent.putExtra(EVENT_ID, event.id)
return PendingIntent.getBroadcast(context, 0, realIntent, 0)
}
class NotificationReceiver : WakefulBroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// build and display the notification
}
}
So 1 of 10 times the notification is tirggered and shown (by NotificationReceiver) correctly, also at the desired time. So I think the scheduling part is working properly.
Which leads me to another question: Whenever the user creates a new CalendarEvent the method scheduleNotification(newEvent) is called. It seems to me that AlarmService is internally updating the PendingIntents of existing and that this is the reason why 1 of 10 (usually the first scheduled PendingIntent) is triggered, but the others are not.
How many alarms can I schedule for an Android App? Do you spot any other issue with my code?