12
votes

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.

3
How did you do it?jL4
@jL4 I updated my question with the answer.Daniel Sims

3 Answers

8
votes

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
}
-1
votes

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