Search code examples
androidandroid-architecture-navigation

Handle physical back button in Navigation Component with Toolbar


I have a simple proof of concept app that contains only 2 fragments: MainFragment and RepositoryFragment and 1 activity: MainActivity

Here's 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/nav_graph"
    app:startDestination="@id/id_main">

    <fragment
        android:id="@+id/id_main"
        android:name="com.repofetcher.main.view.MainFragment"
        android:label="@string/app_name"
        tools:layout="@layout/main_fragment">
        <action
            android:id="@+id/action_mainfragment_to_repositoryfragment"
            app:destination="@id/id_repo"
            app:popUpTo="@id/id_main"
            app:popUpToInclusive="true"/>
    </fragment>

    <fragment
        android:id="@+id/id_repo"
        android:name="com.repofetcher.repo.view.RepositoryFragment"
        android:label="{arg_repo_name}"
        tools:layout="@layout/repository_fragment">
        <argument
            android:name="arg_repo_name"
            app:argType="string"
            android:defaultValue="@null"
            app:nullable="true" />
    </fragment>

</navigation>

MainActivity:

    private lateinit var navController: NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        (application as RepoFetcherApplication).appComponent.mainComponent().create().inject(this)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)
        navController = findNavController(R.id.nav_host_fragment)
        setupActionBarWithNavController(navController)
    }

    override fun onSupportNavigateUp(): Boolean {
        return navController.navigateUp() || super.onSupportNavigateUp()
    }

Main activity's layout:

<?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=".main.view.MainActivity">

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/nav_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>

This is the part that navigates from MainFragment to RepositoryFragment

    private fun navToRepositoryFragment(name: String) {
        findNavController().navigate(MainFragmentDirections.actionMainfragmentToRepositoryfragment(argRepoName = name))
    }

RepositoryFragment:

 override fun onCreate(savedInstanceState: Bundle?) {
        (requireActivity().application as RepoFetcherApplication).appComponent.repoComponent().create().inject(this)
        super.onCreate(savedInstanceState)
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        dataBinding = DataBindingUtil.inflate(inflater, R.layout.repository_fragment, container, false)
        dataBinding.lifecycleOwner = this
        viewModel = ViewModelProvider(this, viewModelProviderFactory).get(RepositoryViewModel::class.java)
        dataBinding.viewModel = viewModel
        return dataBinding.root
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel.getRepository(args.argRepoName)
    }

Ideally, I would like to achieve these navigations:

navigating from MainFragment to RepositoryFragment, then by clicking either the back button on the Toolbar or on the device, it should navigate back to the MainFragment.

However in my case, only the back button on the toolbar will work as intended. The back button on the device will directly quit the app.

I wonder what I'm missing in the code?

Update

One of the real problem behind this question is I used livedata for a single event (to handle the click event in order to navigate to RepositoryFragment).

So after taking a look at this article : https://proandroiddev.com/livedata-with-single-events-2395dea972a8

with solution @GabrieleMariotti provided and got the problem solved.

I would like to thank @GabrieleMariotti who helped me with great patient.


Solution

  • Remove the app:popUpTo="@id/id_main" and app:popUpToInclusive="true in your action:

        <action
            android:id="@+id/action_mainfragment_to_repositoryfragment"
            app:destination="@id/nav_gallery"
            />
    

    In your onCreate method use:

        appBarConfiguration = AppBarConfiguration(navController.graph)
        setupActionBarWithNavController(navController, appBarConfiguration)
    

    and in your onSupportNavigateUp method:

    override fun onSupportNavigateUp(): Boolean {
        val navController = findNavController(R.id.nav_host_fragment)
        return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
    }