Search code examples
androidkotlindata-bindingandroid-viewpager2viewstub

Binding is not applying to a fragments associated with ViewStub


I have the current problem. Right now I'm trying to lazy inflate bunch of fragments for better performance, which are part of a ViewPager. The only solution I found was the usage of ViewStub. Here's where what I did so far:

abstract class ViewStubFragment: BaseFragment() {

    private lateinit var binding: FragmentViewStubBinding

    private var mSavedInstanceState: Bundle? = null
    private var hasInflated = false
    private var viewStub: ViewStub? = null
    private var visible = false

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentViewStubBinding.inflate(inflater, container, false)

        viewStub = binding.fragmentViewStub.viewStub
        viewStub?.layoutResource = getViewStubLayoutResource()
        mSavedInstanceState = savedInstanceState

        if(visible && !hasInflated) {
            val inflatedView = viewStub?.inflate()
            if (inflatedView != null) {
                onCreateViewAfterViewStubInflated(inflatedView, mSavedInstanceState)
            }
        }

        return binding.root
    }

    protected abstract fun onCreateViewAfterViewStubInflated(
        inflatedView: View,
        savedInstanceState: Bundle?
    )

    @LayoutRes
    protected abstract fun getViewStubLayoutResource(): Int


    @CallSuper
    protected fun afterViewStubInflated(originalViewContainerWithViewStub: View?) {
        hasInflated = true
        if (originalViewContainerWithViewStub != null) {
            val progressBar = binding.progressBar
            progressBar.visibility = View.GONE
        }
    }

    override fun onResume() {
        super.onResume()

        visible = true
        if (viewStub != null && !hasInflated) {
            val inflatedView = viewStub!!.inflate()
            onCreateViewAfterViewStubInflated(inflatedView, mSavedInstanceState)
            afterViewStubInflated(view)
        }
    }

The corresponding layout:

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ProgressBar
            android:id="@+id/progress_bar"
            android:foregroundGravity="center"
            android:layout_gravity="center"
            android:indeterminate="true"
            android:layout_width="48dp"
            android:layout_height="48dp"/>
        <ViewStub
            android:id="@+id/fragment_view_stub"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

    </FrameLayout>

</layout>

Part of the code from HomeFragment which I'm trying to lazily inflate:

class HomeFragment : ViewStubFragment() {

    lateinit var binding: FragmentHomeBinding

........

    override fun onCreateViewAfterViewStubInflated(
        inflatedView: View,
        savedInstanceState: Bundle?
    ) {
        setDataBinding()
        observeLiveData()
    }

    override fun getViewStubLayoutResource(): Int {
        binding = FragmentHomeBinding.inflate(layoutInflater)
        return binding.root.sourceLayoutResId
    }

The problem is when setDataBinding() is called, the binding for some reason is not applied, thus Views like RecyclerView are not showing on the screen or click events aren not working. I have searched for hours but couldn't find solution related to this issue.


Solution

  • After another hour of struggle I found a solution, not sure if it is the best possible.

            if(visible && !hasInflated) {
                inflatedViewBinding = binding.fragmentViewStub.binding
                val inflatedView = viewStub?.inflate()
                if (inflatedView != null) {
                    onCreateViewAfterViewStubInflated(inflatedView, mSavedInstanceState, inflatedViewBinding)
                }
            }
    

    I passed the stored in ViewStubProxy binding (associated with the inflated layout) as argument in onCreateViewAfterViewStubInflated() and then assign the HomeFragment binding to it.

        override fun onCreateViewAfterViewStubInflated(
            inflatedView: View,
            savedInstanceState: Bundle?,
            binding: ViewDataBinding?
        ) {
            this.binding = binding as FragmentHomeBinding
            .......
        }