Jetpack Navigation Drawer always recreate fragment

Viewed 372

I tried to use default setup of Jetpack Navigation Drawer generated by AS IDE but I have this problem where it always recreate fragments when switching/navigating instead of just adding a new fragment on the top? They say that it was intended but is there any solution to not recreate fragments even without handling ViewModel stuff?

This is the activity

class MainActivity : AppCompatActivity() {

    private lateinit var appBarConfiguration: AppBarConfiguration
    private var _binding: ActivityMainBinding? = null

    // This property is only valid between onCreate and
    // onDestroyView.
    private val binding get() = _binding!!

    private lateinit var drawerLayout: DrawerLayout

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        _binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        setSupportActionBar(binding.appBarMain.toolbar)

        drawerLayout = binding.drawerLayout
        val navView: NavigationView = binding.navView
        val navController = findNavController(R.id.nav_host_fragment_content_main)
        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        appBarConfiguration = AppBarConfiguration(setOf(
                R.id.nav_home, R.id.nav_marketcap, R.id.nav_about), drawerLayout)
        setupActionBarWithNavController(navController, appBarConfiguration)
        navView.setupWithNavController(navController)

    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        // Inflate the menu; this adds items to the action bar if it is present.
        menuInflater.inflate(R.menu.main, menu)
        menu.findItem(R.id.action_settings).isChecked = AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES
        return true
    }


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

    override fun onDestroy() {
        super.onDestroy()
        _binding = null
    }

    override fun onBackPressed() {
        if (drawerLayout.isDrawerOpen(GravityCompat.START))
            drawerLayout.closeDrawer(GravityCompat.START)
        else
            super.onBackPressed()
    }

}

Fragment

class AssetFragment : Fragment() {

    companion object {
        fun newInstance() = AssetFragment()
    }

    private lateinit var viewModel: AssetViewModel

    private var _binding: FragmentAssetsBinding? = null

    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!

    private lateinit var logTxt: AppCompatTextView
    private lateinit var recyclerView: RecyclerView
    private lateinit var swipeRefreshLayout: SwipeRefreshLayout

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View {

        _binding = FragmentAssetsBinding.inflate(inflater, container, false)
        val root: View = binding.root

        recyclerView = binding.recyclerView
        swipeRefreshLayout = binding.refreshLayout
        logTxt = binding.errorLog

        recyclerView.layoutManager = LinearLayoutManager(context)
        adapter = AssetAdapter(requireContext(), this)
        recyclerView.adapter = adapter

        swipeRefreshLayout.isRefreshing = true
        fetchAssets("30")

        swipeRefreshLayout.setOnRefreshListener {
            swipeRefreshLayout.isRefreshing = true
            fetchAssets("30")
        }

        return root

    }

    private fun fetchAssets(limit: String) {

        //Network stuff
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProvider(this).get(AssetViewModel::class.java)
        // TODO: Use the ViewModel
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

}

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/mobile_navigation"
    app:startDestination="@+id/nav_home">

    <fragment
        android:id="@+id/nav_home"
        android:name=".ui.home.HomeFragment"
        android:label="@string/home"
        tools:layout="@layout/fragment_home" />

    <fragment
        android:id="@+id/nav_marketcap"
        android:name=".ui.marketcap.MarketCapFragment"
        android:label="@string/marketCap"
        tools:layout="@layout/fragment_marketcap" />

    <fragment
        android:id="@+id/nav_about"
        android:name=".ui.about.AboutFragment"
        android:label="@string/about"
        tools:layout="@layout/fragment_about" />

</navigation>

Menu xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:showIn="navigation_view">

    <group android:checkableBehavior="single">

        <item android:title="@string/menu">
            <menu>

                <item
                    android:id="@+id/nav_home"
                    android:icon="@drawable/ic_crypto"
                    android:title="@string/home" />

                <item
                    android:id="@+id/nav_marketcap"
                    android:icon="@drawable/ic_marketcap"
                    android:title="@string/marketCap" />

                <item
                    android:id="@+id/nav_about"
                    android:icon="@drawable/ic_about"
                    android:title="@string/about" />

            </menu>
        </item>

    </group>

        <item android:title="@string/connect">
            <menu>
                <item
                    android:id="@+id/email_connect"
                    android:icon="@drawable/ic_email"
                    android:title="@string/fui_email_hint" />
            </menu>
        </item>

</menu>

onCreateView is getting called every time a fragment switch even pressing back button. I needed to avoid recreating Home fragment, just like when you regularly use fragment manager to add and pop fragment at the top and do not recreate the home fragment.

1 Answers

https://developer.android.com/guide/navigation/navigation-programmatic#share_ui-related_data_between_destinations_with_viewmodel

Any ViewModel objects created in this way live until the associated NavHost and its ViewModelStore are cleared or until the navigation graph is popped from the back stack.

In the fragment, instead of

    private lateinit var viewModel: AssetViewModel

put there

val viewModel: AssetViewModel
        by navGraphViewModels(R.id.nav_host_fragment_content_main)

And remove

viewModel = ViewModelProvider(this).get(AssetViewModel::class.java)
Related