I've been trying to implement Notifications in my app without effectively succeeding. The following are my observations:
Requirements:
After scheduling a Notification it has to be repeated on a monthly basis.
The notification will be inflated with some model object data, so the object needs to be passed to the notification builder.
How I tackled the problem so far:
I created a NotificationScheduler object that basically creates a PendingIntent for the notification to be fired.
object NotificationScheduler {
fun scheduleNotification(context: Context, model: Model, cal: Calendar) {
cal.set(Calendar.HOUR_OF_DAY, 11)
val notificationIntent = Intent(context, NotificationPublisher::class.java)
notificationIntent.putExtra("model", Utils.parcelToBytes(model))
val pendingIntent = PendingIntent.getBroadcast(context, 1, notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.set(AlarmManager.RTC_WAKEUP, cal.timeInMillis, pendingIntent)
}
}
Then I have a NotificationPublisher class that should show the notification and set the next one by calling NotificationScheduler again.
class NotificationPublisher: BroadcastReceiver() {
var model: Model? = null
override fun onReceive(context: Context?, intent: Intent?) {
val modelBytes = intent?.getByteArrayExtra("model")
if (modelBytes != null) {
model = Utils.bytesToParcel(modelBytes, Model.CREATOR)
}
val notificationBuilder = NotificationBuilder(context!!, model!!)
val notification = notificationBuilder.buildNotification()
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(1, notification)
//schedules the next notification
NotificationScheduler.scheduleNotification(context, model!!, Calendar.getInstance())
}
}
Where the NotificationBuilder class just takes the context and the model and builds the notification
class NotificationBuilder(val context: Context, val model: Model) {
fun buildNotification(): Notification {
generateNotificationChannel(context)
val builder = NotificationCompat.Builder(context, "ID")
.setSmallIcon(R.drawable.sub)
.setContentTitle(model.title)
.setPriority(NotificationCompat.PRIORITY_HIGH)
return builder.build()
}
private fun generateNotificationChannel(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = "Test"
val description = "description"
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel("ID", name, importance)
channel.description = description
val notificationManager = context.getSystemService(NotificationManager::class.java)
notificationManager?.createNotificationChannel(channel)
}
}
My problem with this approach:
It works if I purposely display the notification as soon as it is created, but I get a NullPointerException whenever the user, for example, reboots his device or kills the process. While debugging I found out that the data I store in the notificationIntent in NotificationScheduler, once the device is rebooted, isn't available anymore in the onReceive() of the NotificationPublisher and therefore will generate a NPE.
I think I am doing something conceptually wrong with this approach, and I'd like you to help me figure out how to achieve the above mentioned requirements.
Well.., I am not going in details of your programming problems and will not help you to find the error.. But i have read your question keenly neatly and got the problem and its solution too..!! It is a bit theoretical.. But as i can see your programming skills are good .. so you just needs the direction and concept clearance.
Here we go :
It works if I purposely display the notification as soon as it is created, but I get a NullPointerException whenever the user, for example, reboots his device or kills the process.
This is the main cause of the problem. Whenever system reboots or whenever system kills the app.
What causing it...?
Whenever system is rebooted "All the alarms set by AlarmManager are no more preserved and needs to be re-scheduled again by the application"
What is work around then...?
Maintain a table of scheduled alarms. As soon as system fires ON_BOOT_COMPLETE re-schedule all again.
Most of the devices in market not triggering my ON_BOOT_COMPLETE receiver..!!
Yes most of the devices now a days are keeping only major companies services like google, facebook, whatsapp, twitter, instagram and rest all others are discarded at boot time.
Why mobile manufacturers does this?
As it is a business and it also improves performance and speed of the overall device. And if they do not allow said companies services on reboot .., then no one will take such phones..!!
Any solution for this..?
Use firebase job dispatcher. Let Google play services handle your service.
I hope it helps you and correct me if i am wrong or if need any further clarification over the issue. Thanks
EDIT 2
I would suggest create a job schedular job via your mainactivity context and let it handle google play services which will start executing job on every reboot. That job should call a foreground service with a proper notification. And via that foreground service again call to an intentservice which will re-schedule all the alarms by reading from Sqlite table one by one.
Edit : 3
Even if you follow these all paradigms suggested here... There is another problem you will notice..
Google play not starting My foreground service on reboot..!!
Yes..., As Post Marshmallow and modified ROMs ( including stock android and mobile manufacturers modified OS like OxygenOs, FunTouchOs, ColorOs,.. This list never ends as well) As battery is optimised and no private apps processes can come in memory on reboot..!!
Now is there any way i can tackle this problem too...?
Yes .. Show user a bold and bright alert :
If You want to perform this app normally,
you must stop optimising battery for this
app from settings..!!
That what the ways we can tackle these many problems.. Yes i
understand it has no well documentation from google and no sample codes too, Still in case if you require any help regarding implementing any sub-point I will help you in implementing these all sub-points as well
Thanks you as it helped you
Related
Context
I am working on an app that uses FCM. The use of this application is to alert a user of an event that is occurring (such as an alarm system). In view of the alarm nature of the notification, it is essential that a sound is played when receiving a notification even if the smartphone is in silent or vibrate mode.
Question
Is there a way to achieve this described behavior for all smartphone modes (silent, vibrate, sound) ?
What I've tried
As I am working with API26> I created a notification channel to have the highest priority which is Max Priority,
I've set the notification channel to bypass Do Not Disturb mode like so:
notificationChannel.SetBypassDnd(true);
Obviously it only affects the Do Not Disturb mode and absolutely not what I want,
In the notification builder, I've set the notification priority to Max and the category to Alarm:
.SetPriority(NotificationCompat.PriorityMax)
.SetCategory(NotificationCompat.CategoryAlarm);
Reading the Android documentation, this feature is also related to Do Not Disturb Mode.
I am actively looking for a solution to this problem, but at this point I'm a bit stuck.
Any suggestions ?
I've read about a full screen intent in the Android documentation but it's not written that a sound will fire if the smartphone is in silent mode.
Maybe there is a way to create a service that rings when the notification arrives? But this service has to be running all the time, which isn't really a good design idea.
If you guys have any idea, any remarks or suggestions, i'd be grateful to read them !
I believe you need to set priority for your notification.
private fun setPriorityForAlarmNotification() {
if (notificationManager.isNotificationPolicyAccessGranted) {
notificationManager.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val policy = NotificationManager.Policy(PRIORITY_CATEGORY_ALARMS, 0, 0)
notificationManager.notificationPolicy = policy
}
}
}
As I can see you setCategory for your notification builder is NotificationCompat.CategoryAlarm.
However, in order to set this priority, you need this permission on your manifest
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
And request permission if needed
fun requestNotificationPolicyPermission() {
val notificationManager = activity!!.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
if (!notificationManager.isNotificationPolicyAccessGranted) {
val intent = Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS)
startActivityForResult(intent, REQUEST_CODE_NOTIFICATION_POLICY)
}
}
This solution should work absolutely. Hope this can help you :D
I am a little uncertain as to how I could go about doing this task and was hoping for some clarification.
The APP: It reminds people to water their plants and the user can specify how often they wish to do so.
The problem I am facing is how I can go about sending the local notifications to the user. Instead of setting up an individual notification for each new plant they have with a scheduled time to go off. I was hoping I could specify a time of the day (say 8:00 in the morning) where my app runs through all my plants and checks if any require watering today. If they do, it then tells the user through a local notification saying for instance "You have 5 plants to water today" and when they click on it they go through to the app which shows them what plants they are.
Should I be using the Alarm Manager api for a daily alert? But can this run a script to check plant data and then send a local notification.
Or is it best to just attach an individual local notification to each plant?
Now I am still a novice at Android/Flutter development and just a little unsure what the best practices are for this? Hope I was clear enough in what I said, happy to answer any further questions. Thanks in advance for any help.
I think that this package will help.
It really depends if you need a specific reminder for each plant (e.g. plant1 should be watered at 16:30 and plant2 needs watering at 17:30) or the scenario you describe where all notifications arrive together is what you desire.
If you need a reminder per plant - the only way is to set an alarm per plant
If you can live with one reminder for all plants - you can set one alarm that goes through them all
Please see an excellent implementation example here
public class Water extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "");
wl.acquire();
// Check if there are plants need watering
// Notify user
wl.release();
}
public void setAlarm(Context context)
{
AlarmManager am =( AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent(context, Alarm.class);
PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, 0);
am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 1000 * 60 * 10, pi); // Millisec * Second * Minute
}
}
When you all setAlarm, the Android system will schedule a repeating alarm that wakes up your process at the onReceive function (by broadcasting an event to it).
Please keep in mind this is just the core, look at the link I provided for complete set up including required permissions
We have code similar to the following in our app
val pendingIntent = PendingIntent.getActivity(ctx, id.toInt(), intent, PendingIntent.FLAG_CANCEL_CURRENT)
val builder = NotificationCompat.Builder(ctx, Channel.TEST_CHANNEL.channelId)
builder.setTicker(tickerText)
.setContentTitle(contentTitle)
.setContentText(contentText)
.setVibrate(vibrate)
.setSmallIcon(icon)
.setAutoCancel(true)
.setLights(-0xff0100, 300, 1000)
.setSound(uri)
.setContentIntent(pendingIntent)
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
.addAction(R.drawable.ic_notification, ctx.getString(R.string.notification), piAction)
val notification = builder.build()
val nf = ctx.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nf.notify(NOTIFICATION_TAG, id.toInt(), notification)
}
Starting recently we noticed that notifications on some device running Android 8+ started disappearing briefly after being shown, without user's interaction. Setting auto-cancel to false helps, but the user experience degrades.
The id is a unique item id from the database. This may be important thing to note - technically we can have a notification with such id be shown, removed/canceleld by user, and later some time used again for a similar notification with the same id. Can this be the reason?
We've updated the support libs and tried the following method on builder for luck:
builder.setTicker(tickerText)
...
.setTimeoutAfter(-1)
...
Setting this param to a positive value delayed the notification disappearing by that amount of time (so it did affect). Thus we tried a negative number, the notifications seem to stay there now.
I couldn't find any reasonable documentation explaining this, so this answer is not 100%, but keeping it here for now for others to try and see if it helps them.
Disable your application from auto optimize from battery optimization setting in android OREO. Notification will stay as long as you want
Only thing I found uncertain is NotificationCompat.Builder
Android oreo now uses Notification.Builder instead of NotificationCompat.Builder.
Might be you have to check android version like:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//Use Notification.Builder
} else {
// Use NotificationCompat.Builder.
}
I don't think unique id will be an issue for disappearing notification.
Google has created open source sample for this new changes. Please refer to it for more info.
https://github.com/googlesamples/android-NotificationChannels
.setAutoCancel(false)
May be it will work for you.
When testing parse.com push notifications (sent from CloudCode on afterSave), there is something weird.
Sometimes the Android app gets the notification immediately (below 1 second), but other times, it comes after a multi-second delay.
Restarting the app seems to cause the not-yet-received notifications to appear immediately.
What could be the cause?
Could this be a bug in, for example, the parse.com service?
Is there any limit of how many notifications can be sent or received (per unit of time) ?
The problem happens both with custom BroadcastReceiver and with the default system bar notification.
Server-side javascript CloudCode:
Parse.Cloud.afterSave("Timer", function(request) {
// from https://www.parse.com/docs/js/guide#cloud-code
console.log("Before Parse.Push.send -- without alert");
var query = new Parse.Query(Parse.Installation);
// http://blog.parse.com/announcements/pushing-from-the-javascript-sdk-and-cloud-code/ :
Parse.Push.send({
where: query,
data: {
//alert: "afterSave on a Timer -- Parse.Push.send"
}
});
console.log("After Parse.Push.send -- without alert");
});
Custom broadcast receiver in Kotlin (but the problem happens also without the custom BroadcastReceiver) :
override fun onCreate(savedInstanceState: Bundle?) {
super<BaseActivity>.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
// ...
registerReceiver()
}
private fun registerReceiver() {
val intentFilter = IntentFilter()
intentFilter.addAction("com.parse.push.intent.RECEIVE")
registerReceiver(MyBroadcastReceiver(), intentFilter)
}
inner class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context, "MyBroadcastReceiver 2: onReceive: "
+ context + ";" + intent, Toast.LENGTH_SHORT).show()
loadTimers()
}
}
We are using a non-paid parse.com account for now. Could that affect the timeliness of the reaction to the push notifications?
Edit: if you think, that using push notifications to trigger near-realtime item updates/sync, is not a good idea (either in general or in parse.com), that would also be a valuable answer, especially if an alternative is proposed...
At karolvrn's suggestion, here's my answer:
I don't think there's any guarantee of immediate delivery of push notifications.
https://developers.google.com/cloud-messaging/concept-options#setting-the-priority-of-a-message
You have two options for assigning delivery priority to downstream
messages: normal and high priority. Delivery of high and normal
priority messages works like this:
High priority. GCM attempts to deliver high priority messages immediately, allowing the GCM service to wake a sleeping device when
possible and open a network connection to your app server...
Normal priority. This is the default priority for message delivery...
I emphasized "attempts", which implies that it does not guarantee that the message will be delivered immediately.
Here's another developer's experience with reliability issues with GCM:
https://eladnava.com/google-cloud-messaging-extremely-unreliable/
I have a problem with appcelerator, regarding to android background service.
The service starts, and when I press the home button, the service is still running, and also when I click the back button.
But when I remove my app from the recent apps list(pressing long home button) , the service stops.
This is my code for the call service:
var SECONDS = 6;
// every 10 minutes
var intent = Titanium.Android.createServiceIntent({
url : 'myservice.js'
});
intent.putExtra('interval', SECONDS * 1000);
intent.putExtra('message_to_echo', num);
//in millimeter
var service = Titanium.Android.createService(intent);
service.addEventListener('resume', function(e) {
// num++;
// alert(num);
});
service.start();
And this is the file service code:
var service = Titanium.Android.currentService;
var intent = service.intent;
var message = intent.getStringExtra("message_to_echo");
var intent = Ti.Android.createIntent({
flags : Ti.Android.FLAG_ACTIVITY_CLEAR_TOP | Ti.Android.FLAG_ACTIVITY_SINGLE_TOP,
// Substitute the correct classname for your application
className : 'com.mappa.angeli.MappaActivity',
});
intent.addCategory(Ti.Android.CATEGORY_LAUNCHER);
// Create a PendingIntent to tie together the Activity and Intent
var pending = Titanium.Android.createPendingIntent({
intent: intent,
flags: Titanium.Android.FLAG_UPDATE_CURRENT
});
// Create the notification
var notification = Titanium.Android.createNotification({
// icon is passed as an Android resource ID -- see Ti.App.Android.R.
// icon: Ti.App.Android.R.drawable.my_icon,
contentTitle: 'Something Happened',
contentText : 'Click to return to the application.',
// tickerText: 'Our app made a notification!',
defaults:Titanium.Android.NotificationManager.DEFAULT_SOUND,
contentIntent: pending
});
// Send the notification.
Titanium.Android.NotificationManager.notify(0, notification);
How can I run the service continously, such as whats app or something like that?
Please help me, it's very important.
Thanks in advance!!!
First let me give you a little perspective about your questions so that you'll be able to understand the logic behind.
My Understanding about your Question and Conclusion
What you are asking for is not possible even in the native app, specifically if you are using the background services.
Why it is doing so?
Because the application is no longer in service anymore. The app is
forcefully removed from the background and hence the OS stops all its
services running in the background to clear the Memory.
What most of the default players do to play music is the use the foreground service and show a notification that the app is running and is processing something in the background (in this case playing the songs).
Options that you could look for
I would suggest you to add up 'push notification service' to your app rather than going in for 'background service'. You will get the notification even if your app is not in the background (same as it is in WhatsApp).
You could follow the below link to implement the GCM push services for android:
http://www.tidev.io/2013/12/20/using-gcm-for-android-push-notifications-with-acs/
Hope this helps.
Good Luck, Cheers