1

i'm currently working on an Android app and encountered a problem concerning BottomNavigationView and Fragments. I know, there are similar questions like mine but either they doesn't solve my problem or they have no working answers.

My app consists of five top-level destination fragments. For navigating between them I use the BottomNavigationView. Additionally, I have several fragments which serve as lower-level destinations and will be called from one of the top-level fragments. I use SafeArgs plugin to navigate to these fragments and also to pass data to.

My BottomNavigationView Configuration looks like this:

val navView: BottomNavigationView = findViewById(R.id.nav_view)
val navController = (supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController

val appBarConfiguration = AppBarConfiguration(setOf(
    R.id.navigation_dest1, R.id.navigation_dest2, R.id.navigation_dest3,
    R.id.navigation_dest4, R.id.navigation_dest5))
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController);

The problem of this usage is that BottomNavigationView doesn't seem to provide support for saving and storing the fragments somewhere and reuse these instances for navigation. It just creates a new instance and displays it.

Currently each fragment contains some data fetching code, e.g. running a network request in a coroutine or loading files from the filesystem. And because BottomNavigationView doesn't preserve fragment instances, these data fetching parts are run too often.
Of course I thought about putting the data fetching process into the main activity but this results in an overall slower app-startup and doesn't solve the problem that the fragments still need to be recreated every time the user navigates between them.

Up to this point, I already found half of a solution. By using the SupportFragmentManager, manually adding, showing and hiding my fragments it works. But the app runs noticeably slower and the navigation to my lower-level destinations with SafeArgs just doesn't work anymore. I use SafeArgs because it's easy to use and pretty hassle-free, and I would like to keep using it.

I tried to manage it all manually with SupportFragmentManager, but it ends up in chaos and worse performance.

Is there any known way my problem can be solved? A way, BottomNavigationView can interact with SafeArgs and SupportFragmentManager to reuse the fragments instead of recreating them on each navigation action?

(If you need further information or parts of my code, please ask. I think posting my complete code here doesn't make much sense.)

Jonasj
  • 25
  • 1
  • 6
  • For all seeing this thread: The solution does not resolve the exact problem I described initially, but it resolves the architectural problem which lead me to the problem with navigation. – Jonasj Mar 04 '21 at 22:13

1 Answers1

1

Have you considered the option of sharing a ViewModel with your fragments ? For example:

Create a ViewModel class like the following:

 class MyViewModel: ViewModel() {
    ....
    ....
    }

Then, because your fragments share the same Activity, you can declare the following (in Kotlin):

 class MyFragment1: Fragment() {
        val viewModel: MyViewModel by activityViewModels()
        ....
        ....  
    }

    class MyFragment2: Fragment() {
        val viewModel: MyViewModel by activityViewModels()
        ....
        ....  

    }

In this case Fragment1 and Fragment2 will share the same ViewModel instance and the ViewModel will remain in memory until the activity is destroyed. Fragments won't be preserved when you navigate out, but you can preserve all data of each fragment and re-use them. It is fast and smooth and you won't mind if the fragment is re-created because all its data will be kept in memory and ready for use in the shared ViewModel.

See also the official documentation: ViewModel Overview

Sergiob
  • 838
  • 1
  • 13
  • 28
  • Yes, I also tried this. The problem I encountered with this was that I need to perform network requests at startup. Cause Android doesn't let me run them in main thread, I use a coroutine with IO dispatcher. When I try to update my ViewModel after the data fetching finished, I get an error saying: viewModel cannot be set from background thread. And that data fetching is not the only problem, in my fragments I display several HighDPI pictures, recreating the view therefore also takes time which I think wouldn't be solved by ViewModel – Jonasj Mar 04 '21 at 17:15
  • ViewModel has its own Coroutine context. When you set data in the ViewModel from a suspend function make sure you use withContext(Dispatcher.Main).launch { ... your inititialization code here } and it'll work – Sergiob Mar 04 '21 at 17:18
  • I forgot to tell you...save the pictures in the ViewModel. Believe me it's one of the best options, you can use the ViewModel scope with Coroutines: like: viewModel.viewModelScope.launch(Dispatchers.Main) and set all Views inside this, you'll avoid running into the UI thread exception – Sergiob Mar 04 '21 at 17:26
  • Thanks for your help, actually it works pretty well like this. Somehow I didn't managed to resolve the UI exception and abandoned this idea too early. – Jonasj Mar 04 '21 at 22:11