Skip to content

Commit 0e5c091

Browse files
committed
chore: vendor 'vbpd' library
https://github.com/androidbroadcast/ViewBindingPropertyDelegate/ Simplifies the API for View Bindings Chosen as it has a simple, non-reflection-based API There is a line of duplication of `R.layout` references/ViewBinding caused by the lack of reflection Issue 11116
1 parent 3a94228 commit 0e5c091

7 files changed

Lines changed: 666 additions & 0 deletions

File tree

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* Copyright (c) 2025 David Allison <davidallisongithub@gmail.com>
3+
*
4+
* This program is free software; you can redistribute it and/or modify it under
5+
* the terms of the GNU General Public License as published by the Free Software
6+
* Foundation; either version 3 of the License, or (at your option) any later
7+
* version.
8+
*
9+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
10+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
11+
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License along with
14+
* this program. If not, see <http://www.gnu.org/licenses/>.
15+
*
16+
* This file incorporates code under the following license:
17+
*
18+
* Copyright 2020-2025 Kirill Rozov
19+
*
20+
* Licensed under the Apache License, Version 2.0 (the "License");
21+
* you may not use this file except in compliance with the License.
22+
* You may obtain a copy of the License at
23+
*
24+
* http://www.apache.org/licenses/LICENSE-2.0
25+
*
26+
* Unless required by applicable law or agreed to in writing, software
27+
* distributed under the License is distributed on an "AS IS" BASIS,
28+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29+
* See the License for the specific language governing permissions and
30+
* limitations under the License.
31+
*/
32+
33+
@file:JvmName("ActivityViewBindings")
34+
35+
package dev.androidbroadcast.vbpd
36+
37+
import android.app.Activity
38+
import android.app.Application.ActivityLifecycleCallbacks
39+
import android.os.Bundle
40+
import android.view.View
41+
import androidx.annotation.IdRes
42+
import androidx.annotation.RestrictTo
43+
import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP
44+
import androidx.viewbinding.ViewBinding
45+
import dev.androidbroadcast.vbpd.internal.findRootView
46+
import dev.androidbroadcast.vbpd.internal.requireViewByIdCompat
47+
import dev.androidbroadcast.vbpd.internal.weakReference
48+
import kotlin.reflect.KProperty
49+
50+
@RestrictTo(LIBRARY_GROUP)
51+
public class ActivityViewBindingProperty<in A : Activity, T : ViewBinding>(
52+
viewBinder: (A) -> T,
53+
) : LazyViewBindingProperty<A, T>(viewBinder) {
54+
private var lifecycleCallbacks: ActivityLifecycleCallbacks? = null
55+
private var activity: Activity? by weakReference(null)
56+
57+
override fun getValue(
58+
thisRef: A,
59+
property: KProperty<*>,
60+
): T =
61+
super
62+
.getValue(thisRef, property)
63+
.also { registerLifecycleCallbacksIfNeeded(thisRef) }
64+
65+
private fun registerLifecycleCallbacksIfNeeded(activity: Activity) {
66+
if (lifecycleCallbacks != null) return
67+
this.activity = activity
68+
VBActivityLifecycleCallbacks()
69+
.also { callbacks -> this.lifecycleCallbacks = callbacks }
70+
.let(activity.application::registerActivityLifecycleCallbacks)
71+
}
72+
73+
override fun clear() {
74+
super.clear()
75+
val lifecycleCallbacks = lifecycleCallbacks
76+
if (lifecycleCallbacks != null) {
77+
activity?.application?.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
78+
}
79+
80+
this.activity = null
81+
this.lifecycleCallbacks = null
82+
}
83+
84+
private inner class VBActivityLifecycleCallbacks : ActivityLifecycleCallbacks {
85+
override fun onActivityCreated(
86+
activity: Activity,
87+
savedInstanceState: Bundle?,
88+
) {
89+
}
90+
91+
override fun onActivityStarted(activity: Activity) {
92+
}
93+
94+
override fun onActivityResumed(activity: Activity) {
95+
}
96+
97+
override fun onActivityPaused(activity: Activity) {
98+
}
99+
100+
override fun onActivityStopped(activity: Activity) {
101+
}
102+
103+
override fun onActivitySaveInstanceState(
104+
activity: Activity,
105+
outState: Bundle,
106+
) {
107+
}
108+
109+
override fun onActivityDestroyed(activity: Activity) {
110+
if (activity === this@ActivityViewBindingProperty.activity) clear()
111+
}
112+
}
113+
}
114+
115+
/**
116+
* Create new [ViewBinding] associated with the [Activity].
117+
* Cached [ViewBinding] will be cleaned after [Activity.onDestroy]
118+
*
119+
* @param viewBinder Function that creates a new instance of [ViewBinding]. Use `MyViewBinding::bind` as default
120+
*
121+
* @return [ViewBindingProperty] associated with the [Activity]'s view
122+
*/
123+
@JvmName("viewBindingActivityWithCallbacks")
124+
@Suppress("UnusedReceiverParameter")
125+
public fun <A : Activity, T : ViewBinding> Activity.viewBinding(viewBinder: (A) -> T): ViewBindingProperty<A, T> =
126+
ActivityViewBindingProperty(viewBinder = viewBinder)
127+
128+
/**
129+
* Create new [ViewBinding] associated with the [Activity].
130+
* Cached [ViewBinding] will be cleaned after [Activity.onDestroy]
131+
*
132+
* @param vbFactory Function that creates a new instance of [ViewBinding]. Use `MyViewBinding::bind` as default
133+
* @param viewProvider Function that provides a root view for the view binding
134+
*
135+
* @return [ViewBindingProperty] associated with the [Activity]'s view
136+
*/
137+
@JvmName("viewBindingActivityWithCallbacks")
138+
public inline fun <A : Activity, T : ViewBinding> Activity.viewBinding(
139+
crossinline vbFactory: (View) -> T,
140+
crossinline viewProvider: (A) -> View = ::findRootView,
141+
): ViewBindingProperty<A, T> = viewBinding { activity -> vbFactory(viewProvider(activity)) }
142+
143+
/**
144+
* Create new [ViewBinding] associated with the [Activity][this] and allow customization of how
145+
* a [View] will be bound to the view binding.
146+
*
147+
* @param vbFactory Function that creates a new instance of [ViewBinding]. `MyViewBinding::bind` can be used
148+
* @param viewBindingRootId Root view's id that will be used as a root for the view binding
149+
*
150+
* @return [ViewBindingProperty] associated with the [Activity]'s view
151+
*/
152+
@JvmName("viewBindingActivity")
153+
public inline fun <A : Activity, T : ViewBinding> Activity.viewBinding(
154+
crossinline vbFactory: (View) -> T,
155+
@IdRes viewBindingRootId: Int,
156+
): ViewBindingProperty<A, T> =
157+
viewBinding { activity ->
158+
vbFactory(activity.requireViewByIdCompat(viewBindingRootId))
159+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
* Copyright (c) 2025 David Allison <davidallisongithub@gmail.com>
3+
*
4+
* This program is free software; you can redistribute it and/or modify it under
5+
* the terms of the GNU General Public License as published by the Free Software
6+
* Foundation; either version 3 of the License, or (at your option) any later
7+
* version.
8+
*
9+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
10+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
11+
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License along with
14+
* this program. If not, see <http://www.gnu.org/licenses/>.
15+
*
16+
* This file incorporates code under the following license:
17+
*
18+
* Copyright 2020-2025 Kirill Rozov
19+
*
20+
* Licensed under the Apache License, Version 2.0 (the "License");
21+
* you may not use this file except in compliance with the License.
22+
* You may obtain a copy of the License at
23+
*
24+
* http://www.apache.org/licenses/LICENSE-2.0
25+
*
26+
* Unless required by applicable law or agreed to in writing, software
27+
* distributed under the License is distributed on an "AS IS" BASIS,
28+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29+
* See the License for the specific language governing permissions and
30+
* limitations under the License.
31+
*/
32+
33+
@file:JvmName("FragmentViewBindings")
34+
35+
package dev.androidbroadcast.vbpd
36+
37+
import android.view.View
38+
import androidx.annotation.IdRes
39+
import androidx.annotation.RestrictTo
40+
import androidx.fragment.app.DialogFragment
41+
import androidx.fragment.app.Fragment
42+
import androidx.fragment.app.FragmentManager
43+
import androidx.viewbinding.ViewBinding
44+
import dev.androidbroadcast.vbpd.internal.findRootView
45+
import dev.androidbroadcast.vbpd.internal.requireViewByIdCompat
46+
import dev.androidbroadcast.vbpd.internal.weakReference
47+
import kotlin.reflect.KProperty
48+
49+
private class FragmentViewBindingProperty<F : Fragment, T : ViewBinding>(
50+
viewBinder: (F) -> T,
51+
) : LazyViewBindingProperty<F, T>(viewBinder) {
52+
private var lifecycleCallbacks: FragmentManager.FragmentLifecycleCallbacks? = null
53+
private var fragmentManager: FragmentManager? = null
54+
55+
override fun getValue(
56+
thisRef: F,
57+
property: KProperty<*>,
58+
): T {
59+
val viewBinding = super.getValue(thisRef, property)
60+
registerLifecycleCallbacksIfNeeded(thisRef)
61+
return viewBinding
62+
}
63+
64+
/**
65+
* Register callbacks to listen event about Fragment's View
66+
*/
67+
private fun registerLifecycleCallbacksIfNeeded(fragment: Fragment) {
68+
if (lifecycleCallbacks != null) return
69+
70+
val fragmentManager =
71+
fragment.parentFragmentManager
72+
.also { fm -> this.fragmentManager = fm }
73+
lifecycleCallbacks =
74+
VBFragmentLifecycleCallback(fragment).also { callbacks ->
75+
fragmentManager.registerFragmentLifecycleCallbacks(callbacks, false)
76+
}
77+
}
78+
79+
override fun clear() {
80+
super.clear()
81+
82+
val lifecycleCallbacks = lifecycleCallbacks
83+
if (lifecycleCallbacks != null) {
84+
fragmentManager?.unregisterFragmentLifecycleCallbacks(lifecycleCallbacks)
85+
}
86+
87+
fragmentManager = null
88+
this.lifecycleCallbacks = null
89+
}
90+
91+
inner class VBFragmentLifecycleCallback(
92+
fragment: Fragment,
93+
) : FragmentManager.FragmentLifecycleCallbacks() {
94+
private val fragment by weakReference(fragment)
95+
96+
override fun onFragmentViewDestroyed(
97+
fm: FragmentManager,
98+
f: Fragment,
99+
) {
100+
if (fragment === f) clear()
101+
}
102+
}
103+
}
104+
105+
/**
106+
* Create new [ViewBinding] associated with the [Fragment]
107+
*/
108+
@Suppress("UnusedReceiverParameter")
109+
@JvmName("viewBindingFragmentWithCallbacks")
110+
public fun <F : Fragment, T : ViewBinding> Fragment.viewBinding(viewBinder: (F) -> T): ViewBindingProperty<F, T> =
111+
fragmentViewBinding(viewBinder = viewBinder)
112+
113+
/**
114+
* Create new [ViewBinding] associated with the [Fragment]
115+
*
116+
* @param vbFactory Function that creates a new instance of [ViewBinding]. `MyViewBinding::bind` can be used
117+
* @param viewProvider Provide a [View] from the Fragment. By default call [Fragment.requireView]
118+
*/
119+
@Suppress("UnusedReceiverParameter")
120+
@JvmName("viewBindingFragment")
121+
public inline fun <F : Fragment, T : ViewBinding> Fragment.viewBinding(
122+
crossinline vbFactory: (View) -> T,
123+
crossinline viewProvider: (F) -> View = Fragment::requireView,
124+
): ViewBindingProperty<F, T> = fragmentViewBinding(viewBinder = { fragment -> viewProvider(fragment).let(vbFactory) })
125+
126+
/**
127+
* Create new [ViewBinding] associated with the [Fragment]
128+
*
129+
* @param vbFactory Function that creates a new instance of [ViewBinding]. `MyViewBinding::bind` can be used
130+
* @param viewBindingRootId Root view's id that will be used as a root for the view binding
131+
*/
132+
@JvmName("viewBindingFragmentWithCallbacks")
133+
public inline fun <F : Fragment, T : ViewBinding> Fragment.viewBinding(
134+
crossinline vbFactory: (View) -> T,
135+
@IdRes viewBindingRootId: Int,
136+
): ViewBindingProperty<F, T> =
137+
when (this) {
138+
is DialogFragment -> {
139+
fragmentViewBinding { fragment ->
140+
(fragment as DialogFragment)
141+
.findRootView(viewBindingRootId)
142+
.let(vbFactory)
143+
}
144+
}
145+
146+
else -> {
147+
fragmentViewBinding { fragment ->
148+
fragment
149+
.requireView()
150+
.requireViewByIdCompat<View>(viewBindingRootId)
151+
.let(vbFactory)
152+
}
153+
}
154+
}
155+
156+
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
157+
public fun <F : Fragment, T : ViewBinding> fragmentViewBinding(viewBinder: (F) -> T): ViewBindingProperty<F, T> =
158+
FragmentViewBindingProperty(viewBinder)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
## ViewBindingPropertyDelegate (`vbdp`)
2+
3+
Simplifies [view bindings](https://developer.android.com/topic/libraries/view-binding):
4+
5+
* Manages ViewBinding lifecycle and clears the reference to it to prevent memory leaks
6+
* Eliminates the need to keep nullable references to Views or ViewBindings
7+
* Creates ViewBinding lazily
8+
9+
### Implementation notes
10+
11+
This library has been vendored to reduce the number of dependencies, and to remove code we will not use
12+
13+
The reflection-based capabilities of this library are removed, to focus on performance
14+
15+
Original source: https://github.com/androidbroadcast/ViewBindingPropertyDelegate/tree/73c87714df0d3e464a68c6e5f149e775fcd75bba

0 commit comments

Comments
 (0)