lsjwzh / RecyclerViewPager

Deprecated
Apache License 2.0
3.53k stars 667 forks source link

Does not allow null adapter #188

Open sevar83 opened 6 years ago

sevar83 commented 6 years ago

If you try to set null adapter, it will throw NPE in RecyclerViewPagerAdapter's constructor at the .hasStableIds() call. But setting a null adapter is a perfectly correct usage, especially when your adapter has references to context and other dependencies. Nulling it out in the RecyclerViewPager prevents memory leaks. One way to be fixed is all overridden methods in RecyclerViewPagerAdapter to have proper null checks before accessing the wrapper adapter. But RecyclerViewPagerAdapter assumes the wrapper adapter is non-null.

Here's a reflection workaround for anyone interested:

class NullableRecyclerViewPager : RecyclerViewPager() {
    // ...
    override fun setAdapter(adapter: Adapter<*>?) {
        setPrivateField("mViewPagerAdapter", adapter)
        // Emulate RecyclerView.setAdapter()
        this.isLayoutFrozen = false
        RecyclerView::class.java.getDeclaredMethod("setAdapterInternal",
                RecyclerView.Adapter::class.java, Boolean::class.java, Boolean::class.java).apply {
            isAccessible = true
            invoke(this@ScaleCollapsingRecyclerViewPager, adapter, false, true)
        }
        this.requestLayout()
    }

    override fun swapAdapter(adapter: Adapter<*>?, removeAndRecycleExistingViews: Boolean) {
        setPrivateField("mViewPagerAdapter", adapter)
        // Emulate RecyclerView.swapAdapter()
        this.isLayoutFrozen = false
        RecyclerView::class.java.getDeclaredMethod("setAdapterInternal",
                RecyclerView.Adapter::class.java, Boolean::class.java, Boolean::class.java).apply {
            isAccessible = true
            invoke(this@ScaleCollapsingRecyclerViewPager, adapter, true, removeAndRecycleExistingViews)
        }
        this.requestLayout()
    }

    private fun setPrivateField(fieldName: String, value: Any?) {
        val field: Field = RecyclerViewPager::class.java.getDeclaredField(fieldName)
        field.isAccessible = true
        field.set(this, value)
    }
}

p.s. You'll need also to add this in your ProGuard config:

-keepclassmembers class android.support.v7.widget.RecyclerView {
    void setAdapterInternal(...);
}
-keepclassmembers class com.lsjwzh.widget.recyclerviewpager.RecyclerViewPager {
    com.lsjwzh.widget.recyclerviewpager.RecyclerViewPagerAdapter mViewPagerAdapter;
}