80

When I send multiple push notifications, I need them to be all shown in the notification bar ordered by the time sent desc. I know I should use unique notification - I tried to generate random number but that did not solve my problem since I need them to be ordered. I tried to use AtomicInt and still don't have the desired result.

package com.mypackage.lebadagency;
import java.util.concurrent.atomic.AtomicInteger;

import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v4.app.NotificationCompat;
import android.util.Log;



import android.widget.RemoteViews;

import com.google.android.gms.gcm.GoogleCloudMessaging;

public class GCMNotificationIntentService extends IntentService {

  private AtomicInteger c = new AtomicInteger(0);
  public int NOTIFICATION_ID = c.incrementAndGet(); 

  private NotificationManager mNotificationManager;
  NotificationCompat.Builder builder;

  public GCMNotificationIntentService() {
    super("GcmIntentService");
  }

  public static final String TAG = "GCMNotificationIntentService";

  @Override
  protected void onHandleIntent(Intent intent) {
    Bundle extras = intent.getExtras();
    GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);

    String messageType = gcm.getMessageType(intent);

    if (!extras.isEmpty()) {
      if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR
          .equals(messageType)) {
        sendNotification("Send error: " + extras.toString());
      } else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED
          .equals(messageType)) {
        sendNotification("Deleted messages on server: "
            + extras.toString());
      } else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE
          .equals(messageType)) {

        for (int i = 0; i < 3; i++) {
          Log.i(TAG,
              "Working... " + (i + 1) + "/5 @ "
                  + SystemClock.elapsedRealtime());
          try {
            Thread.sleep(5000);
          } catch (InterruptedException e) {
          }

        }
        Log.i(TAG, "Completed work @ " + SystemClock.elapsedRealtime());

        sendNotification(""
            + extras.get(Config.MESSAGE_KEY));
        Log.i(TAG, "Received: " + extras.toString());
      }
    }
    GcmBroadcastReceiver.completeWakefulIntent(intent);
  }

  private void sendNotification(String msg) {

    Log.d(TAG, "Preparing to send notification...: " + msg);
    mNotificationManager = (NotificationManager) this
        .getSystemService(Context.NOTIFICATION_SERVICE);
    //here start
    Intent gcmintent = new Intent(this, AppGcmStation.class);
    gcmintent.putExtra("ntitle", msg);
    gcmintent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
    int requestID = (int) System.currentTimeMillis();
    //here end
    PendingIntent contentIntent = PendingIntent.getActivity(this, requestID,
        gcmintent, PendingIntent.FLAG_UPDATE_CURRENT);

    NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
        this).setSmallIcon(R.drawable.ic_launcher)
        .setContentTitle("my title")
        .setStyle(new NotificationCompat.BigTextStyle().bigText(msg))
        .setContentText(msg);
    mBuilder.setAutoCancel(true);
    mBuilder.setTicker(msg);
    mBuilder.setVibrate(new long[] { 1000, 1000, 1000, 1000, 1000 }); 
    mBuilder.setLights(Color.RED, 3000, 3000);
    mBuilder.setContentIntent(contentIntent);
    mBuilder.setDefaults(Notification.DEFAULT_SOUND);



    mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
    Log.d(TAG, "Notification sent successfully.");
  }
}

I need the BEST and simplest way to generate an int id which is incremental to assign it as the notification id.

fractalwrench
  • 4,028
  • 7
  • 33
  • 49
Zenogrammer
  • 803
  • 1
  • 6
  • 4
  • 1
    _"i tried to use atomicint and still dont have the desired result_" - What result did you get and how is it different from the desired result? – Ted Hopp Sep 07 '14 at 17:54
  • 2
    when i used atomic ,the android device recive all the notification but the notification list only show latest notification sent. – Zenogrammer Sep 07 '14 at 17:56

7 Answers7

114

You are using the same notification ID (the value is always 1) for all your notifications. You probably should separate out the notification ID into a separate singleton class:

public class NotificationID {
    private final static AtomicInteger c = new AtomicInteger(0);
    public static int getID() {
        return c.incrementAndGet();
    }
}

Then use NotificationID.getID() instead of NOTIFICATION_ID in your code.

EDIT: As @racs points out in a comment, the above approach is not enough to ensure proper behavior if your app process happens to be killed. At a minimum, the initial value of the AtomicInteger should be initialized from some activity's saved state rather than starting at 0. If the notification IDs need to be unique across restarts of the app (again, where the app process may be killed off), then the latest value should be saved somewhere (probably to shared prefs) after every increment and restored when the app starts.

Ted Hopp
  • 232,168
  • 48
  • 399
  • 521
  • thank you again sir but can you explain to me why it works when its in separate class and not working in my code ? – Zenogrammer Sep 07 '14 at 18:14
  • 2
    Your code calls `incrementAndGet` exactly once: when initializing `NOTIFICATION_ID`. Thereafter you just use the assigned value. Also, each time the service is re-created, it initializes `NOTIFICATION_ID` to 1. By moving the id generator to a separate class, it creates a value that persists beyond the lifetime of the service itself (and lasts for the duration of the app in memory). To be really secure that each notification generated by your app is unique, you maybe should persist the value in shared preferences (or somewhere else convenient) so it survives the entire process being shut down. – Ted Hopp Sep 07 '14 at 18:25
  • When the maximum value of Atomic integer is reached - will it automatically tip over into negative integers, thus creating an endless circle that requires no further measures? – AgentKnopf Feb 16 '15 at 10:20
  • 1
    @Zainodis - Yes, the same behavior will hold for `AtomicInteger` as it does for normal `int` values--continuous incrementing will eventually wrap around from `Integer.MAX_VALUE` to `Integer.MIN_VALUE`. – Ted Hopp Feb 16 '15 at 15:43
  • 21
    This is not a proper solution: if your app is killed then the ID will restart unless you save the previous state to somewhere between app starts (e.g. to shared prefs). – racs Jul 29 '15 at 03:16
  • I believe that should also be a static class – Joe Maher Jan 15 '16 at 00:23
  • @JoeMaher - If it's an inner class, yes, it should be `static`. I had in mind a top-level class. (That's why I used the word "separate".) – Ted Hopp Jan 15 '16 at 00:27
37

For anyone still looking around. I generated a timestamp and used it as the id.

import java.util.Date;
import java.util.Locale;

public int createID(){
   Date now = new Date();
   int id = Integer.parseInt(new SimpleDateFormat("ddHHmmss",  Locale.US).format(now));
   return id;
}

Use it like so

int id = createID();
mNotifyManager.notify(id, mBuilder.build());
  • so if you receive multiple notifications in the same second, it still cancels the previous one? – Raphael C Dec 13 '16 at 12:57
  • 2
    @RaphaelC you may want to add milliseconds to that format, so it'd be "ddHHmmssSS" – Paul Freez Jan 03 '17 at 12:10
  • 12
    @PaulFreez same goes if you receive multiple notifications in the same millisecond, but I agree that the odds are small. And in that case you also simply go for: ```int id = (int)System.currentTimeMillis();``` saves any parsing processing. – Raphael C Jan 03 '17 at 14:13
  • @RaphaelC I don't think that using simple cast from long to int would work, because System.currentTimeMillis() could return much bigger value than Integer.MAX_VALUE. – Paul Freez Jan 03 '17 at 14:48
  • 2
    @Paul Freez of course it works. It will just get truncated. – Raphael C Jan 04 '17 at 15:05
  • but then ure creating a logic that will truncate numbers and at some point on the future the delivery order wont be correct, so your approach is not robust @RaphaelC – marcos E. Apr 11 '17 at 08:10
  • 1
    @marcosE. it's not my approach, I was responding. see my 1st answer: "so if you receive multiple notifications in the same second, it still cancels the previous one". – Raphael C Apr 12 '17 at 08:51
  • You could even use `int id = (int)System.nanoTime()` – Nico Feulner Sep 11 '20 at 14:19
  • Be careful with this solution: It will cause a `NumberFormatException` for some days in the month due to it exceeding Int.MAX -> Example `2415073767` – jules Sep 24 '20 at 13:27
  • 4
    I don't recommend this approach. The user may change the system clock (or move between time zones, or have daylight savings) and unexpectedly cause id collisions. And multiple notifications sent in the same second will have the same id. – Bip901 Jan 17 '21 at 06:14
21

Maybe not the best, but definitely the simplest is to use current time.

int oneTimeID = (int) SystemClock.uptimeMillis();
mNotificationManager.notify(oneTimeID, mBuilder.build());

The good: this is the easiest way to get increasing ids.

The bad: time is a long and we're truncating it to half of that. This means that the counter will wrap around every 2'147'483'647 /1000(ms->s)/60(s->m)/60(m->h)/24(h->d) =~25 days.

SystemClock.uptimeMillis() has 2 advantages over currentTimeMillis:

  1. discounts all the milliseconds spent at deep sleep, what decreases amount of wraparounds.
  2. starts at 0 when the phone is restarted.
Agent_L
  • 4,960
  • 28
  • 30
18
private static final String PREFERENCE_LAST_NOTIF_ID = "PREFERENCE_LAST_NOTIF_ID";

private static int getNextNotifId(Context context) {
    SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
    int id = sharedPreferences.getInt(PREFERENCE_LAST_NOTIF_ID, 0) + 1;
    if (id == Integer.MAX_VALUE) { id = 0; } // isn't this over kill ??? hahaha!!  ^_^
    sharedPreferences.edit().putInt(PREFERENCE_LAST_NOTIF_ID, id).apply();
    return id;
}
Raphael C
  • 2,296
  • 1
  • 22
  • 22
  • 1
    shared preferences storage is done asynchronously, so between the time you store the value and until it actually gets stored, you might call this method again and getInt() will return the same int as before, this +1 will get you the same id as the previous notification. this might happen if you get push notifications at once and you call this method in rapid succession. – AndreiBogdan Mar 21 '18 at 11:49
  • 7
    @AndreiBogdan not really: when calling `apply`, the commit to disk is asynchronous while the in-memory data is updated immediately. If another editor on this SharedPreferences does a regular commit() while a apply() is still outstanding, the commit() will block until all async commits are completed as well as the commit itself. https://developer.android.com/reference/android/content/SharedPreferences.Editor.html#apply() in short, no this might not happen. I also made a unit test for this particular case, and you can easily check yourself that this does not happen. – Raphael C Mar 21 '18 at 12:45
  • 2
    I probably post this just for fun, but we have to respect, that your answer will not work in the case, your app sent notifications of number Integer.MAX_VALUE and then your id will reset to value 0 so it will override the first notifications with id 0. But this case will really never reached, cause you probably cant have so much notifications in android, but in theory... Forget about it, im just kidding XD Thank you for your good answer! P.S nice comment ^_^ – JonasPTFL Feb 01 '20 at 17:12
5

You can use a counter and store it in the SharedPreferences. This is an example in kotlin:

fun getNextNotificationId(context: Context) : Int {
    val sp = context.getSharedPreferences("your_shared_preferences_key", MODE_PRIVATE)
    val id = sp.getInt("notification_id_key", 0)
    sp.edit().putInt("notification_id_key", (id + 1) % Int.MAX_VALUE).apply()

    return id
}

it will get the id and it will store the next id (increased by 1), also if the id reaches the max value for an integer it will be reset to 0.

You can use it like this:

val notificationId = getNextNotificationId(applicationContext)
notificationManager.notify(notificationId, yourNotification)
Chuy47
  • 2,391
  • 1
  • 30
  • 29
5

You need to set either a unique tag (String) / id (integer)

Checkout this method in official documentaation

I suggest using any timestamp (SystemClock.uptimeMillis() / System.currentTimeMillis()) as a tag while notifying.

 notificationManager.notify(String.valueOf(System.currentTimeMillis()), 0, notification);
ImBatman
  • 306
  • 4
  • 5
  • It should be the best answer for this case, in case we need to handle push notification (dismiss programmatically) we should keep track of (tag, id). IMO for best practice, we should define id for the specific purpose instead of magic number 0 – NguyenDat Sep 04 '20 at 02:41
  • what happens if you notify() or cancel() a notifcation without passing a TAG, where you have 2 visible notifications with the same ID, but different tags? All both of them gonna be cancelled or updated? – Malachiasz Jan 05 '23 at 11:45
1

If someone reading this, there is a method here. it is better to specify a tag name also with id, so that it will help if you are bundling the part as a module to share with developers.

NOTE: The problem with assigning some random integer id is that, if any module or library uses the same id, your notification will be replaced by new notification data.

// here createID method means any generic method of creating an integer id
int id = createID();
// it will uniqly identify your module with uniq tag name & update if present.
mNotifyManager.notify("com.packagename.app", id, mBuilder.build());
Renjith Thankachan
  • 4,178
  • 1
  • 30
  • 47
  • 5
    Please specify what `createID()` is, it is confusing if one hasn't read the other answers. – user905686 Jul 20 '17 at 15:46
  • @I Am Batman. Thank you. Now I wonder how this answers the question? How does adding a tag affect order? And it doesn't solve the original problem of needing unique IDs. If you want to add this as extra information, consider replacing your answer with a comment to an answer. – user905686 Jul 22 '17 at 12:13
  • @user905686 using a unique tag name say, your module( a downloader library you created), some random number from the user's application code will not update/cancel your notification – Renjith Thankachan Jul 22 '17 at 13:12
  • @user905686 check the *NOTE* in my answer – Renjith Thankachan Jul 22 '17 at 13:14
  • @I Am Batman Well the probability of getting the same random number is negligible. If you want to use non-random IDs it might help but you still have to make sure the user doesn't use the same tag. – user905686 Jul 23 '17 at 19:24
  • @user905686 I answered this question for them who makes SDKs or libs, not for you I think ;), you can use jquery – Renjith Thankachan Jul 24 '17 at 04:02