Reputation: 43
I'm using navigation component in my app and for get data from api i'm using retrofit in MVVM architecture, i want get data from api and display in nested RecyclerView, this is worked and not problem for display data into nested Recylerview but when go to fragment detail and back to previous fragment not saved state and position item in horizontal list ,how to can display current position RecyclerView when back to previous fragment?
parent adapter
import kotlinx.android.synthetic.main.item_main_shaping_group.view.*
class MainShapingAdapter(
private val listGroup: MutableList<MainModel>,
private val listener: ListItemClick
) : RecyclerView.Adapter<MainShapingAdapter.MyViewHolder>(),
MainShapingChildAdapter.ListItemClickChild {
private val viewPool = RecyclerView.RecycledViewPool()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val layout = LayoutInflater.from(parent.context)
.inflate(R.layout.item_main_shaping_group, parent, false)
return MyViewHolder(layout)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.itemView.apply {
tv_titleGroup_itemGroup.text = listGroup[position].category.categoryTitle
rv_itemGroup.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, true)
rv_itemGroup.adapter = MainShapingChildAdapter(
listGroup[position].listProduct.toMutableList(),
this@MainShapingAdapter
)
rv_itemGroup.isNestedScrollingEnabled = false
rv_itemGroup.setRecycledViewPool(viewPool)
btn_more_itemGroup.setOnClickListener {
listener.itemOnclickCategory(listGroup[position].category)
}
}
}
override fun getItemCount(): Int = listGroup.size
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
}
interface ListItemClick {
fun itemOnclickCategory(category: CategoryModel)
fun itemOnclickChild(product: Product)
}
override fun childOnclick(product: Product) {
listener.itemOnclickChild(product)
}
override fun onViewRecycled(holder: MyViewHolder) {
super.onViewRecycled(holder)
Log.d(ConstantApp.TAG, "onViewRecycled 1")
}
}
childe adapter
import kotlinx.android.synthetic.main.item_main_shaping_child.view.*
class MainShapingChildAdapter(
private val listProduct: MutableList<Product>,
private val listener: ListItemClickChild
) : RecyclerView.Adapter<MainShapingChildAdapter.MyViewHolder>() {
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val layout = LayoutInflater.from(parent.context)
.inflate(R.layout.item_main_shaping_child, parent, false)
return MyViewHolder(layout)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.itemView.apply {
Glide.with(context).load(listProduct[position].productCover)
.into(iv_coverProduct_shapingChild)
tv_titleProduct_shapingChild.text = listProduct[position].productTitle
tv_priceProduct_shapingChild.text = listProduct[position].productPrice.toString()
setOnClickListener {
listener.childOnclick(listProduct[position])
}
}
}
override fun getItemCount(): Int = listProduct.size
interface ListItemClickChild {
fun childOnclick(product: Product)
}
}
Upvotes: 4
Views: 2050
Reputation: 3
What worked for me, while using this (Ruben Sousa's blog entry), was using a view model to store the bundle and use it on onDestroyView
with scrollStateHolder?.onSaveInstanceState(viewModel.bundle)
and on onCreateView with scrollStateHolder = ScrollStateHolder(viewModel.bundle)
. Just replaced outBundle
and savedInstanceState
with those and it's working while changing fragment and/or rotation.
I used his ParentAdapter
and ChildAdapter
with his ScrollStateHolder
modified with my models and views and it's working well. Later I'll try with other types of adapters and multi-views.
You could also try, a little more "ugly way" of doing it, create the layout managers that will be used in the child adapters in your fragment and past them when to their respective instances. Then, with the method described before, save and restore theor instance state. [not tested]
Upvotes: 0
Reputation: 498
I used this tutorial to make my carousel recycler views hold their scroll state: https://rubensousa.com/2019/08/27/saving_scroll_state_of_nested_recyclerviews/
Basically you have to create a new class:
import android.os.Bundle
import android.os.Parcelable
import androidx.recyclerview.widget.RecyclerView
/**
* Persists scroll state for nested RecyclerViews.
*
* 1. Call [saveScrollState] in [RecyclerView.Adapter.onViewRecycled]
* to save the scroll position.
*
* 2. Call [restoreScrollState] in [RecyclerView.Adapter.onBindViewHolder]
* after changing the adapter's contents to restore the scroll position
*/
class ScrollStateHolder(savedInstanceState: Bundle? = null) {
companion object {
const val STATE_BUNDLE = "scroll_state_bundle"
}
/**
* Provides a key that uniquely identifies a RecyclerView
*/
interface ScrollStateKeyProvider {
fun getScrollStateKey(): String?
}
/**
* Persists the [RecyclerView.LayoutManager] states
*/
private val scrollStates = hashMapOf<String, Parcelable>()
/**
* Keeps track of the keys that point to RecyclerViews
* that have new scroll states that should be saved
*/
private val scrolledKeys = mutableSetOf<String>()
init {
savedInstanceState?.getBundle(STATE_BUNDLE)?.let { bundle ->
bundle.keySet().forEach { key ->
bundle.getParcelable<Parcelable>(key)?.let {
scrollStates[key] = it
}
}
}
}
fun setupRecyclerView(recyclerView: RecyclerView, scrollKeyProvider: ScrollStateKeyProvider) {
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
saveScrollState(recyclerView, scrollKeyProvider)
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val key = scrollKeyProvider.getScrollStateKey()
if (key != null && dx != 0) {
scrolledKeys.add(key)
}
}
})
}
fun onSaveInstanceState(outState: Bundle) {
val stateBundle = Bundle()
scrollStates.entries.forEach {
stateBundle.putParcelable(it.key, it.value)
}
outState.putBundle(STATE_BUNDLE, stateBundle)
}
fun clearScrollState() {
scrollStates.clear()
scrolledKeys.clear()
}
/**
* Saves this RecyclerView layout state for a given key
*/
fun saveScrollState(
recyclerView: RecyclerView,
scrollKeyProvider: ScrollStateKeyProvider
) {
val key = scrollKeyProvider.getScrollStateKey() ?: return
// Check if we scrolled the RecyclerView for this key
if (scrolledKeys.contains(key)) {
val layoutManager = recyclerView.layoutManager ?: return
layoutManager.onSaveInstanceState()?.let { scrollStates[key] = it }
scrolledKeys.remove(key)
}
}
/**
* Restores this RecyclerView layout state for a given key
*/
fun restoreScrollState(
recyclerView: RecyclerView,
scrollKeyProvider: ScrollStateKeyProvider
) {
val key = scrollKeyProvider.getScrollStateKey() ?: return
val layoutManager = recyclerView.layoutManager ?: return
val savedState = scrollStates[key]
if (savedState != null) {
layoutManager.onRestoreInstanceState(savedState)
} else {
// If we don't have any state for this RecyclerView,
// make sure we reset the scroll position
layoutManager.scrollToPosition(0)
}
// Mark this key as not scrolled since we just restored the state
scrolledKeys.remove(key)
}
}
Then you use this class to store the state when the fragment/activity is detached/destroyed.
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
scrollStateHolder = ScrollStateHolder(savedInstanceState)
return inflater.inflate(R.layout.layout, container, false)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
scrollStateHolder.onSaveInstanceState(outState)
}
You also have to use these two lines somewhere in your code:
scrollStateHolder.setupRecyclerView(itemView.child_recipes_rv, this)
scrollStateHolder.restoreScrollState(itemView.child_recipes_rv, this)
I'm saying 'somewhere' because that depends on your specific implementation. I did a variation of what the guy did in the tutorial, so that's up to you. In my case, those two lines are called one after the other when I'm building each child recycler view.
Basically you have to have an identifier for every child recyclerview. And you use that as a key in your map (see ScrollStateHolder.kt), and then when saving the state of the fragment/activity you're saving the state and that includes the scrolling state of the recyclerview.
Upvotes: 1