Use of ViewPager2 and Navigation causing multiple instantiation of home fragment

88 Views Asked by At

I am new to Android development and am completing a project in which I am required to use Jetpack Navigation.

My app is a single-activity, multiple-fragment design.

I am also trying to use ViewPager2 to swipe between my "home" Fragment A, to Fragment B (also, I am using indicator dots to show the user where they are in the ViewPager). In future I will have a Fragment C, which I would like to navigate to with conventional navigation from Navigation B.

I am really struggling to figure out how to use navigation and ViewPager2 together harmoniously: at the moment, their co-existence is causing a duplicate instantiation of Fragment A, which leads to other bugs (chiefly, I collect a Flow in Fragment A and this also gets duplicated in undesirable ways).

This how my activity_main.xml looks:

<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
>

<com.google.android.material.tabs.TabLayout
    android:id="@+id/tab_layout"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:translationZ="2dp"
    android:padding="0dp"
    android:outlineProvider="none"
    android:background="@color/transparent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:tabBackground="@drawable/tab_selector"
    app:tabGravity="center"
    app:tabIndicatorHeight="0dp"/>

<androidx.fragment.app.FragmentContainerView
    android:id="@+id/myNavHostFragment"
    android:name="androidx.navigation.fragment.NavHostFragment"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:defaultNavHost="true"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@id/tab_layout"
    app:navGraph="@navigation/navigation" />

<androidx.viewpager2.widget.ViewPager2
    android:id="@+id/view_pager"
    android:background="@drawable/background"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

I already hate the way this is set up, as it just feels wrong that the ViewPager does not contain the FragmentContainerView, but I'm not sure how else to do it since ViewPager2 does not allow child views.

And then, this is how I set things up in my MainActivity.kt:

const val FRAGMENT_COUNT = 2

class MainActivity : AppCompatActivity() {

private lateinit var navController: NavController
private lateinit var viewPager: ViewPager2
private lateinit var tabLayout: TabLayout
private lateinit var pagerAdapter: PagerAdapter
private val viewModel: MainViewModel by inject()

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    tabLayout = findViewById(R.id.tab_layout)
    viewPager = findViewById(R.id.view_pager)
    pagerAdapter = PagerAdapter(this)
    viewPager.adapter = pagerAdapter
    val navHostFragment = supportFragmentManager.findFragmentById(R.id.myNavHostFragment) as NavHostFragment
    navController = navHostFragment.navController

    TabLayoutMediator(tabLayout, viewPager) { _, _ -> }.attach()

    viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
        override fun onPageSelected(position: Int) {
            super.onPageSelected(position)
            
            val currentDestinationId = navController.currentDestination?.id

            when (position) {
                0 -> {
                    if (currentDestinationId != R.id.newSuggestionFragment) {
                        navController.navigate(R.id.action_favouritesFragment_to_newSuggestionFragment)
                    }
                }
                1 -> {
                    if (currentDestinationId != R.id.favouritesFragment) {
                        navController.navigate(R.id.action_newSuggestionFragment_to_favouritesFragment)
                    }
                }
            }
        }
    })

    viewModel.message.observe(this) {
        Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show()
    }
}

}
class PagerAdapter(fragmentActivity: FragmentActivity): FragmentStateAdapter(fragmentActivity) {
    override fun getItemCount(): Int {
        return FRAGMENT_COUNT
    }

override fun createFragment(position: Int): Fragment =
     when(position){
        0 -> NewSuggestionFragment()
        1 -> FavouritesFragment()
        else -> NewSuggestionFragment()
    }
}

I've tried researching online but I'm struggling to find clear information on this (most likely, I just don't understand the underlying issues well enough). I'd really appreciate any advice on fixing this.

0

There are 0 best solutions below