Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to resume android app without losing activities stack (or the app state) with deep linking?

I have this <intent-filter> that every time certain link is pressed it opens my app but the problem is it opens a new instance of my app. Is there anyway to trigger onResume() and just resume my app without losing its state or the activities stack?

This is the intent filter:

        <intent-filter>
            <data android:scheme="http" />
            <data android:scheme="https" />
            <data android:host="example.com" />
            <data android:pathPattern="/.*" />

            <action android:name="android.intent.action.VIEW" />

            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
        </intent-filter>

Update

Thanks to user David Wasser answer below I found answer:

So I created EntryActivity which is launched on top of gmail/inbox app:

public class EntryActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.entry_activity);

        Uri uriParams = getIntent().getData();

        Log.e("EntryActivity", uriParams.getHost() );
        Log.e("EntryActivity", uriParams.getQueryParameter("uid") + " " + uriParams.getQueryParameter("type") + " " + uriParams.getQueryParameter("token") );


        Intent startCategory = new Intent(this, GotEmailActivity.class);
        startCategory.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(startCategory);
        this.finish();
    }

}

Then when my app is opened at GotEmailActivity I send email to user with link to open app and GotEmailActivity has attribute android:launchMode="singleTop" in AndroidManifest so only 1 instance of it is opened:

    <!-- 
        Important: notice android:launchMode="singleTop"
        which seeks if an instance of this activity is already opened and
        resumes already opened instance, if not it opens new instance.
     -->
    <activity
        android:name=".presenters.register.email.GotEmailActivity"
        android:label="@string/title_activity_got_email"
        android:launchMode="singleTop" 
        android:theme="@android:style/Theme.Translucent.NoTitleBar" >

Now what is happening is that EntryActivity is opened ontop of Gmail app but it closes inmediatle but first launches GotEmailActivity which is already opened so attribute launchMode Singletop prevents a new instance of such activity.

like image 265
Jeka Avatar asked Dec 28 '15 15:12

Jeka


People also ask

Why does my activity disappear when I resize the app?

This behavior also occurs if an app already in multi-window mode gets resized. Your activity can handle the configuration change itself, or it can allow the system to destroy the activity and recreate it with the new dimensions.

Should you support multiple back stacks in your app?

In some cases, it might be helpful to maintain multiple back stacks at the same time, with the user moving back and forth between them. For example, if your app includes bottom navigation or a navigation drawer, multiple back stack support allows your users to switch freely between flows in your app without losing their place in any of them.

Why does the system keep killing apps in the background?

Otherwise the system Back behavior may be jarring to the user. If an app is in the background and the system needs to free up additional memory for a foreground app, the background app can be killed by the system to free up more memory.


1 Answers

You should create another Activity that you use as an entry point to your application when responding to that <intent-filter>. Something like this:

What you need is just a simple Activity that does nothing. Here is an example:

public class EntryActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Check to see if this Activity is the root activity
        if (isTaskRoot()) {
            // This Activity is the only Activity, so
            //  the app wasn't running. So start the app from the
            //  beginning (redirect to MainActivity)
            Intent mainIntent = getIntent(); // Copy the Intent used to launch me
            // Launch the real root Activity (launch Intent)
            mainIntent.setClass(this, MainActivity.class);
            // I'm done now, so finish()
            startActivity(mainIntent);
            finish();
        } else {
            // App was already running, so just finish, which will drop the user
            //  in to the activity that was at the top of the task stack
            finish();
        }
    }
}

Put your <intent-filter> on this activity, instead of your "launcher" Activity. Make sure that in the manifest the task affinity of this activity is the same as the task affinity of the other activities in your application (by default it is, if you haven't explicitly set android:taskAffinity).

When the <intent-filter> gets triggered, if your application is running, then the EntryActivity will be started on top of the topmost activity in your application's task and that task will be brought to the foreground. When the EntryActivity finishes, it will simply return the user to the topmost activity in your application (ie: wherever the user left it when it went into the background).

If your app was not running, the EntryActivity recognizes this and starts your app from the beginning, passing it the Intent containing the ACTION and DATA that triggered the <intent-filter>.

Should work.

like image 122
David Wasser Avatar answered Oct 03 '22 09:10

David Wasser