Espresso offers mechanisms to scroll to or act on a particular item for two
types of lists: adapter views and recycler views.

When dealing with lists, especially those created with a `RecyclerView` or an
`AdapterView` object, the view that you're interested in might not even be on
the screen because only a small number of children are displayed and are
recycled as you scroll. The `scrollTo()` method can't be used in this case
because it requires an existing view.

## Interact with adapter view list items

Instead of using the `onView()` method, start your search with `onData()` and
provide a matcher against the data that is backing the view you'd like to match.
Espresso will do all the work of finding the row in the `Adapter` object and
making the item visible in the viewport.

### Match data using a custom view matcher

The activity below contains a `ListView`, which is backed by a `SimpleAdapter`
that holds data for each row in a `Map<String, Object>` object.

![The list activity currently shown on the screen contains a list with
23 items. Each item has a number, stored as a String, mapped to a
different number, which is stored as an Object instead.](https://developer.android.com/static/images/training/testing/list-showing-all-rows.png)

Each map has two entries: a key `"STR"` that contains a String, such as
`"item: x"`, and a key `"LEN"` that contains an `Integer`, which represents the
length of the content. For example:  

```
{"STR" : "item: 0", "LEN": 7}
```

The code for a click on the row with "item: 50" looks like this:  

### Kotlin

```kotlin
onData(allOf(`is`(instanceOf(Map::class.java)), hasEntry(equalTo("STR"),
        `is`("item: 50")))).perform(click())
```

### Java

```java
onData(allOf(is(instanceOf(Map.class)), hasEntry(equalTo("STR"), is("item: 50"))))
    .perform(click());
```

Note that Espresso scrolls through the list automatically as needed.

Let's take apart the `Matcher<Object>` inside `onData()`. The
`is(instanceOf(Map.class))` method narrows the search to any item of the
`AdapterView`, which is backed by a `Map` object.

In our case, this aspect of the query matches every row of the list view, but we
want to click specifically on an item, so we narrow the search further with:  

### Kotlin

```kotlin
hasEntry(equalTo("STR"), `is`("item: 50"))
```

### Java

```java
hasEntry(equalTo("STR"), is("item: 50"))
```

This `Matcher<String, Object>` will match any Map that contains an entry with
the key `"STR"` and the value `"item: 50"`. Because the code to look up this is
long and we want to reuse it in other locations, let's write a custom
`withItemContent` matcher for that:  

### Kotlin

```kotlin
return object : BoundedMatcher<Object, Map>(Map::class.java) {
    override fun matchesSafely(map: Map): Boolean {
        return hasEntry(equalTo("STR"), itemTextMatcher).matches(map)
    }

    override fun describeTo(description: Description) {
        description.appendText("with item content: ")
        itemTextMatcher.describeTo(description)
    }
}
```

### Java

```java
return new BoundedMatcher<Object, Map>(Map.class) {
    @Override
    public boolean matchesSafely(Map map) {
        return hasEntry(equalTo("STR"), itemTextMatcher).matches(map);
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("with item content: ");
        itemTextMatcher.describeTo(description);
    }
};
```

You use a `BoundedMatcher` as a base because to only match objects of type
`Map`. Override the `matchesSafely()` method, putting in the matcher found
earlier, and match it against a `Matcher<String>` that you can pass as an
argument. This allows you to call `withItemContent(equalTo("foo"))`. For code
brevity, you can create another matcher that already calls the `equalTo()` and
accepts a `String` object:  

### Kotlin

```kotlin
fun withItemContent(expectedText: String): Matcher<Object> {
    checkNotNull(expectedText)
    return withItemContent(equalTo(expectedText))
}
```

### Java

```java
public static Matcher<Object> withItemContent(String expectedText) {
    checkNotNull(expectedText);
    return withItemContent(equalTo(expectedText));
}
```

Now the code to click on the item is simple:  

### Kotlin

```kotlin
onData(withItemContent("item: 50")).perform(click())
```

### Java

```java
onData(withItemContent("item: 50")).perform(click());
```

For the full code of this test, take a look at the `testClickOnItem50()` method
within the
[`AdapterViewTest`](https://github.com/android/android-test/blob/7e834ce37faf52f2a65a73b0a6d83ab148707cbb/testapps/ui_testapp/javatests/androidx/test/ui/app/AdapterViewTest.java)
class and
[this custom `LongListMatchers`](https://github.com/android/android-test/blob/461ca02299807020c0b317b1805eb709d3c25147/testapps/ui_testapp/javatests/androidx/test/ui/app/LongListMatchers.java)
matcher on GitHub.

### Match a specific child view

The sample above issues a click in the middle of the entire row of a `ListView`.
But what if we want to operate on a specific child of the row? For example, we
would like to click on the second column of the row of the `LongListActivity`,
which displays the String.length of the content in the first column:

![In this example, it would be beneficial to extract just the length of
a particular piece of content. This process involves determining the
value of the second column in a row.](https://developer.android.com/static/images/training/testing/list-highlighting-one-col.png)

Just add an `onChildView()` specification to your implementation of
`DataInteraction`:  

### Kotlin

```kotlin
onData(withItemContent("item: 60"))
    .onChildView(withId(R.id.item_size))
    .perform(click())
```

### Java

```java
onData(withItemContent("item: 60"))
    .onChildView(withId(R.id.item_size))
    .perform(click());
```
| **Note:** This sample uses the `withItemContent()` matcher from the sample above. Take a look at the `testClickOnSpecificChildOfRow60()` method in the [`AdapterViewTest`](https://github.com/android/android-test/blob/7e834ce37faf52f2a65a73b0a6d83ab148707cbb/testapps/ui_testapp/javatests/androidx/test/ui/app/AdapterViewTest.java) class on GitHub.

## Interact with recycler view list items

`RecyclerView` objects work differently than `AdapterView` objects, so
`onData()` cannot be used to interact with them.

To interact with RecyclerViews using Espresso, you can use the
`espresso-contrib` package, which has a collection of
[`RecyclerViewActions`](https://developer.android.com/reference/androidx/test/espresso/contrib/RecyclerViewActions)
that can be used to scroll to positions or to perform actions on items:

- `scrollTo()` - Scrolls to the matched View, if it exists.
- `scrollToHolder()` - Scrolls to the matched View Holder, if it exists.
- `scrollToPosition()` - Scrolls to a specific position.
- `actionOnHolderItem()` - Performs a View Action on a matched View Holder.
- `actionOnItem()` - Performs a View Action on a matched View.
- `actionOnItemAtPosition()` - Performs a ViewAction on a view at a specific position.

The following snippets feature some examples from the
[RecyclerViewSample](https://github.com/android/testing-samples/tree/main/ui/espresso/RecyclerViewSample)
sample:  

### Kotlin

```kotlin
@Test(expected = PerformException::class)
fun itemWithText_doesNotExist() {
    // Attempt to scroll to an item that contains the special text.
    onView(ViewMatchers.withId(R.id.recyclerView))
        .perform(
            // scrollTo will fail the test if no item matches.
            RecyclerViewActions.scrollTo(
                hasDescendant(withText("not in the list"))
            )
        )
}
```

### Java

```java
@Test(expected = PerformException.class)
public void itemWithText_doesNotExist() {
    // Attempt to scroll to an item that contains the special text.
    onView(ViewMatchers.withId(R.id.recyclerView))
            // scrollTo will fail the test if no item matches.
            .perform(RecyclerViewActions.scrollTo(
                    hasDescendant(withText("not in the list"))
            ));
}
```  

### Kotlin

```kotlin
@Test fun scrollToItemBelowFold_checkItsText() {
    // First, scroll to the position that needs to be matched and click on it.
    onView(ViewMatchers.withId(R.id.recyclerView))
        .perform(
            RecyclerViewActions.actionOnItemAtPosition(
                ITEM_BELOW_THE_FOLD,
                click()
            )
        )

    // Match the text in an item below the fold and check that it's displayed.
    val itemElementText = "${activityRule.activity.resources
        .getString(R.string.item_element_text)} ${ITEM_BELOW_THE_FOLD.toString()}"
    onView(withText(itemElementText)).check(matches(isDisplayed()))
}
```

### Java

```java
@Test
public void scrollToItemBelowFold_checkItsText() {
    // First, scroll to the position that needs to be matched and click on it.
    onView(ViewMatchers.withId(R.id.recyclerView))
            .perform(RecyclerViewActions.actionOnItemAtPosition(ITEM_BELOW_THE_FOLD,
            click()));

    // Match the text in an item below the fold and check that it's displayed.
    String itemElementText = activityRule.getActivity().getResources()
            .getString(R.string.item_element_text)
            + String.valueOf(ITEM_BELOW_THE_FOLD);
    onView(withText(itemElementText)).check(matches(isDisplayed()));
}
```  

### Kotlin

```kotlin
@Test fun itemInMiddleOfList_hasSpecialText() {
    // First, scroll to the view holder using the isInTheMiddle() matcher.
    onView(ViewMatchers.withId(R.id.recyclerView))
        .perform(RecyclerViewActions.scrollToHolder(isInTheMiddle()))

    // Check that the item has the special text.
    val middleElementText = activityRule.activity.resources
            .getString(R.string.middle)
    onView(withText(middleElementText)).check(matches(isDisplayed()))
}
```

### Java

```java
@Test
public void itemInMiddleOfList_hasSpecialText() {
    // First, scroll to the view holder using the isInTheMiddle() matcher.
    onView(ViewMatchers.withId(R.id.recyclerView))
            .perform(RecyclerViewActions.scrollToHolder(isInTheMiddle()));

    // Check that the item has the special text.
    String middleElementText =
            activityRule.getActivity().getResources()
            .getString(R.string.middle);
    onView(withText(middleElementText)).check(matches(isDisplayed()));
}
```

## Additional resources

For more information about using Espresso lists in Android tests, consult the
following resources.

### Samples

- [DataAdapterSample](https://github.com/android/testing-samples/tree/main/ui/espresso/DataAdapterSample): Showcases the `onData()` entry point for Espresso, for lists and `AdapterView` objects.