Issue
While I can scroll to a specific index of my RecyclerView I cannot scroll to an item that satisfies a given Matcher that relies upon the values of data bound values.
If I hard code the layouts of the items inserted into the RecyclerView with values then the Matcher finds them but if these values are filled through data binding the values the Matcher is comparing are all "empty". This happens even though the pending bindings have all been completed and no amount of waiting (or sleeping) or anything will make them available.
It is also interesting to note that Espresso's "withText" Matcher can 'see' the values when run as a simple assertion but when used as a Matcher in the "scrollTo(...)" method it fails to find them.
It's worth noting that the "scrollTo(...)" method does cause my adapter's "onCreateViewHolder(...)" to be run again but the adapter's "onBindViewHolder(...)" is also run before the checks start and inserting IdlingResource blockers in these methods does not help.
Any idea what is happening here?
Edit: I have made a simple stand alone project that illustrates this issue in an attempt to isolate the problem. Adding specific code as an example
The "text_row_item"s Layout file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="text"
type="String"
/>
</data>
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{text}"
/>
</layout>
My Adapter looks like this:
class CustomAdapter(private val dataSet: List<String>) :
RecyclerView.Adapter<CustomAdapter.CustomViewHolder>() {
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): CustomViewHolder {
val binding: ViewDataBinding = DataBindingUtil.inflate(
LayoutInflater.from(viewGroup.context),
R.layout.text_row_item,
viewGroup,
false)
binding.executePendingBindings()
return CustomViewHolder(binding)
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
holder.bind(dataSet[position])
}
inner class CustomViewHolder(private val binding: ViewDataBinding)
: RecyclerView.ViewHolder(binding.root) {
fun bind(item: String) {
binding.setVariable(BR.text, item)
}
}
override fun getItemCount() = dataSet.size
}
MainActivity.kt:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = CustomAdapter(listOf(
"W00t",
"W01t",
.
.
.
"W41t",
"W42t"
))
}
}
And my actual Espresso test file itself contains:
@Test
fun checkW00_42t() {
withText("W00t").assertAny(isDisplayed())
checkText("W00t")
checkText("W01t")
.
.
.
checkText("W41t")
checkText("W42t")
}
private fun checkText(text: String) {
Espresso.onView(withId(R.id.recycler_view))
.perform(RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText(text))))
withText(text).assertAny(isDisplayed())
And this is what this app looks like at the time the test is run:
There are 43 "W00t"s added to the RecyclerView so that it will need to be scrolled to see the last ones however it fails to find even the first one that doesn't require scrolling at all.
It's worth noting that the first check (which doesn't use "scrollTo(...)") passes:
withText("W00t").assertAny(isDisplayed())
However attempting to scroll to the exact same text fails:
checkText("W00t")
It is also worth noting that if instead of using DataBinding one were to just assign the text in onBindViewHolder, as in:
viewHolder.textView.text = dataSet[position]
the scrollTo(...)
works.
Solution
Oh my goodness, I think I have it!
I had to move executePendingBindings()
from onCreateViewHolder(...)
to CustomViewHolder()
:
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): CustomViewHolder {
val binding: ViewDataBinding = DataBindingUtil.inflate(...)
// binding.executePendingBindings() // ** remove here **
return CustomViewHolder(binding)
}
inner class CustomViewHolder(private val binding: ViewDataBinding)
: RecyclerView.ViewHolder(binding.root) {
fun bind(item: String) {
binding.setVariable(BR.text, item)
binding.executePendingBindings() // ** insert here **
}
}
Answered By - Slartibartfast
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.