Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
.externalNativeBuild
.cxx
firebase-debug.log
.worktrees/
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,15 @@ class EagerViewBindingPropertyTest {
val second = property.getValue(thisRef, mockProperty)
assertEquals(first, second)
}

@Test
fun `clear does not affect getValue`() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. clear test lacks visibility 📘 Rule violation ✓ Correctness

The added test function has implicit public visibility because it declares fun without a
visibility modifier. This violates the requirement that all public Kotlin declarations must declare
visibility explicitly.
Agent Prompt
## Issue description
A newly added Kotlin declaration is implicitly `public` because it omits a visibility modifier.

## Issue Context
The project compliance rules require explicit visibility modifiers for any declaration that would otherwise be public by default.

## Fix Focus Areas
- vbpd-core/src/test/kotlin/dev/androidbroadcast/vbpd/EagerViewBindingPropertyTest.kt[44-53]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

val binding = createMockBinding()
val property = EagerViewBindingProperty<Any, ViewBinding>(binding)
val thisRef = Any()

property.clear()
val result = property.getValue(thisRef, mockProperty)
assertEquals(binding, result)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import androidx.viewbinding.ViewBinding
*
* @return [ViewBindingProperty] that holds [ViewBinding] instance
*/
@JvmName("viewBindingFragment")
@JvmName("viewBindingViewGroup")
public inline fun <reified T : ViewBinding> ViewGroup.viewBinding(
createMethod: CreateMethod = CreateMethod.BIND,
): ViewBindingProperty<ViewGroup, T> = viewBinding(T::class.java, createMethod)
Expand All @@ -27,7 +27,7 @@ public inline fun <reified T : ViewBinding> ViewGroup.viewBinding(
*
* @return [ViewBindingProperty] that holds [ViewBinding] instance
*/
@JvmName("viewBindingFragment")
@JvmName("viewBindingViewGroup")
@JvmOverloads
public fun <T : ViewBinding> ViewGroup.viewBinding(
viewBindingClass: Class<T>,
Expand All @@ -50,7 +50,7 @@ public fun <T : ViewBinding> ViewGroup.viewBinding(
*
* @return [ViewBindingProperty] that holds [ViewBinding] instance
*/
@JvmName("viewBindingFragment")
@JvmName("viewBindingViewGroupInflate")
public inline fun <reified T : ViewBinding> ViewGroup.viewBinding(attachToRoot: Boolean = false): ViewBindingProperty<ViewGroup, T> =
viewBinding(T::class.java, attachToRoot)

Expand All @@ -62,7 +62,7 @@ public inline fun <reified T : ViewBinding> ViewGroup.viewBinding(attachToRoot:
*
* @return [ViewBindingProperty] that holds [ViewBinding] instance
*/
@JvmName("viewBindingFragment")
@JvmName("viewBindingViewGroupInflate")
public fun <T : ViewBinding> ViewGroup.viewBinding(
viewBindingClass: Class<T>,
attachToRoot: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public fun DialogFragment.findRootView(
if (viewBindingRootId != 0) requireViewByIdCompat(viewBindingRootId) else this
}
} else {
return requireView().findViewById(viewBindingRootId)
return requireView().requireViewByIdCompat(viewBindingRootId)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Dialog rootid 0 throws 🐞 Bug ✓ Correctness

DialogFragment.findRootView() treats viewBindingRootId==0 as valid when showsDialog=true (returns
decorView), but when showsDialog=false it unconditionally calls
requireViewByIdCompat(viewBindingRootId) and will throw for 0, making the API behavior inconsistent
depending on showsDialog.
Agent Prompt
### Issue description
`DialogFragment.findRootView()` is inconsistent: it supports `viewBindingRootId == 0` when `showsDialog == true` (returns `decorView`), but will throw when `showsDialog == false` because it unconditionally calls `requireViewByIdCompat(0)`.

### Issue Context
This function already encodes `0` as a sentinel value in the dialog path, so the non-dialog path should handle it consistently.

### Fix Focus Areas
- vbpd/src/main/kotlin/dev/androidbroadcast/vbpd/internal/VbpdUtils.kt[45-56]

### Suggested change (concept)
In the `else` branch:
- `val root = requireView()`
- `return if (viewBindingRootId != 0) root.requireViewByIdCompat(viewBindingRootId) else root`

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import kotlin.reflect.KProperty
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertNotNull

@RunWith(RobolectricTestRunner::class)
Expand Down Expand Up @@ -55,6 +58,37 @@ class ActivityViewBindingPropertyTest {
val activity = controller.get()
assertNotNull(activity.binding)

// Verify full lifecycle completes without crash
// onDestroy triggers clear() which nulls binding and unregisters callbacks
controller.pause().stop().destroy()
}

@Test
fun `clear resets binding and allows recreation`() {
val mockProperty = mockk<KProperty<*>>(relaxed = true)
var callCount = 0
val delegate =
ActivityViewBindingProperty<Activity, ViewBinding> { activity ->
callCount++
mockk<ViewBinding> {
every { root } returns View(activity)
}
}

val controller =
Robolectric
.buildActivity(TestActivity::class.java)
.create()
.start()
.resume()
val activity = controller.get()

val binding1 = delegate.getValue(activity, mockProperty)
assertEquals(1, callCount)

delegate.clear()
val binding2 = delegate.getValue(activity, mockProperty)
assertEquals(2, callCount)
assertNotEquals(binding1, binding2, "After clear(), a new binding should be created")
}
}