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.
Related
So example i have 3 fragment
fragment A with 1 edittext, and 1 button
fragment B with 1 textview, and 1 button
framgnet C with 2 textview and 1 button
fragment A with edittext data example "this is test" -> fragment B -> fragment C -> fragment A again
how to prevent fragment A recreate again when from fragment C, so the edittext input in fragment A not empty.
here my nav graph
<?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/my_nav"
app:startDestination="#id/firstFragment">
<fragment
android:id="#+id/firstFragment"
android:name="com.fjr.simplenavigation.FirstFragment"
android:label="fragment_first"
tools:layout="#layout/fragment_first" >
<action
android:id="#+id/action_firstFragment_to_secondFragment"
app:destination="#id/secondFragment"/>
</fragment>
<fragment
android:id="#+id/secondFragment"
android:name="com.fjr.simplenavigation.SecondFragment"
android:label="fragment_second"
tools:layout="#layout/fragment_second" >
<action
android:id="#+id/action_secondFragment_to_thirdFragment"
app:destination="#id/thirdFragment" />
<argument
android:name="data"
app:argType="string"
app:nullable="true" />
</fragment>
<fragment
android:id="#+id/thirdFragment"
android:name="com.fjr.simplenavigation.ThirdFragment"
android:label="fragment_third"
tools:layout="#layout/fragment_third">
<action
android:id="#+id/action_thirdFragment_to_firstFragment"
app:destination="#id/firstFragment"
app:popUpTo="#+id/firstFragment"
app:popUpToInclusive="true"/>
</fragment>
</navigation>
if i replace the code for action_thirdFragment_to_firstFragment to like this
<fragment
android:id="#+id/thirdFragment"
android:name="com.fjr.simplenavigation.ThirdFragment"
android:label="fragment_third"
tools:layout="#layout/fragment_third">
<action
android:id="#+id/action_thirdFragment_to_firstFragment"
app:popUpTo="#+id/firstFragment"
app:popUpToInclusive="false"/>
</fragment>
it will solve the problem, fragment A will not recreated and the edittext still have the input like on the description, but is there any other way?
the second question is how to passing the data back to fragment A (but with edittext still have an input like on the description)?
You can use either an Activity scoped or NavGraph scoped ViewModel, and store your variable in there.
class FragmentA : Fragment {
...
private val viewModel by activityViewModels<ViewModelA>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
myEditText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable) {
viewModel.text = s.toString()
}
})
}
}
class ViewModelA : ViewModel() {
...
var text: String = ""
}
In the above example, your ViewModel would live as long as your Activity lives. If you'd like to modify your ViewModel, you would reach it the same way in any other Fragment, and get the same instance of the ViewModelA
class FragmentC : Fragment {
...
private val viewModel by activityViewModels<ViewModelA>() <-- gives you the same instance of the ViewModel
}
Read more about "Sharing data between Fragments" here:
https://developer.android.com/topic/libraries/architecture/viewmodel#sharing
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
I'm using BottomNavigation in the main menu class to cycle through Fragments in the main screen. The weird thing about this is my app crashes because NavController doesn't find the destination in one of four fragments. MainMenuFragment is not the same as MainMenu (activity containing the fragment and NavHostFragment). Any idea why this is happening?
NavGraph: http://prntscr.com/pcfufk
Activity (MainMenu.java) screen http://prntscr.com/pcgcnv
In MainMenu.java:
private BottomNavigationView.OnNavigationItemSelectedListener listener = new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem menuItem) {
Bundle args = new Bundle();
switch (menuItem.getItemId()) {
case R.id.addorder:
args.putString("UID", UID);
controller.navigate(R.id.action_mainMenuFragment_to_addOrderFragment, args);
break;
case R.id.addcust:
bundle.putString("UID", UID);
controller.navigate(R.id.action_mainMenuFragment_to_addCustomerFragment);
break;
case R.id.barcodeScan:
controller.navigate(R.id.action_mainMenuFragment_to_addProductFragment);
break;
case R.id.home:
bundle.putString("UID", UID);
if (controller.getCurrentDestination() != null && controller.getCurrentDestination().getId() == R.id.mainMenuFragment) {
controller.navigate(R.id.mainMenuFragment, bundle);
}
break;
}
return true;
}
};
onCreate in MainMenu.java:
bundle.putString("UID", UID);
controller = Navigation.findNavController(this, R.id.fragment_container);
controller.navigate(R.id.mainMenuFragment, bundle);
NavGraph xml
<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_mainmenu"
app:startDestination="#id/mainMenuFragment">
<fragment
android:id="#+id/addCustomerFragment"
android:name="com.google.android.gms.samples.vision.barcodereader.fragments.customer.addCustomerFragment"
android:label="addcustomerfragment"
tools:layout="#layout/addcustomerfragment" />
<fragment
android:id="#+id/mainMenuFragment"
android:name="com.google.android.gms.samples.vision.barcodereader.fragments.mainmenu.mainMenuFragment"
android:label="mainmenufragment"
tools:layout="#layout/mainmenufragment">
<action
android:id="#+id/action_mainMenuFragment_to_addProductFragment"
app:destination="#id/addProductFragment"
app:enterAnim="#anim/fui_slide_in_right"
app:exitAnim="#anim/fui_slide_out_left"
app:popExitAnim="#anim/fui_slide_out_left"
app:popUpTo="#+id/mainMenuFragment" />
<action
android:id="#+id/action_mainMenuFragment_self"
app:destination="#id/mainMenuFragment" />
<action
android:id="#+id/action_mainMenuFragment_to_addOrderFragment"
app:destination="#id/addOrderFragment"
app:enterAnim="#anim/fui_slide_in_right"
app:exitAnim="#anim/fui_slide_out_left"
app:popUpTo="#+id/mainMenuFragment" />
<argument
android:name="UID"
app:argType="string"
app:nullable="false" />
<action
android:id="#+id/action_mainMenuFragment_to_addCustomerFragment"
app:destination="#id/addCustomerFragment"
app:enterAnim="#anim/fui_slide_in_right"
app:exitAnim="#anim/fui_slide_out_left"
app:popUpTo="#+id/navigation_mainmenu" />
</fragment>
<fragment
android:id="#+id/addProductFragment"
android:name="com.google.android.gms.samples.vision.barcodereader.fragments.products.addProductFragment"
android:label="addproductfragment"
tools:layout="#layout/addproductfragment" />
<fragment
android:id="#+id/additionalInfoFragment"
android:name="com.google.android.gms.samples.vision.barcodereader.fragments.orders.additionalInfoFragment"
android:label="addinfofragment"
tools:layout="#layout/addinfofragment">
<argument
android:name="UID"
app:argType="string" />
</fragment>
<fragment
android:id="#+id/addOrderFragment"
android:name="com.google.android.gms.samples.vision.barcodereader.fragments.orders.addOrderFragment"
android:label="choosecustomerfragment"
tools:layout="#layout/choosecustomerfragment">
<action
android:id="#+id/action_addOrderFragment_to_additionalInfoFragment"
app:destination="#id/additionalInfoFragment"
app:enterAnim="#anim/fui_slide_in_right"
app:exitAnim="#anim/fui_slide_out_left"
app:popUpTo="#+id/mainMenuFragment" />
<argument
android:name="UID"
app:argType="string" />
</fragment>
Using BottomNavigationView with NavigationUI using NavigationUI.setupWithNavigationController(controller) should solve the problem.
Links to sources:
https://medium.com/#vepetruskova/the-new-android-in-app-navigation-f7bfbe925b9
https://developer.android.com/guide/navigation/navigation-ui
I have implemented navigation Drawer with Navigation Components in Android. I have 5 fragments that I want to go back to my HomeFragment when I click on back pressed. For the moment they stay onBackStack and do not go to my desired fragment but go to whatever fragment was first.
This is my nav_graph :
<?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"
app:startDestination="#id/setupFragment"
android:id="#+id/treasure_nav"
android:label="Pick a country">
<fragment android:id="#+id/homeFragment"
android:name="com.stavro_xhardha.pockettreasure.ui.home.HomeFragment"
android:label="Home"
tools:layout="#layout/fragment_home">
<action android:id="#+id/action_home_fragment_to_namesFragment2"
app:popUpTo="#id/homeFragment"
app:destination="#id/namesFragment"/>
<action android:id="#+id/action_home_fragment_to_quranFragment"
app:popUpTo="#id/homeFragment"
app:destination="#id/quranFragment"/>
<action android:id="#+id/action_homeFragment_to_tasbeehFragment"
app:popUpTo="#id/homeFragment"
app:destination="#id/tasbeehFragment"/>
<action android:id="#+id/action_homeFragment_to_galleryFragment"
app:popUpTo="#id/homeFragment"
app:destination="#id/galleryFragment"/>
<action android:id="#+id/action_homeFragment_to_newsFragment"
app:popUpTo="#id/homeFragment"
app:destination="#id/newsFragment"/>
<action android:id="#+id/action_homeFragment_to_settingsFragment"
app:popUpTo="#id/homeFragment"
app:destination="#id/settingsFragment"/>
</fragment>
<fragment
android:id="#+id/namesFragment"
android:name="com.stavro_xhardha.pockettreasure.ui.names.NamesFragment"
android:label="Names of Allah"
tools:layout="#layout/fragment_names"/>
<fragment
android:id="#+id/quranFragment"
android:name="com.stavro_xhardha.pockettreasure.ui.quran.QuranFragment"
android:label="Quran"
tools:layout="#layout/fragment_quran"/>
<fragment android:id="#+id/tasbeehFragment"
android:name="com.stavro_xhardha.pockettreasure.ui.tasbeeh.TasbeehFragment"
android:label="Tasbeeh"
tools:layout="#layout/fragment_tasbeeh"/>
<fragment android:id="#+id/galleryFragment"
android:name="com.stavro_xhardha.pockettreasure.ui.gallery.GalleryFragment"
android:label="Gallery"
tools:layout="#layout/fragment_gallery"/>
<fragment android:id="#+id/newsFragment"
android:name="com.stavro_xhardha.pockettreasure.ui.news.NewsFragment"
android:label="News"
tools:layout="#layout/fragment_news"/>
<fragment android:id="#+id/settingsFragment"
android:name="com.stavro_xhardha.pockettreasure.ui.settings.SettingsFragment"
android:label="Settings"
tools:layout="#layout/fragment_settings"/>
<fragment android:id="#+id/setupFragment"
android:name="com.stavro_xhardha.pockettreasure.ui.setup.SetupFragment"
android:label="Pick country"
tools:layout="#layout/fragment_setup">
<action android:id="#+id/action_setupFragment_to_homeFragment3"
app:destination="#+id/homeFragment"
app:launchSingleTop="true"
app:popUpTo="#+id/treasure_nav"
app:popUpToInclusive="true"/>
</fragment>
</navigation>
And this is my onBackPressed in my MainActivity (and the only one) :
override fun onBackPressed() {
if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
drawer_layout.closeDrawer(GravityCompat.START)
} else {
super.onBackPressed()
}
}
Edit: When i remove the super.onBackPressed() and replace it with :
findNavController(R.id.nav_host_fragment).popBackStack(R.id.homeFragment, false) I achieve what I want. The only problem is that when I am in the homeFragment I want to end the app but I can't.
If my understanding is correct, you want to go back to HomeFragment wherever you are in the navigation flow. For this case you could try registering OnBackPressedCallback on your Fragments via addOnBackPressedCallback, and call popBackStack to navigate to your HomeFragment. Try adding this to Fragments' onViewCreated that need to go back to HomeFragment on backpress:
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
navController = Navigation.findNavController(view);
requireActivity().addOnBackPressedCallback(getViewLifecycleOwner(), () -> {
navController.popBackStack(R.id.homeFragment, false);
});
return true;
});
If you want to close app when press back in HomeFragment, it's just specified these attributes of the last action that navigates you to this destination:
app:popUpToInclusive to true
app:popUpTo to of the last fragment(SetupFragment) that navigates you here(HomeFragment)
It means change your code like this:
<fragment android:id="#+id/setupFragment"
android:name="com.stavro_xhardha.pockettreasure.ui.setup.SetupFragment"
android:label="Pick country"
tools:layout="#layout/fragment_setup">
<action android:id="#+id/action_setupFragment_to_homeFragment3"
app:destination="#+id/homeFragment"
app:launchSingleTop="true"
app:popUpTo="#+id/setupFragment" // this line changes
app:popUpToInclusive="true" /> // and this requires too
</fragment>
I have main navigation:
SplashFragment -> RegistrationFragment -> RootFragment
<fragment
android:id="#+id/splashFragment"
android:name="com.low6.low6.features.splash.SplashFragment"
android:label="Splash"
tools:layout="#layout/fragment_splash" >
<action
android:id="#+id/action_next"
app:clearTask="true"
app:destination="#id/registrationFragment" />
</fragment>
<fragment
android:id="#+id/registrationFragment"
android:name="com.low6.low6.features.registration.RegistrationFragment"
android:label="Register">
<action
android:id="#+id/action_next"
app:clearTask="true"
app:destination="#id/rootFragment" />
</fragment>
<fragment
android:id="#+id/rootFragment"
android:name="com.low6.low6.core.RootFragment"
android:label="#string/home"
tools:layout="#layout/fragment_root" />
And I have nested registration navigation:
RegistrationPersonalFragment -> RegistrationContactFragment -> RegistrationSecurityFragment
<fragment
android:id="#+id/registrationPersonalFragment"
android:name="com.low6.low6.features.registration.RegistrationPersonalFragment"
android:label="Register">
<action
android:id="#+id/action_next"
app:destination="#+id/registrationContactFragment" />
</fragment>
<fragment
android:id="#+id/registrationContactFragment"
android:name="com.low6.low6.features.registration.RegistrationContactFragment"
android:label="Register">
<action
android:id="#+id/action_next"
app:destination="#+id/registrationSecurityFragment" />
</fragment>
<fragment
android:id="#+id/registrationSecurityFragment"
android:name="com.low6.low6.features.registration.RegistrationSecurityFragment"
android:label="Register">
<action
android:id="#+id/action_next"
app:destination="#+id/rootFragment" />
</fragment>
How to redirect from the last nested RegistrationSecurityFragment to RootFragment using Jetpack Navigation component?
Currently
<action
android:id="#+id/action_next"
app:destination="#+id/rootFragment" />
And
navigateTo(R.id.action_next)
Gives me
java.lang.IllegalArgumentException: navigation destination com.xxx:id/rootFragment referenced from action com.xxx:id/action_next is unknown to this NavController
at androidx.navigation.NavController.navigate(NavController.java:691)
at androidx.navigation.NavController.navigate(NavController.java:648)
at androidx.navigation.NavController.navigate(NavController.java:634)
at com.xxx.core.BaseFragment.navigateTo(BaseFragment.kt:73)
at com.xxx.core.BaseFragment.navigateTo$default(BaseFragment.kt:66)
at com.xxx.features.registration.RegistrationSecurityFragment$epoxyController$1$$special$$inlined$button$lambda$1.onClick(RegistrationSecurityFragment.kt:106)
at android.view.View.performClick(View.java:6597)
at android.view.View.performClickInternal(View.java:6574)
at android.view.View.access$3100(View.java:778)
at android.view.View$PerformClick.run(View.java:25885)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
If you have more than one navigation graph, please make sure you're using the right navigation controller. Using Navigation.findNavController(view) in some cases you might need to get your root view to get the root's navigation. Hope, this'll help.
When you have nested NavControllers, findNavController() return only last. To get previous navControllers, you can traverse up using parentFragment property.
Extensions with this approach:
// find all nav controllers from closest to farest
fun Fragment.findAllNavControllers(): List<NavController> {
val navControllers = mutableListOf<NavController>()
var parent = parentFragment
while (parent != null) {
if (parent is NavHostFragment) {
navControllers.add(parent.navController)
}
parent = parent.parentFragment
}
return navControllers
}
// find one nav controller by fragment id
fun Fragment.findNavControllerById(#IdRes id: Int): NavController {
var parent = parentFragment
while (parent != null) {
if (parent is NavHostFragment && parent.id == id) {
return parent.navController
}
parent = parent.parentFragment
}
throw RuntimeException("NavController with specified id not found")
}
And usage:
findAllNavControllers()[2]
findNavControllerById(R.id.navHostFragment)
In your code, you can pass the resource ID of the global action to the navigate() method for each UI element.
your_button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Navigation.findNavController(view).navigate(R.id.main_fragment);
}
});
Thanks to #Vladimir answer I came up with this solution
val mainNavView = requireActivity().findViewById<View>(R.id.mainNavFragment)
Navigation.findNavController(mainNavView).navigate(R.id.action_next)
You can do it like this ;
in Child Fragment
// parentFragment = getParentFragment() for java
(parentFragment as MyParentFragment).myNavigationHandler(myArguments)
in Parent Fragment
fun myNavigationHandler(myArguments) {
Navigation.findNavController(binding.root)
.navigate(MyFragmentDirections.actionMyAction(myArguments))
}
findNavController().navigate(HostFragmentDirections.actionHostToOtherFragment())
HostFragment is just an example, which should be the name of host of your nested fragment.
For everyone struggling with navigating to somewhere from your nested fragment, this works.