Tomk07
Tomk07

Reputation: 113

scrollToPosition() when updating RecyclerView in a fragment

I've made a list which gets items from a Room-database using LiveData. This liveData<List> is then bound to a recyclerView, using a BindingAdapter. The lists adapter is listAdapter, not `RecyclerView.Adapter.

I need help holding onto the scroll-state or somehow returning to the scroll-index I was at before the recyclerView reloaded:

//In ViewModel
    val movieList = moviesRepository.movies

..

//in Repo
  val movies: LiveData<List<Movie>> =
    Transformations.map(database.movieDao.getMovies()) {
        it.asDomainModel()
    }

Every time the DB updates, the recyclerView shoots back up to the top.

And here's the bindingAdapter for the RecyclerView and the list.

@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, data: List<Movie>?) {
    val adapter = recyclerView.adapter as MovieListAdapter
    //Log.d("listData binding", "${data}")
    adapter.submitList(data)
}

I think I need to use something like recyclerView.smoothScrollToPosition(la.getItemCount()); after the update has occured, but I don't know how to automatically call it when the update has occured

Project Repo

Upvotes: 1

Views: 1789

Answers (2)

Tomk07
Tomk07

Reputation: 113

figured it out. All of the examples I could find were using Activities, not fragments. So all I had to do was pop this into the onCreateView() function in the relevant fragment.kt file.

    binding.movieList.layoutManager =
        object : LinearLayoutManager(getActivity(), VERTICAL, false) {
            override fun onLayoutCompleted(state: RecyclerView.State) {
                super.onLayoutCompleted(state)
                val lastVisibleItemPosition = findLastVisibleItemPosition()
                val count = (binding.movieList.adapter as MovieListAdapter).itemCount

                    //speed the scroll up a bit, but make it look smooth at the end
                    binding.movieList.scrollToPosition(count - 5)
                    binding.movieList.smoothScrollToPosition(count)

            }
        }

here, the binding.movieList is referring to this xml element

 <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/movie_list"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintBottom_toTopOf="@+id/load_more_button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:listData="@{viewModel.movieList}"
        tools:listitem="@layout/movie_list_item" />

Upvotes: 0

AouledIssa
AouledIssa

Reputation: 2824

  1. First of all don't use ListAdapter RecyclerView has a more optimized adapter here

  2. in your adapter provide a function that overrides the item list and there is where you notify the data change

  3. Use smoothScrollToPosition(lastVisiblePosition) to scroll to the last visible position where lastVisiblePosition = layoutManager.findFirstVisibleItemPosition()

  4. Update lastVisiblePosition before you push new items to the adapter notifyDatasetChanged()

Step 2

fun updateList(newItems:List<Movie>) {
    moviesList.addAll(newItems)
    lastVisiblePosition = layoutManager.findFirstVisibleItemPosition()
    notifyDataSetChanged()
}

from your view when you call adapter.updateList(newItems) just call recyclerView.smoothScrollToPosition(adapter.lastVisiblePosition)

Upvotes: 1

Related Questions