fragmentviewbindingdelegate-kt
fragmentviewbindingdelegate-kt copied to clipboard
Feature Request: Offer ::inflate Support For Fragments
FragmentViewBindingDelegate is designed for use with ::bind on the ViewBinding object. AFAICT, this requires us to still manually inflate the layout for use in onCreateView(). ::bind then retrieves those inflated widgets from the fragment's view.
The downside of this is we still wind up with a reference to the layout resource ID for use in the manual inflation step. We have to make sure that we are careful and inflate the right layout in onCreateView(). Usually that will be obvious, but possibly not all the time.
It seems like we should be able to use ::inflate in a slightly-tweaked edition of FragmentViewBindingDelegate. I put this in a project and it seems to work in very light testing:
import android.view.LayoutInflater
import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import androidx.viewbinding.ViewBinding
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
class FragmentViewBindingDelegateAlt<T : ViewBinding>(
val fragment: Fragment,
val viewBindingFactory: (LayoutInflater) -> T
) : ReadOnlyProperty<Fragment, T> {
private var binding: T? = null
init {
fragment.lifecycle.addObserver(object : DefaultLifecycleObserver {
val viewLifecycleOwnerLiveDataObserver =
Observer<LifecycleOwner?> {
val viewLifecycleOwner = it ?: return@Observer
viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
binding = null
}
})
}
override fun onCreate(owner: LifecycleOwner) {
fragment.viewLifecycleOwnerLiveData.observeForever(viewLifecycleOwnerLiveDataObserver)
}
override fun onDestroy(owner: LifecycleOwner) {
fragment.viewLifecycleOwnerLiveData.removeObserver(viewLifecycleOwnerLiveDataObserver)
}
})
}
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
val binding = binding
if (binding != null) {
return binding
}
val lifecycle = fragment.viewLifecycleOwner.lifecycle
if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
throw IllegalStateException("Do not attempt to use the binding until onCreateView() is called")
}
return viewBindingFactory(thisRef.layoutInflater).also { this.binding = it }
}
}
fun <T : ViewBinding> Fragment.viewBindingAlt(bindingInflater: (LayoutInflater) -> T) =
FragmentViewBindingDelegateAlt(this, bindingInflater)
(the Alt suffix is because this project has your library in it)
Other than that suffix, the changes are:
- The extension function is designed to take
::inflateinstead of::bind - The corresponding property in the delegate's constructor also is designed for
::inflate getValue()calls that revised factory usinggetLayoutInflater()on theFragment
According to the JavaDocs, it appears as though it should be safe for us to call getLayoutInflater() from this point in the lifecycle.
We use it the same basic way as what you have, just with ::inflate:
private val binding by viewBindingAlt(FooBinding::inflate)
The benefit is that now we can refer to the binding object's root for onCreateView() of the fragment, instead of having to manually inflate the layout ourselves:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return binding.root
}
Do you feel that this approach is safe? I'm wondering if you considered it and rejected it for some reason.
https://github.com/Zhuinden/fragmentviewbindingdelegate-kt/pull/7#issuecomment-796466177
This kind of worked, but the problem is since the container is not being used, any dimensions that depend on that reference are completely lost since this is the equivalent of doing .inflate(LayoutInflater) instead of .inflate(LayoutInflater, ViewGroup?, Boolean) as far as I understood. The result is an inflated fragment with its width and height collapsed since match_parent no longer works in this scenario.