Espresso "scrollTo(Matcher<...>)" 无法处理数据绑定项
Espresso "scrollTo(Matcher<...>)" not working with data bound items
虽然我可以滚动到我的 RecyclerView 的特定索引,但我无法滚动到满足依赖于数据绑定值的给定匹配器的项目。
如果我使用值对插入到 RecyclerView 中的项目的布局进行硬编码,则 Matcher 会找到它们,但如果这些值是通过数据绑定填充的,则 Matcher 正在比较的值都是“空的”。即使挂起的绑定已全部完成并且没有任何等待(或休眠)或任何东西可以使它们可用,也会发生这种情况。
同样有趣的是,Espresso 的“withText”匹配器可以 'see' 值 运行 作为一个简单的断言,但当在“scrollTo(...) " 找不到它们的方法。
值得注意的是,“scrollTo(...)”方法确实会导致我的适配器的“onCreateViewHolder(...)”再次 运行 但适配器的“onBindViewHolder(...)”在检查开始之前也是 运行 并且在这些方法中插入 IdlingResource 阻止程序没有帮助。
知道这里发生了什么吗?
编辑:我做了一个简单的独立项目来说明这个问题,试图找出问题所在。
添加具体代码作为例子
“text_row_item”的布局文件如下所示:
<?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>
我的适配器是这样的:
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"
))
}
}
我实际的 Espresso 测试文件本身包含:
@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())
这就是这个应用程序在测试时的样子 运行:
RecyclerView 添加了 43 个“W00t”,因此需要滚动它才能看到最后一个,但它甚至找不到第一个根本不需要滚动的。
值得注意的是第一次检查(不使用“scrollTo(...)”)通过了:
withText("W00t").assertAny(isDisplayed())
但是尝试滚动到完全相同的文本失败:
checkText("W00t")
还值得注意的是,如果不使用 DataBinding 而只是在 onBindViewHolder 中分配文本,如:
viewHolder.textView.text = dataSet[position]
scrollTo(...)
有效。
我的天啊,我想我得到了!
我不得不将 executePendingBindings()
从 onCreateViewHolder(...)
移动到 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 **
}
}
虽然我可以滚动到我的 RecyclerView 的特定索引,但我无法滚动到满足依赖于数据绑定值的给定匹配器的项目。
如果我使用值对插入到 RecyclerView 中的项目的布局进行硬编码,则 Matcher 会找到它们,但如果这些值是通过数据绑定填充的,则 Matcher 正在比较的值都是“空的”。即使挂起的绑定已全部完成并且没有任何等待(或休眠)或任何东西可以使它们可用,也会发生这种情况。
同样有趣的是,Espresso 的“withText”匹配器可以 'see' 值 运行 作为一个简单的断言,但当在“scrollTo(...) " 找不到它们的方法。
值得注意的是,“scrollTo(...)”方法确实会导致我的适配器的“onCreateViewHolder(...)”再次 运行 但适配器的“onBindViewHolder(...)”在检查开始之前也是 运行 并且在这些方法中插入 IdlingResource 阻止程序没有帮助。
知道这里发生了什么吗?
编辑:我做了一个简单的独立项目来说明这个问题,试图找出问题所在。 添加具体代码作为例子
“text_row_item”的布局文件如下所示:
<?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>
我的适配器是这样的:
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"
))
}
}
我实际的 Espresso 测试文件本身包含:
@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())
这就是这个应用程序在测试时的样子 运行:
RecyclerView 添加了 43 个“W00t”,因此需要滚动它才能看到最后一个,但它甚至找不到第一个根本不需要滚动的。
值得注意的是第一次检查(不使用“scrollTo(...)”)通过了:
withText("W00t").assertAny(isDisplayed())
但是尝试滚动到完全相同的文本失败:
checkText("W00t")
还值得注意的是,如果不使用 DataBinding 而只是在 onBindViewHolder 中分配文本,如:
viewHolder.textView.text = dataSet[position]
scrollTo(...)
有效。
我的天啊,我想我得到了!
我不得不将 executePendingBindings()
从 onCreateViewHolder(...)
移动到 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 **
}
}