Akaya93
Akaya93

Reputation: 117

Why does my list always scroll to the top when going back into the fragment?

This is what happened

enter image description here

I just realize you can't see the touch event

So the first one i touched misc, the second one i touched pasta

I don't think this is the correct behavior, right? Should i save the position when i leave, and scroll the list in onresume?

This is my adapter

class CategoryListAdapter(private val onClickListener: OnClickListener) :
    ListAdapter<Category, CategoryListAdapter.CategoryViewHolder>(DiffCallback) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryViewHolder =
        CategoryViewHolder(
            ItemListVerticalBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )

    override fun onBindViewHolder(holder: CategoryViewHolder, position: Int) {
        val category = getItem(position)
        holder.bindCategory(category)
        holder.itemView.setOnClickListener {
            onClickListener.onClick(category)
        }
    }

    companion object DiffCallback : DiffUtil.ItemCallback<Category>() {
        override fun areItemsTheSame(oldItem: Category, newItem: Category): Boolean =
            oldItem == newItem

        override fun areContentsTheSame(oldItem: Category, newItem: Category): Boolean =
            oldItem.id == newItem.id
                    && oldItem.name == newItem.name
                    && oldItem.lastAccessed == newItem.lastAccessed
                    && oldItem.thumbnailUrl == newItem.thumbnailUrl
    }

    inner class CategoryViewHolder(private var binding: ItemListVerticalBinding) :
        RecyclerView.ViewHolder(binding.root) {

        fun bindCategory(category: Category) {
            binding.itemImageView.load(category.thumbnailUrl)
            binding.itemTextView.text = category.name
        }

    }

    class OnClickListener(val clickListener: (category: Category) -> Unit) {
        fun onClick(category: Category) = clickListener(category)
    }
}

This is my populating method in the fragment

private fun setTheCategories(){
        lifecycleScope.launchWhenStarted {

            val adapter = CategoryListAdapter(
                CategoryListAdapter.OnClickListener{
                    lifecycleScope.launchWhenStarted {
                        viewModel.displayCategoryMeals(it)
                    }
                }
            )

            binding.categoryRecyclerView.layoutManager = LinearLayoutManager(requireContext())
            binding.categoryRecyclerView.adapter = adapter

            viewModel.categories.collect { resource ->
                when (resource.status) {
                    Resource.Status.LOADING -> {
                        Log.v("CATEGORY", "LOADING")
                    }
                    Resource.Status.SUCCESS -> {
                        Log.v("CATEGORY", "SUCCESS")
                        val categories = resource.data!!
                        adapter.submitList(categories)
                    }
                    Resource.Status.ERROR -> {
                        val categories = resource.data!!
                        if(categories[0].name.isNotEmpty()){
                            adapter.submitList(categories)
                        }
                        Log.v("CATEGORY", "ERROR")
                    }
                }
            }
        }
    }

This is how i navigate to next fragment

private fun goToCategoryMeals(){
        lifecycleScope.launchWhenStarted {
            viewModel.category.collect {
                if(it != null) {
                    [email protected]().navigate(
                        CategoryFragmentDirections.actionCategoryFragmentToCategoryMealsFragment(
                            categoryName = it.name,
                            savedDate = it.lastAccessed
                        )
                    )
                    viewModel.navigatedToCategoryMeals()
                }
            }
        }
    }

This is a part of my viewmodel

    init {
        getAllCategories(sharedPrefRepo.getString(AppConstants.CURR_DATE_CAT))
    }

    private fun getAllCategories(savedDate: String) {
        viewModelScope.launch {
            repo
                .getCategories(savedDate)
                .collect {
                    _categories.value = it
                    if (it.status == Resource.Status.SUCCESS && currDate != savedDate) {
                        sharedPrefRepo.putString(AppConstants.CURR_DATE_CAT, currDate)
                    }
                }
        }
    }

This is a part of my repo

override suspend fun getCategories(savedDate: String): Flow<Resource<List<Category>>> =
        flow {
            emit(Resource.loading())

            if (currDate!=savedDate){
                try{
                    val netCategories = networkTransaction.getCategories()
                    dbTransaction.upsertMiniCategories(netCategories.mapToDbCategories())
                }
                catch (e: Exception){
                    if (e is HttpException){
                        try {
                            dbTransaction.getAllCategories().collect {
                                emit(
                                    Resource.error(
                                        "Something is wrong when connecting to network\n" +
                                                "Using the old data inside local storage",
                                        it.mapToCategories()
                                    )
                                )
                            }
                        } catch (e2: IOException) {
                            emit(Resource.error(e2.localizedMessage!!, listOf(Category())))
                        }
                    } else {
                        emit(Resource.error(e.localizedMessage!!, listOf(Category())))
                    }
                }
            }

            try {
                dbTransaction.getAllCategories().collect {
                    emit(Resource.success(it.mapToCategories()))
                }
            }
            catch (e: IOException){
                emit(Resource.error(e.localizedMessage!!, listOf(Category())))
            }

        }.flowOn(ioDispatcher)

This is a part of my Dao

@Query("select * from DbCategory")
    abstract fun getAllCategories(): Flow<List<DbCategory>>

Part of my db interface impl

override suspend fun getAllCategories(): Flow<List<DbCategory>> =
        dbCategoryDao.getAllCategories().distinctUntilChanged()

EDIT 1

fragment_category.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/category_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

Upvotes: 0

Views: 612

Answers (1)

umer farooq
umer farooq

Reputation: 183

You can solve this by using "StateRestorationPolicy" , recyclerview:1.2.0-alpha02 release StateRestorationPolicy has been introduced. It could be a better approach to the given problem.

Also, @rubén-viguera shared more details in the answer below. https://stackoverflow.com/a/61609823/892500

Upvotes: 2

Related Questions