Links are opening in Android webview creating multiple instances - android

I am working on an Android webview app and opening www.xyz.com in it.
When the app is running in the background and if I try to open Whatsapp/SMS received message-www.xyz.com/example then it is opening in a new instance and not opening in the already running instance which is in background.
And when I tried using android:launchMode="singleTask" it resumes www.xyz.com only instead of opening www.xyz.com/example.
Below is my AndroidManifest.xml
<activity android:name=".MainActivity" android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="www.xyz.com"
android:scheme="https"
/>
</intent-filter>
</activity>
Below is the MainActivity.java code -
protected void onResume() {
super.onResume();
Uri data = getIntent().getData();
if (data != null && data.isHierarchical()) {
String uri = this.getIntent().getDataString();
myWebView.loadUrl(uri);
Log.i("MyApp", "Deep link clicked " + uri);
}
}
Please let me know what I am doing wrong? Thank You.

By using launchMode="singleTask" and according to Android docs (see https://developer.android.com/guide/components/activities/tasks-and-back-stack):
The system creates a new task and instantiates the activity at the
root of the new task. However, if an instance of the activity already
exists in a separate task, the system routes the intent to the
existing instance through a call to its onNewIntent() method, rather
than creating a new instance. Only one instance of the activity can
exist at a time.
Add to your main activity an onNewIntent handler where you should see the new URL being passed in newIntent:
#Override
protected void onNewIntent(Intent newIntent)
{
// reload WebView
}
Additionally, I'm not sure that using onResume to mywebView.loadUrl() is a good or necessary practice, as even simple pause/resume events would lead to the page being reloaded. I'd suggest loading it in onCreate(), and reloading it in onNewIntent().

Related

Why starting the Activity by an intent (Intent.ACTION_VIEW) doesn't always start a new activity and call onCreate()?

I have two Android applications.
The first application is the "browser". It gets an URL and displays it in a WebView. The corresponding activity is declared as:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
</intent-filter>
</activity>
The second application has a few buttons. Tapping each button opens the first application and sends the URL to it using Intent.ACTION_VIEW:
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.parse(urlString);
intent.setData(uri);
intent.setComponent(new ComponentName("com.custom.browser", "com.custom.browser.MainActivity"));
startActivity(intent);
I expect this code to start a new activity as per https://developer.android.com/reference/android/app/Activity. So the browser application retrieves the URL in onCreate() by using the code:
#Override
protected void onCreate(Bundle savedInstanceState) {
...
Intent intent = getIntent();
if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
displayUrl(intent.getDataString());
}
...
}
However I found that sometimes onCreate() is not called. After calling startActivity(intent), the browser application is just brought to the front, therefore displaying a previous URL.
I can override this behavior by moving the intent retrieval code in the onResume().
However I'd like to understand what am I doing wrong? Shouldn't the method startActivity(Intent) always start a new activity and always call onCreate(), as suggested by the Android documentation?
I expect this code to start a new activity
That is not necessarily what will happen.
However I found that sometimes onCreate() is not called. After calling startActivity(intent), the browser application is just brought to the front,
Yes, that will happen if the activity you are starting is already running at the front of a task. See the documentation for tasks.
I can override this behavior by moving the intent retrieval code in the onResume().
That will not work. Override onNewIntent() and get the new Intent there. Or, adjust the flags in your Intent, or adjust the manifest settings for the activity that you are starting, as is discussed in the documentation for tasks.

Android 8.0+ Deep Linking Opens Launcher Activity First Only When App was Killed

On Android 8.0+ only, when opening my app with a deep link (clicking on the push notification with a URI attached) the app will open my entrypoint activity and then open up the deep linking activity.
This is not seen on Android lower than 8.0. In that case, it will open the deep linking activity directly and it will not open the splash screen activity.
Unfortunately, the way the app works is that when the entrypoint activity is called (it is a splash screen) it will then move to another activity. This makes it highly unreliable to open the deep link when the app was hard closed because when the deep link activity runs (approximately 80ms after the splash screen runs) the app is already transitioning to the next activity which may cancel out the deep link entirely.
To circumvent this, I've tried to handle the deep linking inside of the splash activity with setting the launchMode to singleTask, singleTop and singleInstance. Unfortunately, none of those worked.
I've managed to solve this issue by using a timer by holding the splash screen for 200ms longer than usual but that solution seems dirty and doesn't seem extremely reliable for all devices.
Here is the my splash activity and the deep linking activity in the manifest
<activity
android:name=".activities.LauncherActivity"
android:excludeFromRecents="true"
android:taskAffinity="${launcherAffinity}"
android:theme="#android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".activities.ParseDeepLinkActivity"
android:theme="#android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<data
android:host="*"
android:scheme="com.deeplink" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="deep.link.net"
android:pathPrefix="/mobile" />
</intent-filter>
</activity>
This is my launcher activity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_launcher);
startActivity(
new Intent(this, LoginActivity.class)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
finish();
}
I would like it if I can just open the deep link activity directly as it does on lower than 8.0. I tried searching for any documentation if deep linking has changed in 8.0+ but there was nothing I could find.
Solved!
The issue was in UrbanAirship. Our class that extends AirshipReceiver in the method onNotifactionOpened was default returning false which means it will also launch the launcher activity. Why this causes an issue in only 8.0+ I am not too sure. Here is how I resolved it
#Override
protected boolean onNotificationOpened(#NonNull Context context, #NonNull NotificationInfo notificationInfo) {
Map<String, ActionValue> notificationActions = notificationInfo.getMessage().getActions();
// Return false here to allow Urban Airship to auto launch the launcher activity
try{
return notificationActions.containsKey("^d");
}catch(Exception e){
return false;
}
}
^d is the default key for deep link actions as listed here
https://docs.airship.com/reference/libraries/android/latest/reference/com/urbanairship/actions/DeepLinkAction.html

How to disable TECH_DISCOVERED on the app?

I used DeepLink to launch my application after detecting a certain url address from NFC like this.
<activity
android:name=".view.main.MainActivity"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<action android:name="android.nfc.action.TECH_DISCOVERED" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="example.com"
android:scheme="http" />
</intent-filter>
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="#xml/nfc_tech_filter" />
</activity>
However, it works even when I already launched my application. So, duplicate activities keep coming up. And sometimes, it leads my app to malfunction or crash. I just want to use Deeplink while I am not using my application.
Is there any way to solve this problem?
You can use launchMode="singleTop". This way, if the activity already is launched and is on the top of the stack, any new launches would be routed through this instance and you would get this as a callback in the onNewIntent() method.
<activity android:name=".view.main.MainActivity"
android:launchMode="singleTop" />
Dinesh's solution seems like it should work, but if it's giving you problems, you can try an alternative (more complicated) one. You can use the NfcAdapter.enableForegroundDispatch() method to take control of the android.nfc.action.TECH_DISCOVERED action inside your activity and make sure it does nothing.
You can that by adding the following two methods to your MainActivity (or adding the code to existing ones):
#Override
protected void onResume() {
super.onResume();
//create a broadcast intent that doesn't trigger anything
Intent localIntent = new Intent("fake.action");
localIntent.setPackage(getApplicationContext().getPackageName());
localIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
//set the NFC adapter to trigger our broadcast intent
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
nfcAdapter.enableForegroundDispatch(
this,
PendingIntent.getBroadcast(this, 0, localIntent, PendingIntent.FLAG_UPDATE_CURRENT),
new IntentFilter[] {new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)},
new String[][] { { "android.nfc.tech.IsoDep" /* add other tag types as necessary */ } });
}
#Override
protected void onPause() {
super.onPause();
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
nfcAdapter.disableForegroundDispatch(this);
}
Now whenever your activity is visible and an NFC tag is detected, no action will be triggered. (Or, more specifically, it will trigger a broadcast to which no method is registered.)
Hope this helps.

Multiple Instances of the same app are generated in stack

If there is an app has a login activity, and it launches by the icon click. this login activity can be launched by another intent too. the problem is when the activity is running, (launched by touching the app icon) and when it receives the different intent call it starts another login activity.
how can i launch the login activity again after closing the current running activity, when the different intent call is received to launch the login activity.
the different intent mentioned above is, when the user select an specific file with specific extension, my app must be started.
<activity
android:name=".login.Login"
android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:scheme="content"
android:mimeType="application/octet-stream"
android:pathPattern=".*\\.chat"
tools:ignore="AppLinkUrlError" />
</intent-filter>
</activity>
The second intent is called , my app downloads the selected file and stores it in internel storage.
this is the onCreate method in the Login Activity
public void onCreate(Bundle savedInstanceState) {
if (getResources().getBoolean(R.bool.portrait_only)) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
super.onCreate(savedInstanceState);
if (isTaskRoot()) {
Uri data = getIntent().getData();
if (data != null) {
getIntent().setData(null);
try {
importData(data);
} catch (Exception e) {
// warn user about bad data here
finish();
return;
}
}
}
...................................
}
when user selects a specified file, the app launches another activity. then there are two login activities in the stack. help me to get rid of this
singleinstance, singletask solutions were tried. but when the launchingmode is set to singleinstance or singletask and the second intent call is received it does not call the
importData()
method. so the file that i want to download is not downloaded..
Here I used "singleTask" as launchMode. Then I have to use onNewIntent() when the Activity is called again. below link gives some more Explanation.
“onCreate()” method is not called from another intent filter

Dropbox Core API for Android: Not returning after Authentication

I am developing two android applications:
the first is a normal application with a launcher and so on
the other is a application only with a viewer activity (see the manifest):
<activity
android:name=".MyActivity"
android:icon="#drawable/icon"
android:label="#string/dropbox" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name="com.dropbox.client2.android.AuthActivity"
android:configChanges="orientation|keyboard"
android:launchMode="singleTask" >
<intent-filter>
<data android:scheme="db-XXXXX" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
The plan is, that the first application does not need internet permissions and the second is some kind of add-on to the first.
The second application should sync a file with Dropbox (with the Core API, not with the Sync API).
From my first app, I start 'MyActivity' from the second app like this:
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setComponent(new ComponentName("my.package","my.package.MyActivity"));
intent.putExtra("filePath", "/myFile.txt");
startActivityForResult(intent, SYNC_REQUEST);
That works. The Activity comes up and there, if not authorized yet, the user must press a button. Then the following code will be executed
AndroidAuthSession session = buildSession();
mApi = new DropboxAPI<AndroidAuthSession>(session);
mApi.getSession().startAuthentication(MyActivity.this);
If the user does not have dropbox installed, the browser will pop up.
Now my troubles begin:
As soon as the user presses 'Accept' or 'Decline', the browser does not disappear. It stays open and MyActivity does not get resumed (onResume is not called!).
I found out, that adding
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
before starting MyActivity from my first application will solve the problem, but then I can not listen/wait for the result of MyActivity.
I am frustrated. Can anyone help me or give me some advise?
Thanks!
When you are using startAuthentication method. It will automatically start the AuthActivity. you do not need to call startActivity() explicitly. Then in onResume method of your activity write this code :
#Override
protected void onResume()
{
super.onResume();
if (if dropBoxAPI!= null && dropboxAPI.getSession().authenticationSuccessful())
{
try {
dropboxAPI.getSession().finishAuthentication();
oAuth2Token = dropboxAPI.getSession().getOAuth2AccessToken();
}
catch (IllegalStateException ie)
{
ie.printStackTrace();
}
}
}

Categories

Resources