24hg824g2g4gh
24hg824g2g4gh

Reputation: 57

Swapping adapters inside the ConcatAdapter

I need to swap two adapters inside the ConcatAdapter but the list of adapters that is returned by getAdapters() is immutable and won't let me use the java.util.Collections utility.

So far what I've tried was to turn that list into a mutable list, swap the items and set the list back but it's ugly to look at since I'm doing this inside the ItemTouchHelper's callback.

Would copying the class from the source and make that list mutable instead work? Are there any better solutions?

Upvotes: 1

Views: 733

Answers (2)

Zain
Zain

Reputation: 40878

Instead of the Anti-pattern of hitting private API fields with reflections, we can exchange the underlying data of both adapters and then notifyItemChanged().

An adapter in the ConcatAdapter has a data class item, you can expose it to caller to be modified.

Here's a demo:

// we need to swap adapter at pos1 with adapter at pos2 from contactAdapter
private fun swapAdapters(pos1:Int, pos2:Int) {

    // assuming the RV adapter is MyRVAdapter, and holds "data" field
    val adapter1 = concatAdapter.adapters[pos1] as MyRVAdapter
    val adapter2 = concatAdapter.adapters[pos2] as MyRVAdapter

    // Exchange adapters data
    val data1 = adapter1.data.copy()
    val data2 = adapter2.data.copy()
    adapter1.data = data2
    adapter2.data = data1

    // reflect the change
    concatAdapter.notifyItemChanged(pos1)
    concatAdapter.notifyItemChanged(pos2)
}

Upvotes: 0

24hg824g2g4gh
24hg824g2g4gh

Reputation: 57

I tried to do some hacky stuff with reflection, the ConcatAdapter class doesn't hold a reference to the underlying adapters but there's a ConcatAdapterController field (which class is private) that has a mWrappers field which is basically the one returned by getAdapters()

Anyway here's the code I've used and it's working fine

val controllerClass = Class.forName("androidx.recyclerview.widget.ConcatAdapterController")
    val controllerField = ConcatAdapter::class.java.declaredFields.find { it.name == "mController" }?.apply {
        this.isAccessible = true
    }
    val controller = controllerField?.get(this)
    val wrappersField = controllerClass.declaredFields.find { it.name == "mWrappers"}?.apply {
        this.isAccessible = true
    }
    val wrappers = wrappersField?.get(controller) as List<*>

    Collections.swap(wrappers, from, to)

    notifyItemMoved(from, to)

Upvotes: 0

Related Questions