androidkotlinandroid-jetpack-navigationandroid-scrollscroll-position

Android Jetpack Navigation Disable Scroll Position


I have a single activity with many fragments (Using jetpack navigation). On my first fragment, i have a recyclerview. If i scroll on the first fragment and then navigate to the other fragment, the fragment retains the scroll position and i don't want that. An example is as follows:

i.e. Suppose i have two fragments A and B, When my app starts it starts on A. Suppose i start scrolling on A and then navigate to B. My app retains the scroll position on B which is not what i want. I want fragment B to start on top. And then when it returns to fragment A, i want it to retain the scroll position it previously scrolled.

Fragment A.xml

<?xml version="1.0" encoding="utf-8"?>

<layout 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">

<data>

    <import type="android.view.View" />

    <variable
        name="ViewModel"
        type="....AccountViewModel" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/Layout_Fragment_Account"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <!--
       Recyclerview
    -->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/RecyclerView_Account"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="@{ViewModel.accountListVisibility? View.VISIBLE : View.GONE}"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:visibility="gone" />


    <!--
        Empty Views and group
    -->
    <androidx.constraintlayout.widget.Group
        android:id="@+id/Empty_View"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="@{ViewModel.accountEmptyViewVisibility? 

View.VISIBLE : View.GONE}"
            app:constraint_referenced_ids="Empty_View_Illustration,Empty_View_Title,Empty_View_Subtitle" />

    <ImageView
        android:id="@+id/Empty_View_Illustration"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:scaleType="centerCrop"
        app:layout_constraintBottom_toTopOf="@+id/Empty_View_Title"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_chainStyle="packed"
        app:srcCompat="@drawable/il_account" />

    <TextView
        android:id="@+id/Empty_View_Title"
        style="@style/Locky.Text.Title6"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="32dp"
        android:layout_marginEnd="32dp"
        android:text="@string/text_title_emptyView_accounts"
        android:textAlignment="center"
        app:layout_constraintBottom_toTopOf="@id/Empty_View_Subtitle"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/Empty_View_Illustration"
        app:layout_constraintVertical_chainStyle="packed"
        app:layout_constraintWidth_default="percent"
        app:layout_constraintWidth_percent=".8" />

    <TextView
        android:id="@+id/Empty_View_Subtitle"
        style="@style/Locky.Text.Subtitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginStart="32dp"
        android:layout_marginEnd="32dp"
        android:layout_marginBottom="?attr/actionBarSize"
        android:text="@string/text_subtitle_emptyView_accounts"
        android:textAlignment="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.6"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/Empty_View_Title"
        app:layout_constraintWidth_default="percent"
        app:layout_constraintWidth_percent=".8" />

    <!--
        Progress Bar
    -->
    <include
        android:id="@+id/Progress_Bar"
        layout="@layout/custom_view_list_loading"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="@{ViewModel.loadingStatus? View.VISIBLE : View.GONE}"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

</layout>

Fragment A.kt:

class AccountFragment : Fragment() {

private var _binding: FragmentAccountBinding? = null
private var _viewModel: AccountViewModel? = null
private var _lastClickTime: Long = 0

private val binding get() = _binding!!
private val viewModel get() = _viewModel!!

companion object {
    const val TAG = "ACCOUNT_FRAGMENT_DEBUG"
}

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    // Inflate the layout for this fragment
    _binding = FragmentAccountBinding.inflate(inflater, container, false)
    // Fetch view model
    _viewModel = ViewModelProvider(this).get(AccountViewModel::class.java)
    //Bind view model to layout
    binding.viewModel = _viewModel
    // Bind lifecycle owner
    binding.lifecycleOwner = this

    return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    /* Hides the soft keyboard */
    hideSoftKeyboard(binding.root)

    /* Observe snack bar events */
    observeSnackBarEvent()

    /* Observe the account list changes */
    observeAccounts()

    /* Observe back stack entry result after navigating from sort sheet */
    observeBackStackEntryForSortSheet()
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setHasOptionsMenu(true)
}

override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
    inflater.inflate(R.menu.menu_toolbar_filter, menu)
    super.onCreateOptionsMenu(menu, inflater)
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
    return when (item.itemId) {
        R.id.Toolbar_Filter -> {
            navigateToSort()
            true
        }
        else -> false
    }
}

override fun onDestroyView() {
    super.onDestroyView()
    _binding = null
}

/*
* My Functions
*/
private fun observeBackStackEntryForSortSheet() {
    val navController = findNavController()
    // After a configuration change or process death, the currentBackStackEntry
    // points to the dialog destination, so you must use getBackStackEntry()
    // with the specific ID of your destination to ensure we always
    // get the right NavBackStackEntry
    val navBackStackEntry = navController.getBackStackEntry(R.id.Fragment_Account)

    // Create our observer and add it to the NavBackStackEntry's lifecycle
    val observer = LifecycleEventObserver { _, event ->
        if (event == Lifecycle.Event.ON_RESUME
            && navBackStackEntry.savedStateHandle.contains(KEY_ACCOUNTS_SORT)
        ) {

            viewModel.sortChange(
                navBackStackEntry.savedStateHandle.get<AccountSort>(
                    KEY_ACCOUNTS_SORT
                )!!
            )

            navBackStackEntry.savedStateHandle.remove<AccountSort>(KEY_ACCOUNTS_SORT)
        }
    }
    navBackStackEntry.lifecycle.addObserver(observer)

    // As addObserver() does not automatically remove the observer, we
    // call removeObserver() manually when the view lifecycle is destroyed
    viewLifecycleOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
        if (event == Lifecycle.Event.ON_DESTROY) {
            navBackStackEntry.lifecycle.removeObserver(observer)
        }
    })
}

private fun observeSnackBarEvent() {
    viewModel.showSnackBarEvent.observe(viewLifecycleOwner, Observer {
        if (it != null) {
            snackBarAction(it)
        }
    })
}

private fun observeAccounts() {
    with(viewModel) {
        accounts.observe(viewLifecycleOwner, Observer {
            if (it != null) {
                //set loading flag to hide loading animation
                doneLoading()

                //Alternate visibility for account list and empty view
                alternateAccountListVisibility(it.size)

                //Submit the cards
                initiateAccounts().submitList(it)
            }
        })
    }
}

private fun initiateAccounts(): AccountAdapter {
    val adapter = AccountAdapter(
        AccountClickListener {
            navigateToSelectedAccount(it)
        },
        AccountOptionsClickListener { view, card ->
            view.apply {
                isEnabled = false
            }
            createPopupMenu(view, card)
        })

    binding.RecyclerViewAccount.apply {
        this.adapter = adapter
        setHasFixedSize(true)
    }

    return adapter
}

private fun createPopupMenu(view: View, account: Account) {
    requireContext().createPopUpMenu(
        view,
        R.menu.menu_moreoptions_account,
        PopupMenu.OnMenuItemClickListener {
            when (it.itemId) {
                R.id.Menu_CopyUsername -> copyToClipboardAndToast(account.username)
                R.id.Menu_CopyPass -> copyToClipboardAndToast(account.password)
                R.id.Menu_ShowPass -> triggerSnackBarEvent(account.password)
                else -> false
            }
        }, PopupMenu.OnDismissListener {
            view.apply {
                isEnabled = true
            }
        })
}

private fun navigateToSort() {
    if (SystemClock.elapsedRealtime() - _lastClickTime >= 800) {
        _lastClickTime = SystemClock.elapsedRealtime()
        navigateTo(AccountFragmentDirections.actionFragmentAccountToBottomSheetFragmentAccountFilter())
    }
}

private fun navigateToSelectedAccount(account: Account) {
    navigateTo(
        AccountFragmentDirections.actionFragmentAccountToFragmentViewAccount(
            account
        )
    )
}

private fun snackBarAction(message: String) {
    binding.LayoutFragmentAccount.snackbar(message) {
        action(getString(R.string.button_snack_action_close)) { dismiss() }
    }
    viewModel.doneShowingSnackBar()
}

private fun triggerSnackBarEvent(message: String): Boolean {
    viewModel.setSnackBarMessage(message)
    return true
}

private fun copyToClipboardAndToast(message: String): Boolean {
    copyToClipboard(message)
    toast(getString(R.string.message_copy_successful))
    return true
}

Fragment B.xml

<?xml version="1.0" encoding="utf-8"?>

<layout 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">

<data>

    <variable
        name="Account"
        type="....Account" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/Layout_Credential_View"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/Account_Logo"
        imageUrl="@{Account.logoUrl}"
        loadingResource="@{@drawable/ic_image_loading}"
        errorResource="@{@drawable/ic_account_placeholder}"
        android:layout_width="100dp"
        android:layout_height="100dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:src="@drawable/ic_account_placeholder" />

    <TextView
        android:id="@+id/Account_Name"
        style="@style/Locky.Text.Title5.Name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="32dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="32dp"
        android:textAlignment="center"
        android:text="@{Account.accountName}"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/Account_Logo"
        tools:text="This can be a very very very long title toooooo" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/RecyclerView_Credentials_Field"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="24dp"
        android:layout_marginEnd="8dp"
        android:nestedScrollingEnabled="true"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/Account_Name" />

</androidx.constraintlayout.widget.ConstraintLayout>

</layout>

Fragment B.kt

class ViewAccountFragment : Fragment() {

private var _binding: FragmentViewAccountBinding? = null
private var _viewModel: ViewAccountViewModel? = null
private lateinit var _account: Account

private val binding get() = _binding!!
private val viewModel get() = _viewModel!!

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    //Fetch the layout and do the binding
    _binding = FragmentViewAccountBinding.inflate(inflater, container, false)
    //Instantiate view model
    _viewModel = ViewModelProvider(this).get(ViewAccountViewModel::class.java)
    binding.lifecycleOwner = this

    //Fetch the account clicked on the previous screen
    _account = ViewAccountFragmentArgs.fromBundle(requireArguments()).parcelcredaccount

    with(_account) {

        //Bind the account to the layout for displaying
        binding.account = this

        //Submit the account details to the recyclerview
        initiateCredentialsFieldList().submitList(viewModel.fieldList(this))
    }

    return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    /* Hides the soft keyboard */
    hideSoftKeyboard(binding.root)
}

override fun onDestroyView() {
    super.onDestroyView()
    _binding = null
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setHasOptionsMenu(true)
}

override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
    inflater.inflate(R.menu.menu_credentials_actions, menu)
    super.onCreateOptionsMenu(menu, inflater)
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
    return when (item.itemId) {
        R.id.Action_Duplicate -> {
            /*
            * We set the account id to empty here
            * When the add screen receives it, it wil perceive it as a new account that needs to be
            * added to the database
            */
            navigateToEditScreen(_account.copy(accountID = generateUniqueID()))
            true
        }

        R.id.Action_Edit -> {
            navigateToEditScreen(_account)
            true
        }

        R.id.Action_Delete -> {
            deleteConfirmationDialog(_account.accountName)
            true
        }
        else -> false
    }
}

private fun initiateCredentialsFieldList(): CredentialsViewAdapter {
    val credentialsAdapter =
        CredentialsViewAdapter(
            CopyClickListener { data ->
                copyToClipboardAndToast(data)
            },
            ViewClickListener { data ->
                snackBarAction(data)
            })

    binding.RecyclerViewCredentialsField.apply {
        adapter = credentialsAdapter
        setHasFixedSize(true)
    }

    return credentialsAdapter
}

private fun deleteAndNavigateBackToAccountList() {
    with(_account) {
        viewModel.delete(accountID)
        toast(getString(R.string.message_credentials_deleted, accountName))
        findNavController().popBackStack()
    }
}

navigation.xml

<fragment
    android:id="@+id/Fragment_Account"
    android:name="....AccountFragment"
    android:label="Accounts"
    tools:layout="@layout/fragment_account">
    <action
        android:id="@+id/action_Fragment_Account_to_Fragment_View_Account"
        app:destination="@id/Fragment_View_Account" />
    <action
        android:id="@+id/action_Fragment_Account_to_BottomSheet_Fragment_Account_Filter"
        app:destination="@id/BottomSheet_Fragment_Account_Filter" />
</fragment>

MainActivity.xml

<?xml version="1.0" encoding="utf-8"?>

<layout 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">

<androidx.drawerlayout.widget.DrawerLayout
    android:id="@+id/Drawer_Main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.main.main.MainActivity">

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:id="@+id/Layout_Coordinator_Main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true">

        <com.google.android.material.appbar.MaterialToolbar
            android:id="@+id/Toolbar_Main"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@color/colorOnSurface"
            android:outlineAmbientShadowColor="@color/colorShadowColor"
            android:outlineSpotShadowColor="@color/colorShadowColor">

            <TextView
                android:id="@+id/Toolbar_Main_Title"
                style="@style/Locky.Text.Title6.Toolbar"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:text="@string/app_name" />

        </com.google.android.material.appbar.MaterialToolbar>

        <androidx.core.widget.NestedScrollView
            android:id="@+id/Nested_Scroll"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="?attr/actionBarSize"
            android:fillViewport="true">

            <fragment
                android:id="@+id/Navigation_Host"
                android:name="androidx.navigation.fragment.NavHostFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:defaultNavHost="true"
                app:navGraph="@navigation/navigation_drawer_main" />

        </androidx.core.widget.NestedScrollView>

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/FAB_Search"
            style="@style/Locky.FloatingActionButton.Mini"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="0dp"
            android:layout_marginBottom="85dp"
            app:layout_anchor="@id/FAB_Add"
            app:layout_anchorGravity="top|center_horizontal"
            app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
            app:srcCompat="@drawable/ic_search" />

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/FAB_Add"
            style="@style/Locky.FloatingActionButton.Normal"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:layout_margin="@dimen/fab_margin"
            app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
            app:srcCompat="@drawable/ic_add" />

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

    <com.google.android.material.navigation.NavigationView
        android:id="@+id/Navigation_View"
        style="@style/Locky.Widget.Custom.NavigationView"
        android:layout_width="280dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:clipToPadding="false"
        android:paddingStart="0dp"
        android:paddingEnd="16dp"
        app:headerLayout="@layout/drawer_header"
        app:itemTextAppearance="@style/Locky.Text.Body.Drawer"
        app:menu="@menu/menu_drawer_main" />

</androidx.drawerlayout.widget.DrawerLayout>

MainActivity.kt

class MainActivity : AppCompatActivity() {

private lateinit var _binding: ActivityMainBinding
private lateinit var _viewModel: MainActivityViewModel
private lateinit var _appBarConfiguration: AppBarConfiguration

//Fragments that can navigate with the drawer
private val _navigationFragments = setOf(
    R.id.Fragment_Card,
    R.id.Fragment_Account,
    R.id.Fragment_Device
)

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    _binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
    _viewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)

    _binding.lifecycleOwner = this

    //Set the support action bar to the toolbar
    setSupportActionBar(_binding.ToolbarMain)
    //Remove the default actionbar title
    supportActionBar?.setDisplayShowTitleEnabled(false)

    /* Updates the app settings*/
    updateAppSettings()

    //Setup the navigation components
    navigationUISetup()

    //Load FABs
    listenerForAddFab()

    listenerForSearchFab()

    //Scroll changes to adjust toolbar elevation accordingly
    setUpNestedScrollChangeListener()
}

override fun onOptionsItemSelected(item: MenuItem) =
    item.onNavDestinationSelected(findNavController(R.id.Navigation_Host)) || super.onOptionsItemSelected(
        item
    )

override fun onSupportNavigateUp() =
    findNavController(R.id.Navigation_Host).navigateUp(_appBarConfiguration)

override fun finish() {
    super.finish()

    ActivityNavigator.applyPopAnimationsToPendingTransition(this)
}

private fun navigationUISetup() {
    //Fetch the Nav Controller
    val navController = findNavController(R.id.Navigation_Host)
    //Setup the App Bar Configuration
    _appBarConfiguration = AppBarConfiguration(_navigationFragments, _binding.DrawerMain)

    //Use Navigation UI to setup the app bar config and navigation view
    NavigationUI.setupActionBarWithNavController(this, navController, _appBarConfiguration)
    NavigationUI.setupWithNavController(_binding.NavigationView, navController)

    //Set the mini FABs with navigation to navigate to fragments accordingly.
    Navigation.setViewNavController(_binding.FABAdd, navController)
    Navigation.setViewNavController(_binding.FABSearch, navController)

    //Add on change destination listener to navigation controller to handle fab visibility
    navigationDestinationChangeListener_FAB(navController)

    //Add on change destination listener to navigation controller to handle screen title visibility
    navigationDestinationChangeListener_ToolbarTitle(navController)
}

private fun setUpNestedScrollChangeListener() =
    _binding.NestedScroll.setOnScrollChangeListener { _, _, scrollY, _, _ ->
        if (scrollY > 0) {
            _binding.ToolbarMain.elevation = 12F
        } else {
            _binding.ToolbarMain.elevation = 0F
        }
    }

private fun navigationDestinationChangeListener_ToolbarTitle(navController: NavController) {
    navController.addOnDestinationChangedListener { _, nd, _ ->
        when (nd.id) {
            R.id.Fragment_Account -> updateToolbar(getString(R.string.text_title_screen_accounts))
            R.id.Fragment_Card -> updateToolbar(getString(R.string.text_title_screen_cards))
            R.id.Fragment_Device -> updateToolbar(getString(R.string.text_title_screen_devices))
            R.id.Fragment_Settings -> updateToolbar(getString(R.string.text_title_screen_settings))
            R.id.Fragment_Profile -> updateToolbar(getString(R.string.text_title_screen_profile))
            R.id.Fragment_About -> updateToolbar(getString(R.string.text_title_screen_about))
            R.id.Fragment_Donate -> updateToolbar(getString(R.string.text_title_screen_donate))
            else -> {
                //Show the toolbar
                updateToolbar(null)
            }
        }
    }
}

private fun navigationDestinationChangeListener_FAB(navController: NavController) {
    navController.addOnDestinationChangedListener { nc, nd, _ ->
        when (nd.id) {
            nc.graph.startDestination,
            R.id.Fragment_Card,
            R.id.Fragment_Device -> {
                _binding.DrawerMain.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)

                //Show all the FABs
                showFABs()
            }
            else -> {
                _binding.DrawerMain.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)

                //Hide all the FABs
                hideFABs()
            }
        }
    }
}

private fun getFadeNavOptions(): NavOptions? {
    return NavOptions.Builder()
        .setEnterAnim(R.anim.anim_fade_in)
        .setExitAnim(R.anim.anim_fade_out)
        .build()
}

private fun hideFABs() {
    _binding.FABSearch.hide()
    _binding.FABAdd.hide()
}

private fun showFABs() {
    _binding.FABSearch.show()
    _binding.FABAdd.show()

    showFABFromSlidingBehavior(_binding.FABSearch, _binding.FABSearch.isVisible)
    showFABFromSlidingBehavior(_binding.FABAdd, _binding.FABAdd.isVisible)
}

private fun showFABFromSlidingBehavior(fab: FloatingActionButton, isVisible: Boolean) {
    val layoutParams: ViewGroup.LayoutParams = fab.layoutParams
    if (layoutParams is CoordinatorLayout.LayoutParams) {
        val behavior = layoutParams.behavior
        if (behavior is HideBottomViewOnScrollBehavior) {
            if (isVisible) {
                behavior.slideUp(fab)
            } else {
                behavior.slideDown(fab)
            }
        }
    }
}

I have attached a gif to demontstrate the issue here:

scroll issue

In the GIF i navigate from 3 fragments (Fragment A > Fragment B > Fragment C) Is there anything i am doing wrong here ?


Solution

  • you have the same layoutmanager for both fragments, when you populate your different fragments; the same layoutmanager is called. Which then tries to restore the same position thinking its the same recyclerview, which is kind of a feature when you think about it.

    from the docs:

    Called when the RecyclerView is ready to restore the state based on a previous RecyclerView. Notice that this might happen after an actual layout, based on how Adapter prefers to restore State. See RecyclerView.Adapter.getStateRestorationPolicy()

    which means what we need is not to restore the state which can be done by passing PREVENT to RecyclerView.Adapter.StateRestorationPolicy

    solution1: in your fragment B adapter just call adapter.stateRestorationPolicy = PREVENT

    solution2: create a different layoutmanager for fragment B in case you want to restore position for something else.

    EDIT :: QA :: how can i set the view to be on top (Near Status Bar) :

    Well, since you are populating your fragments inside a NestedScrollView you should call NestedScrollView.scrollTo(0, 0); when you navigate to the required fragment probably by waiting on a callback from addOnDestinationChangedListener inside your MainActivity.kt