-
-
Save gpeal/9925b6333220dcdd3ad29d7c5081c5ea to your computer and use it in GitHub Desktop.
| 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 updated both gists
Thanks for sharing this, very helpful.
Hey! I love this, however I tried to implement it on my project and I am having issues with ProGuard/R8:
NoSuchMethodException: ... .bind [class android.view.View]
Did you @gpeal add any special ProGuard rules for this?
I faced the similar issue with @sjaramillo10. Could anyone find a solution ?
@oiyio I guess you could add a ProGuard keep rule to keep the bind() method of all classes that extend ViewBinding. We ended up using a different approach that does not require reflection.
Ok i solved the issue by adding the following code to proguard file :
-keep class com.example.mypackage.databinding.* {
public static ** inflate( ** );
public static ** bind( ** );
}
Any demo code for activity ?
Could you provide the cast method? Also, how do you handle this for
Activity?