This time I need your help regarding the use of android navigation components with deeplink.
I have been following this documentation and the connection between fragment and deeplink is working fine.
The problem comes in regards to the activity that is receiving the deeplink. In my case, I set the android:launchMode="singleTask"
<activity android:name=".features.welcome.WelcomeActivity"
android:launchMode="singleTask">
<nav-graph android:value="@navigation/welcome_nav_graph" />
</activity>
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
Timber.d("onNewIntent: $intent with activity: $this")
navController.handleDeepLink(intent)
}
With this configuration I noticed a couple of weird behaviours:
WelcomeActivity receives onNewIntent call two times every time I click the deeplink. Having even sometimes new instances of that activity created.. like
1_ object1-onNewIntent
2_ object1-onNewIntent
3_ object2-onCreate
Here you have some logs:
First launch
onCreate: Intent { flg=0x10000000 cmp={applicationId}/{package}.WelcomeActivity } with activity: {package}.WelcomeActivity@4adbef0
Open deep link
onNewIntent: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=https://{depp_link}… flg=0x10010000 cmp={applicationId}/{package}.WelcomeActivity (has extras) } with activity: {package}.WelcomeActivity@4adbef0
onNewIntent: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=https://{depp_link}... flg=0x1001c000 cmp={applicationId}/{package}.WelcomeActivity (has extras) } with activity: {package}.WelcomeActivity@4adbef0
onCreate: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=https://{depp_link}... flg=0x1001c000 cmp={applicationId}/{package}.WelcomeActivity (has extras) } with activity: {package}.WelcomeActivity@b77c6b
Kill the app and open deep link
onCreate: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=https://{depp_link}... flg=0x10018000 cmp={applicationId}/{package}.WelcomeActivity (has extras) } with activity: {package}.WelcomeActivity@b78f4df
onNewIntent: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=https://{depp_link}... flg=0x1001c000 cmp={applicationId}/{package}.WelcomeActivity (has extras) } with activity: {package}.WelcomeActivity@b78f4df
onCreate: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=https://{depp_link}... flg=0x1001c000 cmp={applicationId}/{package}.WelcomeActivity (has extras) } with {package}.WelcomeActivity@dfe87b2
UPDATE:
1 -It seems launch mode has nothing to do with this issue. I noticed the same with default launch mode.
2- navController.navigate(intent.dataString.toUri()) seems to work fine. So I guess the problem is navController.handleDeepLink(intent).
Testing different changes, I came to the conclusion that "navController.handleDeepLink(intent)" is causing this weird behaviour.
This is what I tried:
I removed the deepLink from the navigation, and the deep link was working just fine (I have added deepLink manually) with a normal behaviour: using singleTask, if the activity is already created, then onNewIntent is called only once. If the activity is not created, then onCreate is called.
An extra problem with this is that navController.handleDeepLink(intent) will be called automatically in onCreate (you can check that in the javadocs). When onNewIntent is called, you need to call navController.handleDeepLink(intent).
I decided to try "navigate(Uri deepLink)" from NavController and I see that is working as expected (behaviour described in the first paragraph). Having this alternative method, I decided to make some changes:
class WelcomeActivity : AppCompatActivity(){
private val navController by lazy { findNavController(R.id.nav_host_fragment) }
private var deepLinkData: Uri? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Timber.d("onCreate: $intent with activity: $this")
val data = intent.data
intent.data = null
setContentView(R.layout.activity_welcome)
handleDeepLink(data)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
Timber.d("onNewIntent: $intent with activity: $this")
setIntent(intent)
val data = intent?.data
handleDeepLink(data)
}
private fun handleDeepLink(uri: Uri?) {
//TODO: there is an issue that will cause onNewIntent to be called twice when the activity is already present.
if (uri != null && deepLinkData.toString() != uri.toString() && navController.graph.hasDeepLink(uri)) {
//possible deep link for LoginFragment
deepLinkData = uri
navController.navigate(uri)
}
}
}
It is important to notice this block of code in onCreate:
val data = intent.data
intent.data = null
The reason for this is because if I need to prevent "navController.handleDeepLink(intent)" to be called as it will be called automatically if that information is present, causing the weird behaviour.
navController.graph.hasDeepLink(uri) will help you to see if your graph can handle that uri. If you do not use it, then "navigate(Uri deepLink)" will throw an exception.
Hope it can help you if you are running into the same problem. If you have more insights on this, feel free to leave some comments.
When an implicit deeplink is clicked the FLAG_ACTIVITY_NEW_TASK
is set with intent. And as per the documentation the backstack will be recreated to be in a good state. See the documentation here Implicit deep link.
If you do not want this behavior and do not want to change the intent flags you can handle the deeplink yourself rather than passing it to Navigation components. So if you are using AdvancedNavigationSample you need to remove handledeeplink
and use findNavController().navigate()
to the direction.
Also remember to set the launch mode for launcher activity as singleTask
to receive new intents on clicking deepink or notifications.
NavController.handledeeplink()
in sample withfun BottomNavigationView.navigateDeeplink(
navGraphIds: List<Int>,
fragmentManager: FragmentManager,
containerId: Int,
uri: Uri
) {
navGraphIds.forEachIndexed { index, navGraphId ->
val fragmentTag = getFragmentTag(index)
// Find or create the Navigation host fragment
val navHostFragment = obtainNavHostFragment(
fragmentManager,
fragmentTag,
navGraphId,
containerId
)
// Handle deeplink
val canHandleDeeplink = navHostFragment.navController.graph.hasDeepLink(uri)
if (canHandleDeeplink) {
if (selectedItemId != navHostFragment.navController.graph.id) {
selectedItemId = navHostFragment.navController.graph.id
}
navHostFragment.lifecycleScope.launchWhenResumed {
// Wait for fragment to restore state from backStack
// otherwise navigate will be ignored
// Ignoring navigate() call: FragmentManager has already saved its state
navHostFragment.navController.navigateOnce(uri)
}
}
}
}
intent.data
in a variable and set it to null so that navigation component can not handle it. Also do the same in onHandleNewIntent
The complete solution is implemented at this sample.
Pros:
intent.data
in PendingIntent.When callback on onNewIntent
arrive for the first time, just setup the flag intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
and pass mutated intent to handleDeepLink(intent);
This flag eliminated the second arrive of onNewIntent
callback due to reattaching to existing Activity
(with full reconstruction of backstack trace to your desired deep link destination) instead of launching new Activity
.
Details is in the source code of handleDeepLink
method.
It feels weird, but it actually seems to work as intended. The documentation regarding the implicit deep links says:
When triggering an implicit deep link, the state of the back stack depends on whether the implicit Intent was launched with the Intent.FLAG_ACTIVITY_NEW_TASK flag:
If the flag is set, the task back stack is cleared and replaced with the deep link destination....
If the flag is not set, you remain on the task stack of the previous app where the implicit deep link was triggered.
In your case I believe when you tap a link the intent has the flag Intent.FLAG_ACTIVITY_NEW_TASK
set, so the whole new stack is created. Currently the implementation just restarts the activity with the newly created stack to make sure the task state is consistent.
If you don't need this behavior, one possible workaround would be to clear the flag Intent.FLAG_ACTIVITY_NEW_TASK
from the intent before navigation component has a chance to handle it.
I experienced a similar issue and I was following NavigationAdvancedSample where I had BottomNavigationView
with multiple NavHostFragment
s. So posting my fix for those who has the same case.
More specifically, onCreate()
of my MainActivity was getting called twice when app was launched via a notification deeplink.
I had following flags set for the intent:
private fun getPendingIntent(data: Uri?): PendingIntent {
val intent = Intent(context, MainActivity::class.java)
intent.action = Intent.ACTION_VIEW
intent.data = data
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT)
}
And I didn't have android:launchMode="singleTask"
in the Manifest for the MainActivity, as it wasn't really helpful in my case.
I have fixed my problem first preventing navController.handleDeepLink(intent)
to be called automatically on onCreate()
of the MainActivity with following code (thanks to you):
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val uri = intent.data
intent.data = null
}
and then making the change in this PR in the NavigationExtensions
file which basically replaces navController.handleDeepLink(intent)
with navController.navigate(uri)
which was causing the Activity to be created twice as you noted in your post.
So the code to setup Navigation with BottomNavigationView
looks as follows in the MainActivity:
private fun setupBottomNavigation(uri: Uri?) {
val navGraphIds =
listOf(
R.navigation.all_integrations,
R.navigation.favourites,
R.navigation.settings
)
currentNavController = bottomNavView.setupWithNavController(
navGraphIds,
supportFragmentManager,
R.id.navHostContainer
)
uri?.let {
bottomNavView.handleDeepLinks(
navGraphIds,
supportFragmentManager,
R.id.navHostContainer,
it
)
}
}
User contributions licensed under CC BY-SA 3.0