2

In my MainActivity I have BottomNavigation. My activity is connected with MainViewModel. When app starts I fetch data from firebase. Until the data is downloaded, app displays ProgressBar and BottomNavigation is hide (view.visibility = GONE). When data has been downloaded I hide ProgressBar and show BottomNavigation with the app's content. It works great.

In another part of the app user can open gallery and choose photo. The problem is when activity with photo to choose has been closed, MutableStateFlow is triggered and bottomNavigation displays again but it should be hide in that specific part(fragment) of the app.

Why my MutableStateFlow is triggered although I don't send to it anything when user come back from gallery activity?

MainActivity (onStart):

private val mainSharedViewModel by viewModel<MainSharedViewModel>()


override fun onStart() {
    super.onStart()
    lifecycle.addObserver(mainSharedViewModel)

    val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottomNavigationView)
    val navHostFragment: FragmentContainerView = findViewById(R.id.bottomNavHostFragment)
    bottomNavController = navHostFragment.findNavController()

    bottomNavigationView.apply {
        visibility = View.GONE
        setupWithNavController(navHostFragment.findNavController())
    }

    //the fragment from wchich I open GalleryActivity is hide (else ->)
    navHostFragment.findNavController().addOnDestinationChangedListener { _, destination, _ ->
        when (destination.id) {
            R.id.mainFragment,
            R.id.profileFragment,
            R.id.homeFragment -> bottomNavigationView.visibility = View.VISIBLE
            else -> bottomNavigationView.visibility = View.GONE
        }
    }

    mainSharedViewModel.viewModelScope.launch {
        mainSharedViewModel.state.userDataLoadingState.collect {
            if (it == UserDataLoading.LOADED) {
                bottomNavigationView.visibility = View.VISIBLE
            } else {
                bottomNavigationView.visibility = View.GONE
            }
        }
    }
}

ViewModel:

class MainSharedViewState {
    val userDataLoadingState = MutableStateFlow(UserDataLoading.LOADING) }

enum class UserDataLoading {
    LOADING, UNKNOWN_ERROR, NO_CONNECTION_ERROR, LOADED }
Wafi_ck
  • 1,045
  • 17
  • 41
  • How are you getting the viewModel in your Activity? – Scott Cooper Feb 09 '22 at 09:22
  • private val mainSharedViewModel by viewModel() – Wafi_ck Feb 09 '22 at 19:02
  • 2
    StateFlow always exists 1 value. So when you back from photo activity to home activity the current value still there and trigger to your collector. In your case, I think you could try distinctUntilChanged method or maybe change your logic handle visibility of bottomNav – Steve.P Feb 15 '22 at 04:00
  • @Steve.P is right. You could also try to replace your MutableStateFlow with a MutableSharedFlow and you won't have this problem anymore – Christian C Feb 16 '22 at 11:06
  • I've replaced MutableStateFlow with a MutableSharedFlow and still the same. My collector is still triggered in actiivty – Wafi_ck Feb 16 '22 at 22:43
  • StateFlow is also a special ShareFlow and it has replayCache. In this case, when you go to photo activity, maybe your collector is shutdown. Then when you come back to your main activity, collector re-subscribe and receive the latest value of flow -> trigger collecting again. My suggestion is you should add your check before show botNav in `mainSharedViewModel.state.userDataLoadingState.collect` – Steve.P Feb 17 '22 at 07:28
  • and one thing, why dont you collect inside `lifecycleScope` or `viewOwner.lifecycleScope` instead of viewModelScope? – Steve.P Feb 17 '22 at 07:31

2 Answers2

1

When you come back from the gallery, the stateflow value is still set as Loaded, as the Viewmodel has not been cleared (and the activity was set to Stopped, not destroyed. It is still in the back stack.) This is why the bottomNavigationView is visible when you come back.

Although your architecture/solution is not how I would have done it, in your circumstances I guess you could change the value of the MutableStateFlow when the activity's onStop is called. Either that or use a MutableSharedFlow instead with a replayCount of 0 so that there is no value collected (although then, the bottomNavigationView will still be set as Visible if it is visible by default in XML.)

Sean Blahovici
  • 5,350
  • 4
  • 28
  • 38
1

SOLVED:

I've created

val userDataLoadingState = MutableSharedFlow<UserDataLoading>(replay = 0)

and when my ViewModel is created I set

 state.userDataLoadingState.emit(UserDataLoading.LOADING)

and I collect data in Activity

lifecycleScope.launch {
           mainSharedViewModel.state.userDataLoadingState.collect {
               if (it == UserDataLoading.LOADED) {
                   bottomNavigationView.visibility = View.VISIBLE
               } else {
                   bottomNavigationView.visibility = View.GONE
               }
           }
       }

Now it works great. I don't know why it didn't work before.

Wafi_ck
  • 1,045
  • 17
  • 41