androidandroid-fragmentsandroid-architecture-navigation

Show/hide something after the fragment is completely visible on the screen - Navigation Component


I have a specific scenario: I am developing a music player app and in it, I am using navigation component to move between different fragments (it's a single activity app) So, I have a miniplayer in the app that should be shown at the bottom on all screen except some screens like player screen and splash screen. I have added the miniplayer in my main_activity so that it is shown at all the screens and I only have to hide and make it visible where i want. the below code is working fine:

//! show and hide mini player based on screen
    private val navigationListener = NavController.OnDestinationChangedListener { _, destination, _ ->
        if (destination.id != R.id.playerContainerFragment &&
            destination.id != R.id.splashFragment &&
            destination.id != R.id.equalizerFragment &&
            destination.id != R.id.settingsFragment &&
            destination.id != R.id.appLanguageFragment &&
            destination.id != R.id.feedbackFragment &&
            destination.id != R.id.onBoardingLanguageFragment &&
            destination.id != R.id.onboardFragment &&
            destination.id != R.id.sleepTimerFragment) { //! show player
            if (AppConstants.isServiceRunning) {
                isPlayerVisible = true
                binding.clMiniMusicPlayer.visibility = View.VISIBLE
                binding.seekBarMiniPlayer.max = AppConstants.player?.duration?.toInt() ?: 0
                binding.seekBarMiniPlayer.progress = AppConstants.player?.currentPosition?.toInt() ?: 0
                if (AppConstants.player?.isPlaying == true) {
                    startUpdatingSeekBar()
                } else {
                    updatePlayPauseButtonDrawable()
                    stopUpdatingSeekBar()
                }
            } else {
                isPlayerVisible = false
                binding.clMiniMusicPlayer.visibility = View.GONE
            }
        } else { //! hide player
            binding.clMiniMusicPlayer.visibility = View.GONE
        }
    }
    override fun onResume() {
        super.onResume()
        //! adding the navigation listener:
        navController.addOnDestinationChangedListener(navigationListener)
    }

    override fun onPause() {
        super.onPause()
        navController.removeOnDestinationChangedListener(navigationListener)
    }

but the problem is: the player gets appeared before the fragment gets completely visible on the screen i.e. when I switch back from player fragment to home fragment, the miniplayer should get displayed along with homefragment only but before the home fragment is shown, the player gets visible at the bottom of the player fragment and then the fragment is changed. it is problematic for me as I don't want this behavior.

At home there is a complete song list that gets rendered in recycler view which is a heavy task and takes time and because of this delay (which is variable for different devices and song counts) I also cannot add a delay in showing the miniplayer.

is there any way I can get to know that when my fragment is completely loaded and is visible on the screen now. I have checked the navigation component listeners, and I don't find such type of listener in it.

any help would be highly appreciated!

Thanks in advance!


Solution

  • The solution proposed by @VezDroid was good but for that I have to inherit that interface in every fragment where I want to control the visibility of the mini player, and also I had to set the interface for that fragment as well by using its setter.

    thus, this approach was only suitable if I have to control the mini player visibility in one fragment only.

    as for my use case, every fragment has to change the visibility of the miniplayer according to the requirements, so the approach with which I handled this was:

    I created an abstract BaseFragment class that was inheriting Fragment(). and now my each fragment was inheriting this BaseFragment like this:

    abstract class BaseFragment: Fragment() {
    
    }
    
    class HomeFragment : BaseFragment() { /* my fragment code here */ }
    

    now in base fragment I created method of hiding and showing miniplayer as follow:

    fun hideMiniPlayerAndAdLayout() {
        (activity as? MainActivity)?.binding?.clMiniMusicPlayer?.visibility = View.GONE
        (activity as? MainActivity)?.binding?.adsViewBottom?.visibility = View.GONE
    }
    fun showMiniPlayerAndAdLayout() {
        if (AppConstants.isServiceRunning) {
            (activity as? MainActivity)?.binding?.clMiniMusicPlayer?.visibility = View.VISIBLE
        } else {
            (activity as? MainActivity)?.binding?.clMiniMusicPlayer?.visibility = View.GONE
        }
        (activity as? MainActivity)?.binding?.adsViewBottom?.visibility = View.VISIBLE
        (activity as? MainActivity)?.loadBanner()
    }
    

    these methods can control the visibility of mini player and are now accessible in every fragment that is derived from BaseFragment Class.

    Now in onCreate of every fragment, after showing all recycler items I call one of these methods of showing or hiding by just calling the name of the method as follow and the mini player visibility is controlled by each fragment neatly.

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // my other code to set recycler view and adapter here
        showMiniPlayerAndAdLayout()
    }
    

    hope this helps someone! For now I can't come up with any better solution than this.