13

I'm currently writing an app with the new (to me) navigation component. I've got the basics down with a single navigation graph to navigate around my app, I've got a fragment with a BottomNavigationView in which has 3 seperate fragments, I've managed to update this to use the navigation component (as far as my problem) using the menu with ids that match the navigation items. My fragments which all previously used newInstance methods to pass a bundle to the onCreate are obviously now not used but I still need to pass a bundle to my fragments.

I haven't been able to find any examples of this being done, as the fragments are implicitly created.

My code is structured as ClientFragment which is the host fragment for the navigation drawer etc which is;

class ClientFragment : Fragment() {

    private val viewModel: ClientViewModel by viewModel()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_client, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

       viewModel.client = arguments?.getParcelable(ARG_CLIENT)!!

        toolbar_client.title = viewModel.client.name
        toolbar_client.setNavigationOnClickListener { Navigation.findNavController(view).navigateUp() }
    }
}

This class previously held on onclick listener to my fragments, with a newInstance method which tool viewModel.client.

My fragments in the nav_graph are all similar. The first fragment;

class ClientDetailsFragment : Fragment() {

    private val viewModel: ClientViewModel by viewModel()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_client_details, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

       // viewModel.client = arguments?.getParcelable(ARG_CLIENT)!!

        initClientDetails()
    }

    private fun initClientDetails() {
//        text_client_details_name.text = viewModel.client.name
//        text_client_details_account_number.text = viewModel.client.accountNumber
//        text_client_details_mobile_number.text = viewModel.client.mobileNumber
//        text_client_details_landline_number.text = viewModel.client.landlineNumber
//        text_client_details_email.text = viewModel.client.email
//        text_client_details_address.text = "NOT YET IMPLEMENTED"
//
//        text_client_description_body.text = viewModel.client.description
//        text_client_system_details_body.text = viewModel.client.systemDetails
    }
}

The app crashes on the commented out line;

// viewModel.client = arguments?.getParcelable(ARG_CLIENT)!! 

My navigation graph and menu are;

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/client_nav_graph"
    app:startDestination="@id/clientDetailsFragment">

    <fragment
        android:id="@+id/clientCustomersFragment"
        android:name="com.management.engineering.alarm.alarmengineermanagement.features.client.ClientCustomersFragment"
        android:label="ClientCustomersFragment"
        tools:layout="@layout/fragment_client_customers" />

    <fragment
        android:id="@+id/clientDetailsFragment"
        android:name="com.management.engineering.alarm.alarmengineermanagement.features.client.ClientDetailsFragment"
        android:label="ClientDetailsFragment"
        tools:layout="@layout/fragment_client_details"/>

    <fragment
        android:id="@+id/clientJobHistoryFragment"
        android:name="com.management.engineering.alarm.alarmengineermanagement.features.client.ClientJobHistoryFragment"
        android:label="ClientJobHistoryFragment"
        tools:layout="@layout/fragment_client_job_history" />

</navigation>

menu;

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/clientDetailsFragment"
        android:icon="@drawable/ic_launcher_foreground"
        android:title="Details"/>
    <item
        android:id="@+id/clientJobHistoryFragment"
        android:icon="@drawable/ic_launcher_foreground"
        android:title="Job History"/>
    <item
        android:id="@+id/clientCustomersFragment"
        android:icon="@drawable/ic_launcher_foreground"
        android:title="Customers"/>
</menu>

I have found that you can add arguments to the navigation graph, but have found nothing about where to put them for this specific scenario, I'm also aware of being able to manual add bundles when navigating using .navigate.

Is there a way for me to set in my ClientFragment the arguments for each of these fragments to be

viewModel.client

Update:

My argument issue was solved by using a view model that's shared between all of the fragments in the BottomNavigationView (I realised this as I was typing the issue out to my friend) and the navigation itself I added this to the ClientFragment;

bottom_nav_client.setupWithNavController(
            Navigation.findNavController(
                    view.findViewById<View>(R.id.fl_client_nav_container)
            )
    )

and my xml for fragment_client;

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar_client"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navigationIcon="?attr/NavigationBackIconLight"
        app:titleTextColor="@color/white" />

    <fragment
        android:id="@+id/fl_client_nav_container"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@id/bottom_nav_client"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/toolbar_client"
        app:navGraph="@navigation/client_nav_graph" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_nav_client"
        android:layout_width="match_parent"
        android:layout_height="56dp"
        android:background="?android:attr/windowBackground"
        app:itemBackground="@color/colorPrimary"
        app:itemIconTint="@drawable/bottom_nav_color"
        app:itemTextColor="@color/white"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:menu="@menu/client_menu" />

</androidx.constraintlayout.widget.ConstraintLayout>

This is combined with the same navigation graph and menu as shown above.

Daniel Sims
  • 531
  • 4
  • 21

4 Answers4

12

The Codelabs referred to in the accepted answer don't mention passing arguments to fragments in the BottomNavigationView.

Override the OnNavigationItemSelectedListener set by the setupWithNavController() with a custom one:

val args = Bundle()

bottomNavigationView.setupWithNavController(navController)
bottomNavigationView.setOnNavigationItemSelectedListener { item ->
    navController.navigate(item.itemId, args)
    true
}
Bram Stoker
  • 1,202
  • 11
  • 14
0

if you are using NavigationComponent :

@Override
    public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
        NavigationUI.onNavDestinationSelected(menuItem, navController);
}

you use this function, inside onNavDestiationSelected function you can see that navigation component passes null to args, so if you wanna pass argument to bottomNavigation fragments, simply you can write navigation function byyourselft and the only change is pass your arguments. as follow: create a package level function :

fun onNavDestinationSelected(item: MenuItem, navController: NavController, args: Bundle?): Boolean {
    val builder = NavOptions.Builder().setLaunchSingleTop(true).setRestoreState(true)
    if (
            navController.currentDestination!!.parent!!.findNode(item.itemId)
                    is ActivityNavigator.Destination
    ) {
        builder.setEnterAnim(R.anim.nav_default_enter_anim)
                .setExitAnim(R.anim.nav_default_exit_anim)
                .setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
                .setPopExitAnim(R.anim.nav_default_pop_exit_anim)
    } else {
        builder.setEnterAnim(R.animator.nav_default_enter_anim)
                .setExitAnim(R.animator.nav_default_exit_anim)
                .setPopEnterAnim(R.animator.nav_default_pop_enter_anim)
                .setPopExitAnim(R.animator.nav_default_pop_exit_anim)
    }
    if (item.order and Menu.CATEGORY_SECONDARY == 0) {
        builder.setPopUpTo(
                navController.graph.findStartDestination().id,
                inclusive = false,
                saveState = true
        )
    }
    val options = builder.build()
    return try {
        // TODO provide proper API instead of using Exceptions as Control-Flow.
        navController.navigate(item.itemId, args, options)
        // Return true only if the destination we've navigated to matches the MenuItem
        navController.currentDestination?.hierarchy?.any { it.id == item.itemId } == true
    } catch (e: IllegalArgumentException) {
        false
    }
}

and use it inside onNavigationItemSelected function as follow :

    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
        OnNavDestinationSelectKt.onNavDestinationSelected(menuItem, navController, yourArgument);
}
Sanam Yavarpor
  • 348
  • 3
  • 10
-1

The Navigation Architecture Component documentation shows how to define destination arguments, in your concrete case you should create a custom Parcelable class (i.e. Client) and include it as an argument of the corresponding fragment.

<fragment
    android:id="@+id/clientCustomersFragment"
    android:name="com.management.engineering.alarm.alarmengineermanagement.features.client.ClientCustomersFragment"
    android:label="ClientCustomersFragment"
    tools:layout="@layout/fragment_client_customers" >

    <action
        android:id="@+id/client_to_details"
        app:destination="@+id/clientDetailsFragment" />

</fragment>

<fragment
    android:id="@+id/clientDetailsFragment"
    android:name="com.management.engineering.alarm.alarmengineermanagement.features.client.ClientDetailsFragment"
    android:label="ClientDetailsFragment"
    tools:layout="@layout/fragment_client_details">

    <argument
        android:name="client"
        app:argType="com.management.engineering.alarm.alarmengineermanagement.features.client.Client" />

</fragment>

The 'androidx.navigation.safeargs' gradle plugin will generate the classes ClientToDetails and ClientDetailsFragmentArgs which can be used to pass.retrieve the parameter client.

Source

val client: Client = TODO()
val navController: NavController = TODO()
navController.navigate(ClientToDetails(client))

Destination

val client =  ClientDetailsFragmentArgs.fromBundle(arguments).client
Omar Mainegra
  • 4,006
  • 22
  • 30
  • 5
    When wiring this up to a BottomNav or DrawerLayout as the user pointed out, we don't manually call `navigate` but its called via the wiring of the NavController to the Menu in question. So this won't work. – John Shelley Apr 05 '19 at 20:04
-2

This codelabs exactly what you want to do : https://codelabs.developers.google.com/codelabs/android-navigation/index.html?index=..%2F..index#0

Docs : https://developer.android.com/topic/libraries/architecture/navigation/

GRimmjow
  • 98
  • 1
  • 4
  • 5
    In "9. Navigating using menus, drawers and bottom navigation" I don't see ANY mention of setting argument values to the destinations that are in the bottom navigation menu. – Marcus Wolschon Jul 15 '20 at 10:21