28

I have a Notification, which supports play,pause forward and back.

private static Notification createNotification(String interpret, String title, boolean paused) {
//  if (builder == null)
       builder = new NotificationCompat.Builder(context);

    builder.setPriority(Notification.PRIORITY_MAX);
    builder.setAutoCancel(false);
    builder.setContentTitle(title);
    builder.setContentText(interpret);
    builder.setOngoing(true);
    builder.setOnlyAlertOnce(true);
    builder.setSmallIcon(R.drawable.ic_launcher);
    builder.setContentIntent(PendingIntent.getActivity(context, 9, new Intent(context, ApplicationActivity.class), Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT));
    builder.addAction(R.drawable.av_previous, "", PendingIntent.getBroadcast(context.getApplicationContext(), 0, new Intent(NotificationPlayerControlReceiver.MUSIC_PLAYER_INTENT).putExtra("resultcode", NotificationPlayerControlReceiver.PREVIOUS), PendingIntent.FLAG_CANCEL_CURRENT));

    if (paused)
        builder.addAction(R.drawable.av_play, "", PendingIntent.getBroadcast(context.getApplicationContext(), 2, new Intent(NotificationPlayerControlReceiver.MUSIC_PLAYER_INTENT).putExtra("resultcode", NotificationPlayerControlReceiver.PLAY), PendingIntent.FLAG_CANCEL_CURRENT));
    else
        builder.addAction(R.drawable.av_pause, "", PendingIntent.getBroadcast(context.getApplicationContext(), 3, new Intent(NotificationPlayerControlReceiver.MUSIC_PLAYER_INTENT).putExtra("resultcode", NotificationPlayerControlReceiver.PAUSE), PendingIntent.FLAG_CANCEL_CURRENT));

    builder.addAction(R.drawable.av_next, "", PendingIntent.getBroadcast(context.getApplicationContext(), 1, new Intent(NotificationPlayerControlReceiver.MUSIC_PLAYER_INTENT).putExtra("resultcode", NotificationPlayerControlReceiver.NEXT), PendingIntent.FLAG_CANCEL_CURRENT));

    Notification notification = builder.build();

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
        notification.tickerView = null;

    return notification;
}

Updating the notification:

 public static void update(String interpret, String title, boolean paused) {
    NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    manager.notify(0, createNotification(interpret, title, paused));
}

To avoid blinking on update, I´ve set the builder to a global variable and I reuse it on every update, which works great. but reusing it, means that also all buttons I´ve added are reused and there is no possibility to remove Actions I´ve added before.

The button change only works, if I reinitialize the NotificationCompat.Builder on every update, which means I get the blinking again.

How do I avoid blinking, but letting the button change?

EDIT: Just checked out Rocket Player, they didn´t solve the problem too, but Google Play Music did

Marian Klühspies
  • 15,824
  • 16
  • 93
  • 136
  • 1
    i don't understand how reusing the builder (which, btw, you don't in this code, probably a typo) can prevent the blinking, since the notification object is still a different one every time ? – njzk2 Sep 26 '13 at 10:03
  • 1
    I don´t know why, but it´s a fact. found it here http://stackoverflow.com/questions/6406730/updating-an-ongoing-notification-quietly – Marian Klühspies Sep 26 '13 at 10:16
  • my understanding from the question you posted is that what prevents blinking is the use of the same ID – njzk2 Sep 26 '13 at 10:31
  • no, this doesn´t prevent it! As I said, using always the same builder prevents it, but it won´t let me change the existing look – Marian Klühspies Sep 26 '13 at 11:44
  • are you absolutely certain of that ? because in its present state, you code does create a new builder at each run. – njzk2 Sep 26 '13 at 11:47
  • This are the two possibilities: 1.Comment out if (builder == null) to create each time a new builder object so that the button change can work but the notification is blinking, because it´s getting completely rebuild. 2.don´t comment if (builder == null) out to avoid blinking, but the buttons can´t be changed, they do persist in the actual state – Marian Klühspies Sep 26 '13 at 11:55
  • you could always use reflexion to access the builder's actions list, but there's got to be another way – njzk2 Sep 26 '13 at 12:03
  • out of curiosity, could you post the part where you send the notification to the manager ? – njzk2 Sep 26 '13 at 12:04
  • I mean, I´m not the first one implementing such a notification. Looking to other media players, there seems to be no problem to manage it. ok I´ll update the main thread – Marian Klühspies Sep 26 '13 at 12:05
  • This has been also my problem. I think the issue is that I create new notifiation, when I just want to refresh the UI. I am still searching for solution. – Boris Strandjev Oct 14 '13 at 15:58

3 Answers3

17

Like Boris said, the problem is that a new notification will be build every update. My solution covers the same logic, but I use the NotificationBuilder...

here is the code:

if (mNotificationBuilder == null) {
            mNotificationBuilder = new NotificationCompat.Builder(this)
                    .setSmallIcon(iconId)
                    .setContentTitle(title)
                    .setContentText(message)
                    .setLargeIcon(largeIcon)
                    .setOngoing(true)
                    .setAutoCancel(false);
        } else {
            mNotificationBuilder.setContentTitle(title)
                    .setContentText(message);
        }

keep in mind that mNotificationBuilder is a private field in the class.

Martin Pfeffer
  • 12,471
  • 9
  • 59
  • 68
9

The issue is that you create new notification every time you want to update. I had the same issue and it fixed when I did the following:

  • retain the instance of the notification inbetween different calls of createNotification.
  • set this instance to null every time it is removed from the notification bar.
  • do the following code:

Code:

private static Notification createNotification(String interpret, String title, boolean paused) {
   if (mNotification == null) {
       // do the normal stuff you do with the notification builder
   } else {
      // set the notification fields in the class member directly
      ... set other fields.
      // The below method is deprecated, but is the only way I have found to set the content title and text
      mNotification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
   }
   return mNotification;
}

And now when you call notify no blinking will appear:

manager.notify(0, createNotification(interpret, title, paused));

PS: I also faced a problem that if I executed setLatestEventInfo the large and small icons got scrwed up. That's why I did:

int tmpIconResourceIdStore = mNotification.icon;
// this is needed to make the line below not change the large icon of the notification
mNotification.icon = 0;
// The below method is deprecated, but is the only way I have found to set the content title and text
mNotification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
mNotification.icon = tmpIconResourceIdStore;

Looking into Adnroid ccode this line mNotification.icon = 0; disables the icon screw up.

Boris Strandjev
  • 46,145
  • 15
  • 108
  • 135
6

I know that this is a rather old question, but since I didn't found a solution anywhere else, I thought answering this now might help others with the same problem.

This problem is kind of tricky to begin with. I encountered it today as well, and being my stubborn self, I found a solution after searching and trying for a while.

How to solve this problem:

In order to be compatible with API-Levels lower than 19, my solution is to use the NotificationCompat classes from the support-library.

As suggested by others, I keep the reference to the NotificationCompat.Builder for as long as the notification is required. The actions I use in my Notification are only added upon initial creation of the Builder, and those actions that will change depending on the situation, I also store in a private member of the service. Upon change, I re-use the Builder object and adjust the NotificationCompat.Action object according to my needs. Then I call the Builder.getNotification() or Builder.build() method, depending on API-level (probably not necessary due to the support-libs, but I didn't check that. If I can omit that, please write a comment, so I can improve my code ;)

Here's an example code of what I just described above:

public Notification createForegroundNotification(TaskProgressBean taskProgressBean, boolean indeterminate) {
  Context context = RewardCalculatorApplication.getInstance();

  long maxTime = TaskUtils.getMaxTime(taskEntry);
  long taskElapsedTime = TaskUtils.calculateActualElapsedTime(taskProgressBean);
  long pauseElapsedTime = taskProgressBean.getPauseElapsedTime();

  int pauseToggleActionIcon;
  int pauseToggleActionText;
  PendingIntent pauseToggleActionPI;
  boolean pauseButton = pauseElapsedTime == 0;
  if(pauseButton) {
    pauseToggleActionIcon = R.drawable.ic_stat_av_pause;
    pauseToggleActionText = R.string.btnTaskPause;
    pauseToggleActionPI = getPendingIntentServicePause(context);
  } else {
    pauseToggleActionIcon = R.drawable.ic_stat_av_play_arrow;
    pauseToggleActionText = R.string.btnTaskContinue;
    pauseToggleActionPI = getPendingIntentServiceUnpause(context);
  }

  String contentText = context.getString(R.string.taskForegroundNotificationText,
      TaskUtils.formatTimeForDisplay(taskElapsedTime),
      TaskUtils.formatTimeForDisplay(pauseElapsedTime),
      TaskUtils.formatTimeForDisplay(taskProgressBean.getPauseTotal()));


  // check if we have a builder or not...
  boolean createNotification = foregroundNotificationBuilder == null;
  if(createNotification) { // create one
    foregroundNotificationBuilder = new NotificationCompat.Builder(context);

    // set the data that never changes...plus the pauseAction, because we don't change the
    // pauseAction-object, only it's data...
    pauseAction = new NotificationCompat.Action(pauseToggleActionIcon, getString(pauseToggleActionText), pauseToggleActionPI);
    foregroundNotificationBuilder
        .setContentTitle(taskEntry.getName())
        .setSmallIcon(R.drawable.ic_launcher)
        .setContentIntent(getPendingIntentActivity(context))
        .setOngoing(true)
        .addAction(R.drawable.ic_stat_action_done, getString(R.string.btnTaskFinish), getPendingIntentServiceFinish(context))
        .addAction(pauseAction);
  }

  // this changes with every update
  foregroundNotificationBuilder.setContentText(contentText);

  if(indeterminate) {
    foregroundNotificationBuilder.setProgress(0, 0, true);
  } else {
    foregroundNotificationBuilder.setProgress((int) maxTime, (int) taskElapsedTime, false);
  }

  // if this is not the creation but the button has changed, change the pauseAction's data...
  if(!createNotification && (pauseButton != foregroundNotificationPauseButton)) {
    foregroundNotificationPauseButton = pauseButton;
    pauseAction.icon = pauseToggleActionIcon;
    pauseAction.title = getString(pauseToggleActionText);
    pauseAction.actionIntent = pauseToggleActionPI;
  }

  return (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
         ? foregroundNotificationBuilder.getNotification() // before jelly bean...
         : foregroundNotificationBuilder.build(); // since jelly bean...
}

The variables foregroundNotificationBuilder, pauseAction and foregroundNotificationPauseButton are private members of the service class. The getPendingIntent...() methods are convenience methods that simply create the PendingIntent objects.

This method is then called when I need to update the notification using the NotificationManager, as well as handed over to the service's startForeground() method. This solves the flickering and the problems with the not updatable actions in the notification.

Mjoellnir
  • 486
  • 5
  • 11