9

I have an application that uses Urban Airship for push notification. When a notification arrives and the user clicks on it, activity A in my application should open and do something.

I've installed the BroadcastReceiver as is shown in the docs, and it's almost working.

  1. When my app is in the foreground I don't let the user see the notification at all, and just handle it automatically.
  2. When my app is not running at all, the activity opens up just fine.
  3. When my app is in the background (which always happens when A is the top activity), a second instance of Activity A is created.

This is, of course, a problem. I don't want two A activities, I just want one of them. Here's the relevant BroadcastReceiver code:

@Override
public void onReceive(Context ctx, Intent intent)
{
    Log.i(tag, "Push notification received: " + intent.toString());
    String action = intent.getAction();
    int notificationId = intent.getIntExtra(PushManager.EXTRA_NOTIFICATION_ID, -1);
    if(action.equals(PushManager.ACTION_NOTIFICATION_OPENED))
    {
        Intent intentActivity = new Intent(ctx, ActivityA.class);
        intentActivity.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        UAirship.shared().getApplicationContext().startActivity((intentActivity);
    }
}

UPDATE: I tried to bypass this bug by calling System.exit(0) when the user presses Back on Activity A. The process ended, but then it was restarted immediately! My BroadcastReceiver is not called again in the second instance. What's happening?

UPDATE 2: @codeMagic asked for more information about the app and activity A.

This app lets its user review certain items and comment on them. Activity A is started when the app is launched. If the user's session isn't valid any more, a Login activity is started. Once the user logs in, activity A becomes active again. A only has a "No items to review" message and a "Try now" button.

When the user logs in, the server starts sending push notifications whenever a new item is available for review. When the app gets the notification, activity A accesses the server and gets the next item to review. The item is shown in activity B. Once the review is submitted to the server, activity B finishes and activity A is again the top activity.

The server knows when a user is reviewing an item (because activity A fetched it), and doesn't send push notifications until the review is submitted - meaning a notification can't come if the user isn't logged in or if the user is viewing activity B.

While I agree there is a subtle race condition here, it is not causing the problem I'm seeing - in testing I am 100% positive there's no race condition - the push notification is only sent after Activity A becomes active again.

zmbq
  • 38,013
  • 14
  • 101
  • 171
  • is activity A part of your application or 3rd party activity such as camera .. ? – Mr.Me May 01 '13 at 21:57
  • I may be wrong but, from what you are describing, it doesn't sound like `Activity A` needs to be an `Activity` at all but maybe a `Service` or `BroadcastReceiver`. If all it does is watch for updates and notify `Activity B` or show a message if there are no items, you can show the message from `Activity B` – codeMagic May 02 '13 at 12:40
  • Activity A doesn't watch for updates, it waits for a user to click a button, then it starts an AsyncTask that checks for updates. You can't show a button and wait for a user in a Service. – zmbq May 02 '13 at 13:09

5 Answers5

24

The solution was to add a launchMode='singleTask' to the activity in AndroidManifest.xml . As a result, instead of a new activity, onNewIntent of the same activity instance is called.

zmbq
  • 38,013
  • 14
  • 101
  • 171
  • Such an excellent answer. Works superb. Just want to know which method gets called inside Fragment for this – VVB Nov 27 '15 at 09:54
  • To clarify, you mean singleTask for the *launching* activity, not the *launched* activity? Neither worked for me. – quantumpotato Oct 28 '16 at 23:19
  • Nice, I had been struggling with this for a while. I was using singleInstance and that worked until I tried to navigate back to the previous activity. Thanks! – wax911 Nov 03 '16 at 22:33
  • Works but only showing my xml design not calling any service call or other method – Shweta Nandha Aug 03 '17 at 12:07
3

You can use one of several Intent Flags. FLAG_ACTIVITY_REORDER_TO_FRONT being one of them. This will bring the Activity to the front of the stack if it is already in the stack and if not then it will create a new instance. I believe you will still need FLAG_ACTIVITY_NEW_TASK if you aren't calling it from an Activity

Intent.FLAG_ACTIVITY_CLEAR_TOP should also work. But this will clear any other Activities on the stack. It just depends on what other functionality you need. Look through the Intent Flags and see which of these will work best for you

codeMagic
  • 44,549
  • 13
  • 77
  • 93
  • No, it doesn't work. My activity is created twice (onCreate is called twice, with no onDestroy in the middle). – zmbq May 01 '13 at 22:28
  • What are you doing in `Activity A`? Why would your app always be in the background when that `Activity` is on the top of the stack? You have something going on that we don't know about because either of those `Flags` would keep from creating a new instance – codeMagic May 01 '13 at 22:32
  • I'll clarify in the question – zmbq May 02 '13 at 05:57
0

There are multiple scenarios when this could happen. One of them can be handled this way. Please see my answer here: https://stackoverflow.com/a/44117025/2959575

Ram Iyer
  • 1,621
  • 1
  • 23
  • 25
0

add those attributes in the Manifest - activity:

android:launchMode="singleTask"
android:taskAffinity=""
android:excludeFromRecents="true"

See https://developer.android.com/develop/ui/views/notifications/navigation

Dan Alboteanu
  • 9,404
  • 1
  • 52
  • 40
-2

Ok, two notes on this :

  • You can register a broadcast receiver via the manifest so it is independent of any parts of your app. and use a Singleton pattern (keep a static reference to your activity somewhere in your app) that way you can check if their is an activity viewing or not and process accordingly.

    // your activity A
    
    @Override
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        myActivityReference = this;
    }
    
    public void onPause() {
        super.onPause();
        if (isFinishing()) {
            myActivityReference = null;
        }
    }
    
  • or you can keep everything as it is and use activity lunching modes flags in your manifest such as singleTop, singleInstance ... etc. take a look here android activity lunch modes

JJD
  • 50,076
  • 60
  • 203
  • 339
Mr.Me
  • 9,192
  • 5
  • 39
  • 51