13

I have a BroadcastReceiver that listen to incoming SMS'. If the message is from a certain sender, the BroadcastReceiver starts my app with the following code:

final Intent activityIntent = new Intent(context, MainActivity.class);
activityIntent.putExtra("smsChallenge", smsText);
activityIntent.putExtra("smsSenderNumber", senderMobilNumber);
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
activityIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
context.startActivity(activityIntent);

In the MainActivity of my app (i.e. in onCreate()), I extract the value smsChallenge out of the intent and DELETE IT AFTER THE EXTRACTION with the following code:

Bundle extras = getIntent().getExtras();
if (extras != null) {
    smsChallenge = extras.getString("smsChallenge");
    extras.remove("smsChallenge");
}

So my app gets started from the SMS and runs fine... But if I choose to press the BACK button and restart the application (i.e. through the Taskmanager), the value smsChallenge is still in the bundle extras. This means, my restarted app thinks that it is re-started because of a new SMS which is not true...

Any ideas why removing the key-value from the bundle doesn't seem to work when using the BACK button and restarting the app again?

flx
  • 14,146
  • 11
  • 55
  • 70
pfust75
  • 401
  • 1
  • 5
  • 17
  • I have noticed that when you click back and come forward again the activity is launched with a new intent. If you don't click back and simply navigate away and come back via the recent apps list then the launching intent is the same as before, i.e. the one for which flags were modified. – PJL Feb 21 '14 at 15:18
  • Try this final Intent activityIntent = new Intent(context, MainActivity.class); activityIntent.putExtra("smsChallenge", smsText); activityIntent.putExtra("smsSenderNumber", senderMobilNumber); activityIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(activityIntent); – ankurdayalsingh Mar 02 '18 at 05:44

7 Answers7

9

Because getExtras() creates a copy of intent extras.

You have to do like this

getIntent().removeExtra("smsChallenge");

Docs : http://developer.android.com/reference/android/content/Intent.html#removeExtra(java.lang.String)

Ajay S
  • 48,003
  • 27
  • 91
  • 111
  • 11
    Doesn't work if you go back. A new intent is passed through with the original extras intact. – PJL Feb 24 '14 at 09:35
  • I was trying to do exactly the same thing as the poster of the question (I've subsequently worked around this). I had an extra flag in the passed in intent which I removed using getIntent().removeExtra("....")`. However, on clicking back and then re-selecting the app the activity was being destroyed and the intent that is used to invoke the activity again was not the same one as the original intent above. This new intent had all the extras intact, i.e. including the one that was removed previously. – PJL Feb 25 '14 at 08:31
  • 4
    If the people marking this up have verified that it works then please provide additional details as I can't get it to work. After removing extras and clicking back the activity is destroyed and then re-selecting the activity from the recent apps list results in a new intent with the original extras intact (tested Nexus5, Nexus 10). – PJL Feb 27 '14 at 17:03
  • even if this solution works avoid using it. Even if you successfully remove extras then how are you planning to handle orientation change ? You will then have to work around by over riding onSavedInstance and onRestoreInstance, so one work around will lead to another and then another. A better solution will be to use proper context to launch the activity and hence avoid such workarounds all together – Anirudha Agashe Feb 28 '14 at 05:19
  • 1
    Had an issue with orientation changes where I wanted to remove an init value from an intent. This solution worked perfectly for me: `getIntent().removeExtra("KEY");` removed the key and wasn't shown up after orientation change. In contrast `Bundle bundle = getIntent().getExtras(); bundle.remove("KEY")` didn't remove the key. So it might do the trick for some of you :) Thx to _TGMCians_ – Trinimon Sep 19 '14 at 13:06
8

I think that you can not fix this behavior because by the time you get access to the intent, the os would have already saved the intent for later launch via recent apps.

  1. You can either add a flag to remove from recent apps, but in that way the app will not appear in the recent apps.
  2. Add in persistent storage the last sms that was handled in your MainActivity and do this check in your onCreate, discarding an intent for an sms that was already handled.
  3. A final solution would be not to send the data in the intent at all. For example, save them in a "pending" persistent storage. When your MainActivity starts, let it check for pending stuff, and clear this pending item.

Persistent storage could be SharedPreferences for example.

I would advise you to use 2 but you should count the sms received and use that counter as an id for the sms. Do not rely on the sms content and sender as an identifier for the sms.

Sherif elKhatib
  • 45,786
  • 16
  • 89
  • 106
  • I agree, I don't think you can fix this behavior because it is a different intent that is passed in after back is pressed and the activity is launched again from recent apps. A examination of Android sources would reveal what is actually going on. – PJL Feb 28 '14 at 09:26
  • using context.getApplicationContext() results in the activity being destroyed and it launches new activity with no extras when selected from the list of the apps – Anirudha Agashe Feb 28 '14 at 09:28
  • I had the same issue. I changed my approach, by storing the boolean value in prefs as true, and checking it in onCreate() or onResume(), and once the operation is done, change the boolean value in prefs to false. – Vamsi Challa Mar 24 '15 at 06:31
3

"The Intent is always the original intent used to launch the activity, nothing more. This is immutable." Dianne Hackborn

To work around the problem, first make sure you declare your activity launchMode as singleTop in the manifest.

Then from the following talk, try to use onSaveInstanceState to record the fact that you have 'nulled' out some of the extras. Then in onCreate, examine its 'Bundle savedInstanceState' parameter. If it is not null, it is the Bundle you returned in the onSaveInstanceState earlier. From this Bundle, you can decide to ignore these extras or not.

Edit: In your case using shared preferences will allow you to persist state even if the activity is destroyed.

public YourActivity extends Activity {

 private boolean extrasClearedOut;
 private SharedPreferences mPrefs;
 private static String EXTRA_CLEAR_OUT = "extras_cleared_out";

 public void onCreate(Bundle savedInstanceState) {
     mPrefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());

     extrasClearedOut = mPrefs.getBoolean(EXTRA_CLEAR_OUT, false);

     if (savedInstanceState != null && savedInstanceState.getBoolean(EXTRA_CLEAR_OUT, false))  {
         extrasClearedOut = true;
     }

     Intent intent = getIntent();
     if (extrasClearedOut) {
       // ignore the extras in the intent.

     }

     else {
       // Read and use the extras in the intent.
     }
 }

 protected void onNewIntent (Intent intent)  {
    super.onNewIntent(intent);
    setIntent(intent);

    if (extrasClearedOut) {
       // ignore the extras in the intent.
    }
    else {
       // Read and use the extras in the intent.
    }
 }

 public void someMethod(...) {
    extrasClearedOut = true;
 }

 protected void onSaveInstanceState (Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putBoolean(EXTRA_CLEAR_OUT, extrasClearedOut);
 }

 public void onDestroy()
 {
    final Editor edit = mPrefs.edit();
    edit.putBoolean(EXTRA_CLEAR_OUT, extrasClearedOut);
    edit.commit();
 }

}

Here is another working solution to your question

Community
  • 1
  • 1
Gomino
  • 12,127
  • 4
  • 40
  • 49
  • Clicking back results in the activity being destroyed and there is no call to `onSaveInstanceState`. – PJL Feb 27 '14 at 16:58
  • So why don't you use the shared preference to put the name of each extra you want to remove, then check them out in on create of your activity ? so that you will be able to know if you need to use it or not. – Gomino Feb 27 '14 at 17:03
  • Thanks, there are ways to work around this problem (and I've found one unique to my situation) but I strongly believe that the reason that removing extras does not work (which is the reason for the question) is that the activity is destroyed and a new intent (which could be an original one that is never passed) is used to launch the activity again. I'm sure a quick examination of the Android sources would reveal what's actually happening. – PJL Feb 28 '14 at 09:22
  • Have you read the quote I have posted from diane hackborn, one of the core android framework engineer? My solution is still valid though, why the down vote? – Gomino Feb 28 '14 at 09:47
  • I have heard of Diane Hackborn, however, you cannot use `onSaveInstanceState` to record that you have removed extras after hitting back because `onSaveInstanceState` does NOT get called. – PJL Feb 28 '14 at 15:21
  • That was the reason why I have edited my answer and told you to use sharedPreference in that case. Anyway the answer is NO you can't. – Gomino Feb 28 '14 at 15:52
  • OK, it only became obvious to me after reading the edit that the shared preferences was being used as a work-around. Removed unvote. – PJL Feb 28 '14 at 16:22
1

I think you need to explicitly set the intent of the activity for the behavior you want. I'm not sure why getIntent() appears to be returning a copy.

From the docs for Activity.setIntent()

Change the intent returned by getIntent(). This holds a reference to the given intent; it does not copy it. Often used in conjunction with onNewIntent(Intent).

http://developer.android.com/reference/android/app/Activity.html#setIntent(android.content.Intent)

ghostfly
  • 728
  • 5
  • 12
0
private void removeExtraFromBundle(String key){
    Bundle bundle = getArguments();
    if(bundle != null){
        String value = null;
        value = bundle.getString(key);
        if(value != null){
            bundle.remove(key);
        }
    }
}

The above code will help you to remove a specific key from a bundle object.

DanKodi
  • 3,550
  • 27
  • 26
0

My advice is adding a third extra to the intent with some nonce, e.g. just a random String:

final Intent activityIntent = new Intent(context, MainActivity.class);
activityIntent.putExtra("smsChallenge", smsText);
activityIntent.putExtra("smsSenderNumber", senderMobilNumber);
activityIntent.putExtra("nonce", UUID.randomUUID().toString);
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
activityIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
context.startActivity(activityIntent);

Then check if you got this nonce before in onCreate():

public void onCreate(final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Intent = getIntent();
    smsChallenge = intent.getStringExtra("smsChallenge", null);
    String nonce = intent.getStringExtra("nonce", null);
    if (nonce == null) {
      // should not happen, but do something sane here
    }
    SharedPreferences sp = PreferenceManager.getDefaultPreferences(this);
    if (nonce.equals(sp.getString("nonce", null)) {
      // app was restarted
    } else {
      // app was started from a shiny fresh intent
      sp.edit().putString("nonce", nonce).commit();
    }
}

But keep in mind, that orientation changes etc. are calling onCreate() too. But you can come around that quite easily by checking if savedInstanceState is null.

flx
  • 14,146
  • 11
  • 55
  • 70
0

Removing the extra is not the correct way to solve this problem. This problem is occurring due to the context that is being used to launch the said activity. Change this

final Intent activityIntent = new Intent(context, MainActivity.class);

to this

final Intent activityIntent = new Intent(context.getApplicationContext(), MainActivity.class);

the activity will now be finished when the you press the back button(provided you have not overloaded the function and changed its behavior). If you will select the application from the list of apps it will not contain any extras.

Edit:

Avoid launching activities from receiver. Consider this: You are on MainActivity. You receive sms. Now instead of the ui being updated to reflect new message you will see a new activity being launched which will not be a good user interface design.

Alternative approach: Update your database or do whatever relevant actions need to be done. Then locally broadcast the information. In your activity register a receiver for this broadcast and refresh the UI or do whatever is needed. If you want to notify the user instead of launching activity create a notification.

Anirudha Agashe
  • 3,510
  • 2
  • 32
  • 47
  • Well i am using application context to start Activity yet it does not work. Intent is same and not cleared as expected – Jemshit Jul 28 '17 at 07:04