0

I need to change my AppBarLayout based on Navigation component destination for example I have FragmentA and FragmentB I want when user go to FragmentA add TabLayout to AppBarLayout like YouTube application

Example

I have tried to add TabLayout to the activity and hide/show it based on navigation destination, But I found it useless because I need to access Activity and after that I need to findViewById and set ViewPager but I need to do that with new android Navigation component and clean code

This is how i implementing Navigation component

    bnvActivityMainNavigation.setupWithNavController(navController)
    setupWithNavController(vnActivityMain, navController)
    setupActionBarWithNavController(navController)
    setupActionBarWithNavController(navController, dlActivityMainRoot)
    toolbar?.setNavigationOnClickListener { onSupportNavigateUp() }

There is any suggestions??

Mohamed Hamdan
  • 147
  • 2
  • 16
  • can you please more,what do you mean by " I want when user go to FragmentA add TabLayout to AppBarLayout " – Mouaad Abdelghafour AITALI Dec 24 '18 at 23:13
  • I mean I have main fragment FragmentA this fragment contains one button and there is no TabLayout in the AppBarLayout I need when user clicks on button move destination from FragmentA to FragmentB and show TabLayout in AppBarLayout like YouTube app @pic – Mohamed Hamdan Dec 24 '18 at 23:32
  • try to create a TabLayout in your Main Activity and hide it in FragmentA when you switch to FragmentB visible it – Mouaad Abdelghafour AITALI Dec 24 '18 at 23:40
  • @pic this is just an example i need also to add CollapsingToolbarLayout and other views based on fragment so i can't add multiple views and show/hide, i need do this with clean way – Mohamed Hamdan Dec 25 '18 at 15:37

1 Answers1

0

Here is a proposed solution that aims to limit the coupling of the fragment and the activity to the minimum.

The TabLayout is shown or hidden based on a Boolean LiveData object, hosted in a ViewModel shared by the activity and each fragment destination.
Each destination is responsible for setting the appropriate boolean value to the LiveData object, and the activity observes the LiveData object to show or hide the TabLayout.

To be able to use the TabLayout, the fragment declares an interface which the Activity must implement, and which contains a single method used to get a reference to the TabLayout.

Here are corresponding code samples, in Kotlin.

MainActivityViewModel

class MainActivityViewModel internal constructor() : ViewModel() {

    val tabLayoutDestination = MutableLiveData<Boolean>()

    fun setTabLayoutDestination(newValue : Boolean) {
        //If the new value is the same, do not trigger an update
        if (Objects.equals(tabLayoutDestination.value, newValue)) return
        tabLayoutDestination.value = newValue
    }
}

ExampleFragment

class ExampleFragment : Fragment() {

    private lateinit var viewPager: ViewPager
    private lateinit var activityViewModel : MainActivityViewModel
    private var tabLayout: TabLayout? = null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        ...
        val binding = FragmentExampleBinding.inflate(inflater, container, false)
        val context = context ?: return binding.root

        //Get the activity's ViewModel and specify that this destination requires a TabLayout
        val mainViewModelFactory = InjectorUtils.provideMainActivityViewModelFactory()
        activityViewModel = ViewModelProviders.of(activity!!, mainViewModelFactory)
                .get(MainActivityViewModel::class.java)
        activityViewModel.setTabLayoutDestination(true)

        viewPager = binding.viewpager

        return binding.root
    }

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

        //If the parent activity implements the interface, get a reference to its TabLayout
        val parentActivity = if(activity is TabLayoutHost) activity as TabLayoutHost else return
        tabLayout = parentActivity.getTabLayoutReference()

        //Setup the TabLayout and ViewPager
        tabLayout?.setupWithViewPager(viewPager)
        viewPager.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabLayout))
    }

    //The interface which must be implemented by the parent activity
    interface TabLayoutHost {
        fun getTabLayoutReference() : TabLayout
    }
}

MainActivity

class MainActivity : AppCompatActivity(), ExampleFragment.TabLayoutHost {

    private lateinit var activityViewModel : MainActivityViewModel
    private lateinit var tabLayout: TabLayout

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActivityMainBinding = DataBindingUtil.setContentView(this,
                R.layout.activity_main)

        //Get the viewmodel
        val mainViewModelFactory = InjectorUtils.provideMainActivityViewModelFactory()
        activityViewModel = ViewModelProviders.of(this, mainViewModelFactory)
                .get(MainActivityViewModel::class.java)

        //Get a reference to the TabLayout
        TabLayout = binding.tabLayout

        //Subscribe to the boolean livedata, to be able to make 
        //the appropriate UI changes according to the fragment displayed
        activityViewModel.tabLayoutDestination.observe(this, Observer { 
                if(it == true ) updateUiForTabLayoutDestination() 
                else updateUiForOtherDestination() 
        })
    }

    override fun getTabLayoutReference() = tabLayout

    private fun updateUiForTabLayoutDestination() {
        tabLayout.visibility = View.VISIBLE
    }

    private fun updateUiForOtherDestination() {
        tabLayout.visibility = View.GONE
    }
}

Those samples make use of several jetpack components (LiveData, ViewModel and Binding), and were derived from the sunflower sample app provided by Google.

The LiveData solution was inspired by this answer.

The example given here is for a TabLayout, but I've successfully applied it for other views (a progress bar and a fixed table header) by getting a reference to the whole AppBarLayout and calling findViewById to retrieve each view.

Ronan
  • 398
  • 3
  • 12
  • This is only my second SO answer, it can likely be improved, comments and corrections are appreciated. Specifically, I'm not certain onActivityCreated is the best lifecycle step to call the interface, or if the Object.equals is useful for a boolean comparison in Kotlin. – Ronan Jan 28 '19 at 16:59