Android navigation component passing argument with safeArgs and deep link - android

I have a navigation xml for a feature as below -
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/navigation_featureB.xml"
app:startDestination="#id/FragmentB">
<fragment
android:id="#+id/FragmentB"
android:name="FragmentB"
android:label="FragmentB">
<deepLink app:uri="App://FragmentB" />
</fragment>
</navigation>
In my FeatureA fragment, I do the following -
val uri = Uri.parse("App://FragmentB")
findNavController().navigate(uri)
I know how to use safeArgs without deep link.
How do I pass data to other feature with deep link?

You can do the following
First add argument in navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/navigation_featureB.xml"
app:startDestination="#id/FragmentB">
<fragment
android:id="#+id/FragmentB"
android:name="FragmentB"
android:label="FragmentB">
<argument
android:name="yourarg"
android:defaultValue="Argument Default Value"/>
<deepLink app:uri="App://FragmentB/{yourarg}" />
</fragment>
</navigation>
Then you can call it like this
val args = Bundle()
args.putString("yourarg", "Argument Value")
val deeplink = findNavController().createDeepLink()
.setDestination(R.id.FragmentB)
.setArguments(args)
.createPendingIntent()
val builder = NotificationCompat.Builder(
context, "deeplink")
.setContentTitle("Deep link with data")
.setContentText("Deep link with data to Android")
.setSmallIcon(R.drawable.ic_android)
.setContentIntent(deeplink)
.setAutoCancel(true)
val notificationManager =
context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(0, builder.build())
EDITED
Without Notification
First, add argument in navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/navigation_featureB.xml"
app:startDestination="#id/FragmentB">
<fragment
android:id="#+id/FragmentB"
android:name="FragmentB"
android:label="FragmentB">
<argument
android:name="yourarg"
android:defaultValue="Argument Default Value"/>
<deepLink app:uri="App://FragmentB/{yourarg}" />
</fragment>
</navigation>
Then you can call it like this
val uri = Uri.parse("App://FragmentB/yourarg")
findNavController().navigate(uri)
Then you can get the argument with this code
val argument = arguments?.getString("yourarg")
// Or with Safe Args
val safeArgs: FragmentBArgs by navArgs()
val yourarg = safeArgs.yourarg
References :
https://codelabs.developers.google.com/codelabs/android-navigation#9

Above answers shows how to works with string data types. But if you want to pass custom objects, safe-args also works well;
<fragment
android:id="#+id/verification_fragment"
android:name="com.appname.ui.verification.VerificationFragment"
android:label="VerificationFragment">
<argument
android:name="verificationType"
android:defaultValue="PHONE"
app:argType="com.appname.core.model.VerificationType" />
<deepLink app:uri="android-app://com.appname/verification_fragment?verificationType={verificationType}" />
</fragment>
Pass custom attributes to deeplink;
val request = NavDeepLinkRequest.Builder
.fromUri("android-app://appname/verification_fragment?verificationType=${VerificationType.MAIL}".toUri())
.build()
findNavController().navigate(request)
Read attributes from destination fragments;
private val args: VerificationFragmentArgs by navArgs()
args.verificationType

Related

Android Navigation Component - error No destination with ID

I have problem with navigation component.
I have 2 nav graph: nav1 and nav2
In nav2 start destination by default is FragmentA, but sometimes I need to start from FragmentB. I use this code (this code in MainActivity):
if(isTest) {
val navController = findNavController(navigationFragment())
val navGraph = navController.navInflater.inflate(R.navigation.nav)
navGraph.startDestination = R.id.fragmentB
findNavController(navigationFragment()).graph = navGraph
}
But there is error like this -> java.lang.IllegalArgumentException: No destination with ID 2131362873 is on the NavController's back stack
nav2 code here:
android:id="#+id/nav2"
app:startDestination="#id/FragmentA">
<fragment
android:id="#+id/FragmentA"
android:name="......./FragmentA"
android:label="FragmentA" />
<fragment
android:id="#+id/FragmentB"
android:name="......./FragmentB"
android:label="FragmentB" />
From nav1 action:
<action
android:id="#+id/action_global_link_test"
app:destination="#id/nav2"
app:enterAnim="#anim/enter_from_right"
app:exitAnim="#anim/exit_from_left"
app:popEnterAnim="#anim/enter_from_left"
app:popExitAnim="#anim/exit_from_right" />

How can I add startDestination programmatically accoridng the value?

How can I add startDestination programmatically according the value for example if the integer variable was 0 then I want to start navigation_main_countries_fragment, Otherwise navigation_main_categories_fragment.
Inside activity_main
<androidx.fragment.app.FragmentContainerView
android:id="#+id/activity_main_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:navGraph="#navigation/navigation_main" //I deleted this line already
android:layout_weight="1" />
Inside navigation_main
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
app:startDestination="#id/navigation_main_countries_fragment" //I deleted this line already
tools:ignore="InvalidNavigation">
<fragment
android:id="#+id/navigation_main_countries_fragment"
android:name="com.test.app.fragments.CountriesFragment"
tools:layout="#layout/fragment_countries" />
<fragment
android:id="#+id/navigation_main_categories_fragment"
android:name="com.test.app.fragments.CategoriesFragment"
tools:layout="#layout/fragment_categories" />
</navigation>
Try this code
// Configure the navigation
val navHost = nav_host_fragment as NavHostFragment
graph = navHost.navController.navInflater.inflate(R.navigation.nav_graph)
if(value == 0)
graph.startDestination = R.id.counriesFragment
else
grapgh.startDestination = R.id.categoryFragment
navHost.navController.graph = graph
NavigationUI.setupActionBarWithNavController(this, navHost.navController)
You can use below code for start destination
val navHostFragment = (supportFragmentManager.findFragmentById(R.id.home_nav_fragment) as NavHostFragment)
val inflater = navHostFragment.navController.navInflater
val graph = inflater.inflate(R.navigation.nav_main)
graph.startDestination = R.id.fragment1
navHostFragment.navController.graph = graph

Too many arguments for public final fun actionConformationFragmentToHomeFragment()

I want to send my arguments to another fragment using NavDirections
<fragment
android:id="#+id/conformationFragment"
android:name="com.example.panoramic.ui.ConformationFragment"
android:label="ConformationFragment"
tools:layout="#layout/fragment_conformation">
<action
android:id="#+id/action_conformationFragment_to_registerProductFragment"
app:destination="#id/registerProductFragment" />
<action
android:id="#+id/action_conformationFragment_to_homeFragment"
app:destination="#id/homeFragment" />
<argument
android:name="modelNumber"
app:argType="string" />
<argument
android:name="serialNumber"
app:argType="string" />
</fragment>
Here is how I attempt to send arguments:
class ConformationFragment : Fragment(R.layout.fragment_conformation) {
private var fragmentConformationBinding: FragmentConformationBinding? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = FragmentConformationBinding.bind(view)
fragmentConformationBinding = binding
val modelNumber = binding.modelValue.text.toString()
val serialNumber = binding.serialValue.text.toString()
binding.editingInformationButton.setOnClickListener {
findNavController().navigate(R.id.action_conformationFragment_to_registerProductFragment)
}
binding.confirmButton.setOnClickListener {
findNavController().navigate(ConformationFragmentDirections
.actionConformationFragmentToHomeFragment(modelNumber, serialNumber))
}
}
override fun onDestroyView() {
fragmentConformationBinding = null
super.onDestroyView()
}
}
I am sure dependencies are correct, but still, the error appears:
Too many arguments for public final fun actionConformationFragmentToHomeFragment(): NavDirections defined in com.example.panoramic.ui.ConformationFragmentDirections.Companion
and red line under modelNumber and serialNumber in actionConformationFragmentToHomeFragment() function
I checked other similar question but none of them works for me.
The issue is that the arguments you defined in your navigation graph file are accepted by ConformationFragment and not by HomeFragment.
Arguments should be defined under <fragment> tag of a fragment that is accepting defined arguments, so every other navigation action to this fragment would accept arguments. This is how the navigation graph should be declared regarding ConformatioFragment and HomeFragment:
<fragment
android:id="#+id/conformationFragment"
android:name="com.example.panoramic.ui.ConformationFragment"
android:label="ConformationFragment"
tools:layout="#layout/fragment_conformation">
<action
android:id="#+id/action_conformationFragment_to_registerProductFragment"
app:destination="#id/registerProductFragment" />
<action
android:id="#+id/action_conformationFragment_to_homeFragment"
app:destination="#id/homeFragment" />
</fragment>
<fragment
android:id="#+id/homeFragment"
android:name="com.example.panoramic.ui.HomeFragment"
android:label="HomeFragment"
tools:layout="#layout/fragment_home">
<argument
android:name="modelNumber"
app:argType="string" />
<argument
android:name="serialNumber"
app:argType="string" />
</fragment>
After these changes, you should be able to pass arguments into actionConformationFragmentToHomeFragment() function.

Passing Multiple Agument to fragment using TypeSafe in Navigation

I know it is possible to pass a single argument using Action in the source fragment
override fun onClick(v: View) {
val amountTv: EditText = view!!.findViewById(R.id.editTextAmount)
val amount = amountTv.text.toString().toInt()
val action = SpecifyAmountFragmentDirections.confirmationAction(amount)
v.findNavController().navigate(action)
}
and get that in the destination fragment as specified in android docs
val args: ConfirmationFragmentArgs by navArgs()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val tv: TextView = view.findViewById(R.id.textViewAmount)
val amount = args.amount
tv.text = amount.toString()
}
please let me know is there any way to pass multiple arguments in TypeSafe way
Yes, you could do it by defining multiple arguments for your fragment in the Navigation graph and then pass them to the action in your code. This is an example:
navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/navigation"
app:startDestination="#id/firstFragment">
<fragment
android:id="#+id/firstFragment"
android:name="com.example.app.FirstFragment"
android:label="FirstFragment"
tools:layout="#layout/fragment_first">
<action
android:id="#+id/action_firstFragment_to_secondFragment"
app:destination="#id/secondFragment"
app:enterAnim="#anim/slide_in_right"
app:popExitAnim="#anim/slide_out_left" />
</fragment>
<fragment
android:id="#+id/secondFragment"
android:name="com.example.app.SecondFragment"
android:label="secondFragment"
tools:layout="#layout/fragment_second">
<argument
android:name="firstDataList"
app:argType="com.example.app.domain.FirstData[]" />
<argument
android:name="secondDataList"
app:argType="com.example.app.domain.SecondData[]" />
<argument
android:name="isOkey"
app:argType="boolean" />
<argument
android:name="myString"
app:argType="string" />
</fragment>
</navigation>
and then in your code:
You should pass the arguments respectively as it is navigation.xml
FirstFragment.kt
findNavController().navigate(
FirstFragmentDirections.actionFirstFragmentToSecondFragment(
firstDataList.toTypedArray(),
secondDataList.toTypedArray(),
isOkey,
myString
)
And then retrieve it at the destination as a bundle like so:
SecondFragment.kt
val args = arguments?.let {
SecondFragmentArgs.fromBundle(
it
)
}
if (args != null) {
firstDataList = args.firstDataList.toCollection(ArrayList())
secondDataList = args.secondDataList.toCollection(ArrayList())
isOkey = args.isOkey
myString = args.myString
}
To pass complex objects you should make them parcelable. In my example, I passed two lists of complex models that I parcelized them like this:
DataModels.kt
#Parcelize
data class FirstData(var id: Int, var color: Int = 0) : Parcelable
#Parcelize
data class SecondData(var name: String, var position: ArrayList<Int>) : Parcelable

Is it possible to set startDestination conditionally using Android Navigation Architecture Component(Android Jetpack)?

I am using Navigation from Android Jetpack to navigate between screens.
Now I want to set startDestination dynamically.
I have an Activity named MainActivity
And two Fragments, FragmentA & FragmentB.
var isAllSetUp : Boolean = // It is dynamic and I’m getting this from Preferences.
If(isAllSetUp)
{
// show FragmentA
}
else
{
//show FragmentB
}
I want to set above flow using Navigation Architecture Component. Currently I have used startDestionation as below but it’s not fulfilling my requirement.
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/lrf_navigation"
app:startDestination="#id/fragmentA">
<fragment
android:id="#+id/fragmentA"
android:name="com.mindinventory.FragmentA"
android:label="fragment_a"
tools:layout="#layout/fragment_a" />
</navigation>
Is it possible to set startDestination conditionally using Android Navigation Architecture Component?
Finally, I got a solution to my query...
Put below code in onCreate() method of Activity.
Kotlin code
val navHostFragment = (supportFragmentManager.findFragmentById(R.id.home_nav_fragment) as NavHostFragment)
val inflater = navHostFragment.navController.navInflater
val graph = inflater.inflate(R.navigation.nav_main)
//graph.addArgument("argument", NavArgument)
graph.setStartDestination(R.id.fragment1)
//or
//graph.setStartDestination(R.id.fragment2)
navHostFragment.navController.graph = graph
Java code
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.home_nav_fragment); // Hostfragment
NavInflater inflater = navHostFragment.getNavController().getNavInflater();
NavGraph graph = inflater.inflate(R.navigation.nav_main);
//graph.addArgument("argument", NavArgument)
graph.setStartDestination(R.id.fragment1);
navHostFragment.getNavController().setGraph(graph);
navHostFragment.getNavController().getGraph().setDefaultArguments(getIntent().getExtras());
NavigationView navigationView = findViewById(R.id.navigationView);
NavigationUI.setupWithNavController(navigationView, navHostFragment.getNavController());
Additional Info
As #artnest suggested, remove the app:navGraph attribute from the layout. It would look something like this after removal
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/home_nav_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true" />
</FrameLayout>
In the case of a fragment tag used instead of FragmentContainerView, the above changes remain the same
Some of the APIs have changed, are unavailable or are not necessary since Akash's answer. It's a bit simpler now.
MainActivity.java:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NavHostFragment navHost = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_main_nav_host);
NavController navController = navHost.getNavController();
NavInflater navInflater = navController.getNavInflater();
NavGraph graph = navInflater.inflate(R.navigation.navigation_main);
if (false) {
graph.setStartDestination(R.id.oneFragment);
} else {
graph.setStartDestination(R.id.twoFragment);
}
navController.setGraph(graph);
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!-- Following line omitted inside <fragment> -->
<!-- app:navGraph="#navigation/navigation_main" -->
<fragment
android:id="#+id/fragment_main_nav_host"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
navigation_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<!-- Following line omitted inside <navigation>-->
<!-- app:startDestination="#id/oneFragment" -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/navigation_main"
>
<fragment
android:id="#+id/oneFragment"
android:name="com.apponymous.apponymous.OneFragment"
android:label="fragment_one"
tools:layout="#layout/fragment_one"/>
<fragment
android:id="#+id/twoFragment"
android:name="com.apponymous.apponymous.TwoFragment"
android:label="fragment_two"
tools:layout="#layout/fragment_two"/>
</navigation>
This can be done with navigation action. Because fragmentA is your start destination, so define an action in fragmentA.
Note these two fields:
app:popUpToInclusive="true" app:popUpTo="#id/fragmentA"
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/lrf_navigation"
app:startDestination="#id/fragmentA">
<fragment
android:id="#+id/fragmentA"
android:name="com.mindinventory.FragmentA"
android:label="fragment_a"
tools:layout="#layout/fragment_a">
<action android:id="#+id/action_a_to_b"
app:destination="#id/fragmentB"
app:popUpToInclusive="true"
app:popUpTo="#id/fragmentA"/>
<fragment>
<fragment
android:id="#+id/fragmentB"
android:name="com.mindinventory.FragmentB"
android:label="fragment_b"
tools:layout="#layout/fragment_b"/>
</navigation>
When your MainActivity started, just do the navigation with action id, it will remove fragmentA in the stack, and jump to fragmentB. Seemingly, fragmentB is your start destination.
if(!isAllSetUp)
{
// FragmentB
navController.navigate(R.id.action_a_to_b)
}
this is not an answer but Just a replication of #Akash Patel answer in more clean and clear way
// in your MainActivity
navController = findNavController(R.id.nav_host_fragment)
val graph = navController.navInflater.inflate(R.navigation.nav_graph)
if (Authentication.checkUserLoggedIn()) {
graph.startDestination = R.id.homeFragment
} else {
graph.startDestination = R.id.loginFragment
}
navController.graph = graph
You can set your starting destination programmatically, however, most of the time your starting logic will consult some remote endpoint. If you don't show anything on screen your UI will look bad.
What I do is always show a Splash screen. It will determine which is the next Screen to Show.
For instance, in the picture above you can ask in the Splash Screen State if there is a saved LoginToken. In case it's empty then you navigate to the Login Screen.
Once the Login Screen is done, then you analyze the result save the Token and navigate to your Next Fragment Home Screen.
When the Back Button is Pressed in the Home Screen, you will send back a Result message to the Splash Screen that indicates it to finish the App.
To pop 1 Fragment back and navigate to another you can use the following code:
val nextDestination = if (loginSuccess) {
R.id.action_Dashboard
} else {
R.id.action_NotAuthorized
}
val options = NavOptions.Builder()
.setPopUpTo(R.id.loginParentFragment, true)
.build()
findNavController().navigate(nextDestination, null, options)

Categories

Resources