Is there a way to scroll to the end of recyclerview using Espresso?
There is an item with text let's say 'Text XYZ' and the recyclerview has an id recycler_view. This item happens to be the last item of the recycler view.
I tried
onView(withId(R.id.recycler_view)).check(matches(isDisplayed())).perform(RecyclerViewActions.scrollTo(withText("Text XYZ")),click());
But this doesn't seem to work. Any ideas?
RecyclerViewActions.scrollTo() matches against the ItemView of the ViewHolder, which is inflated in onCreateViewHolder() of the adapter. And in order for the scrollTo() to work, you need to provide a matcher that uniquely identifies that ItemView.
Your current matcher tells espresso to scroll to a ViewHolder that was inflated with a TextView as an itemView. Which can happen, but usually you have some ViewGroup action going on there to style the view holders in the way you want them to look.
If you change your scrollTo() Matcher, to hasDescendant(withText("Text XYZ")) to account for all nested layouts (if there more than one).
Also keep in mind since you are also trying to click the item - you can't do it in the same perform() because it will send the click to the current ViewInteraction, which is in this case a RecyclerView with id R.id.recycler_view. Doing so in the same perform just clicks the middle on the RecyclerView, not the item that you scrolled to.
To solve this you need either need another onView() with the matcher that you used to scroll to an item or use RecyclerView.actionOnItem().
In case of another onView() statement, the hasDescendant(withText("Text XYZ")) will fail you, because it will find all parents of that TextView (viewholder, recyclerview, the viewgroup that holds the recyclerview and so on) as they all have this particular descendant. This will force you to make the ItemView matcher to be more precise and account for all nested layouts. My usual go to matcher is withChild() in these situations, but it might be different for you.
If you know the last position of the RecyclerView, then you can scroll to it
static final int LAST_POSITION = 100;
// First method
onView(withId(R.id.recyclerview))
.perform(RecyclerViewActions.scrollToPosition((LAST_POSITION)));
// Second method
onView(withId(R.id.recyclerview))
.perform(actionOnItemAtPosition(LAST_POSITION, scrollTo()));
Even if this approach doesn't show the scrolling up but allows you to select the position of the recycler.
Thanks to this guy (Espresso-testing-recyclerviews I used this approach:
onView(allOf(withId(R.id.recyclerUserCustomListsView)))
.perform(RecyclerViewActions.actionOnItemAtPosition(7, click()));
Related
I would like to know what is the right way to implement a footer in RecyclerView which can be used to display different views. Few examples are as below
View for network error with a button to reload.
View when data is loading from server with a progress bar.
View when there is no more data available.
And so on.
I came across https://stackoverflow.com/a/29168617/371557 which mentions that I have to Override getItemViewType and inflate different views in onCreateViewHolder
What I do not understand is how will RecyclerView know which ViewType it has to request for? How can I force the RecyclerView to show the ViewType?
RecyclerView is the new view more powerfull than listview, you can make a footer in this but it's for me not the right way.
I recommend to create a layout include your RecyclerView and add a dedicated view like Snackbar or a custom footer.
For your question, the RecyclerView knows what to display with the getItemViewType(int positionItem) method.
For example, if the item in position 6 must be shown in the list, getItemViewType return the id of view in onCreateViewHolder(ViewGroup viewGroup, int id) method and recycle the view which could already be used in list for saving memory.
(Sorry for my poor English :)
There are some topics about RecyclerView inside RecyclerView but I see most of them do not fit my case. My case is I have a RecyclerView (verticle linear layout management) displays a list of CardView, each Cardview contains a inner RecyclerView (horizontal linear layout management). The problem is all about performance when scrolling, it is not smooth at all. I notice if I comment the setAdapter for the Inner Recyclerview, the scrooling become smooth, but I make the CardView not updated the new list. The code is something similar to this:
onBindViewHolder...{
holder.innerRecycler.setAdapter(new InnerAdapter((data));
// comment that line make the outer recyclerview smoothly but the CardView data not updated thanks to the view recycling.
}
I know a scrollable view inside a scrollable view is not a good idea but I dont see any other choices. Anyone face to this kind of layout before?. Thanks.
UPDATE (add more code).
// init outer recyclerview
mOuterRecyclerView = (RecyclerView) findViewById(...);
mOuterRecyclerView.setLayoutManagement(new LinearLayoutManagement(this));
mOuterRecyclerView.setHasFixedSize(true);
mOuterRecyclerView.setAdapter(new OuterAdapter(dataList));
// The adapter class for the outer one
onBindViewHolder(...){
final dataItem = mItems.get(position);
holder.innerRecycler.setAdapter(new InnerAdapter(dataItem.getList()));
}
// the holder for the outer
class MyHolder extends ViewHolder{
RecyclerView innerRecycler;
public MyHolder(View view){
super(..);
innerRecycler = findViewById(...);
}
}
// the adapter for the inner
onBindViewHolder(...){
final dataItem = mItems.get(pos);
// async loading
holder.tvTitle.setText(dataItem.getTitle);
}
The layout is pretty simple so I dont post the fully code here. :)
#Paul i had the same requirement and the below trick worked.
Put the parent recyclerview inside NestedScrollView and in your onCreate method,
set.
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setNestedScrollingEnabled(false);
This prevents scroll of parent recyclerview and scroll becomes absolutely smooth.
There is no issue in such an approach as long as they scroll along different axes.You could enable RecyclerView.startNestedScroll(int) and also handle situations like overscroll.This delay would be because you are reinitiating an adpater everytime. You could try something different like maintaining a map of adpaters and calling RecyclerView.swapAdapter(args...) in bindVH.
Another good step could also be using a common pool for recycled views using RecyclerView.setRecycledViewPool(args...) .
I have created and used a list of 100+ plus items with a nested (different axis) recycler and have not faced issues .
If you would provide more code( where you have written async loading) I could help you more.But I suggest you go through the API and the design patterns and that should solve your issue.
Since there are some user ask me how to archive, I'm going to post what I do.
The sugession that #Droidekas seems a good point but I dont follow that.
What I did is:
Each horizontal recycler view is a item of a vertical recycler view
When I need to set the data for the particular vertical item (onBindViewHolder), I use the horizontal recycler adapter setNotifyDataSetChanged instead of setting a completely new adapter or swapping it. This way, I got the vertical recyler run very smoothly.
The vertical recyler adapter structure could be:
Vertical Item view holder -> Vertical Item[Name, List<Horizontal item>,...]
Vertical Item view holder -> Vertical Item[Name, List<Horizontal item>,...]
In OnBindViewHolder
holder.setData(VerticalItem)
In the Holder, I have a setData method looks like:
setData(VerticalItem item){
mItems = item.getHorizontalItems();
mHorizontalAdapter.notifyDataSetChanged();
}
Hope it helps. :D
I have a ViewPager whith items containing only a picture and a button.
I can't successfully interact with the UI of an item (Page) because, except the displayed picture, there is nothing to differentiate (from UI point of view) all items of the ViewPager.
I tried to select only one item with a position:
onData(is(instanceOf(ItemClass.class)))
.atPosition(0)
.onChildView(withId(R.id.button))
.perform(click());
causing:
NoMatchingViewException: No views in hierarchy found matching: is assignable from class: class android.widget.AdapterView
How to access and test items of a ViewPager with Espresso ?
FirstVal, ViewPager is not an AdapterView, it directly extends from ViewGroup. So the method onData() cannot be used on a ViewPager.
Solution 1
As it's a ViewGroup, each items are direct children of its ViewPager.
So the process is to reference the first view child using a custom matcher (like this onefirstChildOf()) and playing with hasDescendant() and isDescendantOfA() to access the targeted view and perform an action on it.
onView(allOf(withId(R.id.button), isDescendantOfA(firstChildOf(withId(R.id.viewpager)))))
.perform(click());
Solution 2 (da best)
As the particularity of the ViewPager is to display each items (#1 solution's child views) that composed it, one by one (page-by-page style). So even if your items use the same layout with the same IDs, only one is displayed. So we can reference the targeted view by its Id and add the constraints isDisplayed(). It will match only one view, the one that is currently displayed.
onView(allOf(withId(R.id.button), isDisplayed())).perform(click());
as simple as that.
And if you want another item, you may perform a swipe() on your ViewPager to change the displayed item:
onView(withId(R.id.viewpager)).perform(swipeLeft());
Notes from the Android dev doc:
isDisplayed will select views that are partially displayed (eg:
the full height/width of the view is greater than the height/width of
the visible rectangle). If you wish to ensure the entire rectangle
this view draws is displayed to the user use isCompletelyDisplayed()
Thanks #TWiStErRob
Just like a ListView but instead it doesn't scroll. Its content is added programatically via an ArrayAdapter. Is there any view that can be used for this purpose?
PS: Not LinearLayout, since it doesn't get its content from an adapter (I want the observer pattern)
Edit: Let me explain a little bit more. Suppose you need a list of items, but the list itself is not scrollable, what is scrollable is the screen.
That being said, the list of items should show ALL items, not a limited amount based on a fixed height. The way to go is LinearLayout as it is both non-scrollable and shows all items within itself.
But there is a third requierement. You don't want to add(View) directly, but instead, you want something similar to an ArrayAdapter so that you have a control of the items and their position, so you can handle on item click events based on their position. As far as I know, this can't be done using a LinearLayout. So my question is, does any view exist for this purpose?
You could try using a ListView, but disable scrolling, as described here
Put your layout inside a ScrollView and yes you have to use a Linearlayout. You need to set tag for each of your view that you add dynamically and whenever your view is clicked you can get it's position by view.getTag() and perform the required operation.
Note : Adding views at run time may affect performance.
What I want to do is to scroll view after listening a button.
Simply user taps button and view is scrolled to specific id below.
I tried to use scrollto and scrollby passing as an argument reference to object to which I want to scroll with no effect.
Anyone solved this problem yet?
I have spent many hours on this.
Try something like this. It makes sure that the item selected it the one in view:
ListView listView;
int positionSelected = listView.getSelectedItemPosition();
listView.setSelection(positionSelected);