Skip to content

Instantly share code, notes, and snippets.

@gpeal

gpeal/FragmentA.kt

Last active May 12, 2021
Embed
What would you like to do?
View Binding Delegates
class WifiNetworksFragment : TonalFragment(R.layout.wifi_networks_fragment) {
// This automatically creates and clears the binding in a lifecycle-aware way.
private val binding: WifiNetworksFragmentBinding by viewBinding()
...
}
class WifiNetworkView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
// This does the inflate too.
private val binding: WifiNetworkViewBinding by viewBinding()
...
}
import android.os.Handler
import android.os.Looper
import android.view.View
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.viewbinding.ViewBinding
import com.tonal.trainer.utils.cast
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
/**
* Create bindings for a view similar to bindView.
*
* To use, just call
* private val binding: FHomeWorkoutDetailsBinding by viewBinding()
* with your binding class and access it as you normally would.
*/
inline fun <reified T : ViewBinding> Fragment.viewBinding() = FragmentViewBindingDelegate(T::class.java, this)
class FragmentViewBindingDelegate<T : ViewBinding>(
private val bindingClass: Class<T>,
val fragment: Fragment
) : ReadOnlyProperty<Fragment, T> {
private val clearBindingHandler by lazy(LazyThreadSafetyMode.NONE) { Handler(Looper.getMainLooper()) }
private var binding: T? = null
private val bindMethod = bindingClass.getMethod("bind", View::class.java)
init {
fragment.viewLifecycleOwnerLiveData.observe(fragment) { viewLifecycleOwner ->
viewLifecycleOwner.lifecycle.addObserver(object : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
// Lifecycle listeners are called before onDestroyView in a Fragment.
// However, we want views to be able to use bindings in onDestroyView
// to do cleanup so we clear the reference one frame later.
clearBindingHandler.post { binding = null }
}
})
}
}
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
// onCreateView may be called between onDestroyView and next Main thread cycle.
// In this case [binding] refers to the previous fragment view. Check that binding's root view matches current fragment view
if (binding != null && binding?.root !== thisRef.view) {
binding = null
}
binding?.let { return it }
val lifecycle = fragment.viewLifecycleOwner.lifecycle
if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
error("Cannot access view bindings. View lifecycle is ${lifecycle.currentState}!")
}
binding = bindMethod.invoke(null, thisRef.requireView()).cast<T>()
return binding!!
}
}
@Suppress("UNCHECKED_CAST")
private fun <T> Any.cast(): T = this as T
/**
* Create bindings for a view similar to bindView.
*
* To use, just call:
* private val binding: FHomeWorkoutDetailsBinding by viewBinding()
* with your binding class and access it as you normally would.
*/
inline fun <reified T : ViewBinding> ViewGroup.viewBinding() = ViewBindingDelegate(T::class.java, this)
class ViewBindingDelegate<T : ViewBinding>(
private val bindingClass: Class<T>,
private val view: ViewGroup
) : ReadOnlyProperty<ViewGroup, T> {
private val binding: T = try {
val inflateMethod = bindingClass.getMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.javaPrimitiveType)
inflateMethod.invoke(null, LayoutInflater.from(view.context), view, true).cast<T>()
} catch (e: NoSuchMethodException) {
// <merge> tags don't have the boolean parameter.
val inflateMethod = bindingClass.getMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java)
inflateMethod.invoke(null, LayoutInflater.from(view.context), view).cast<T>()
}
override fun getValue(thisRef: ViewGroup, property: KProperty<*>): T = binding
}
@Suppress("UNCHECKED_CAST")
private fun <T> Any.cast(): T = this as T
@zeusalmighty717

This comment has been minimized.

Copy link

@zeusalmighty717 zeusalmighty717 commented Apr 1, 2021

Could you provide the cast method? Also, how do you handle this for Activity?

@gpeal

This comment has been minimized.

Copy link
Owner Author

@gpeal gpeal commented Apr 1, 2021

@zeusalmighty717 updated both gists

@mirokolodii

This comment has been minimized.

Copy link

@mirokolodii mirokolodii commented Apr 13, 2021

Thanks for sharing this, very helpful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment