I can use view actions to perform click on individual list items
onView(withId(R.id.rv_recycler_view))
.perform(actionOnItemAtPosition(0, click()));
However, I want to return the number of list items, and use a loop to click on each list item without writing a separate pice of code for each item.
How can I return the number of list items the recycler view contains? Maybe I need to access the adapter variable through the activity directly?
Thanks
I would stub out the dependency so that you can control your test environment, i.e. If you feed in 3 items, you should expect that three items are displayed. You can check this with the custom action by nenick How to count RecyclerView items with Espresso
public class CustomRecyclerViewAssertions {
//Asserts the number of items in a RecyclerView
public static ViewAssertion AssertItemCount(final int expectedCount) {
return new ViewAssertion() {
#Override
public void check(View view, NoMatchingViewException noViewFoundException) {
if (noViewFoundException != null) {
throw noViewFoundException;
}
RecyclerView recyclerView = (RecyclerView) view;
RecyclerView.Adapter adapter = recyclerView.getAdapter();
assertThat(adapter.getItemCount(), is(expectedCount));
}
};
}
}
which is called by using
onView(withId(R.id.recyclerView)).check(new RecyclerViewItemCountAssertion(3));
Once you know how many items are present, you can use:
onView(withId(R.id.recyclerView)).perform(
actionOnItemAtPosition<RecyclerView.ViewHolder>(1, click()))
As the recyclerView is showing multiple instances of the same view, I wouldn't waste time duplicating code to click each item
Related
I have a RecyclerView on which i add items from another RecyclerView, each item has a property called "TYPE" and it's value can be "FASE1", "FASE2" and up to "FASE8".
When a new item is added to that list or removed i need to sort it based on the TYPE values.
So all items has to be sorted like ITEM1 FASE1 > ITEM2 FASE2 ....
Till now i was just adding or removing items from the RecyclerView like the following:
Here is the code from RecyclerView Adapter of the RecyclerView which adds items to the other RecyclerView.
private void addOrRemove(int position, boolean add) {
// piattiItems is a reference to ArrayList<ItemPTERM> from the Adapter of the RecyclerView where i have to add the items
Item prodotto = mFilteredList.get(position); // getting item from current RecyclerView
ItemPTERM prodottoAggiunto = piattiAdapter.getItemByCode(prodotto.getCodice()); // cheking if there is yet the same item in the RecyclerView where i have to add it
if (add) {
piattiItems.add(nuovoProdotto(prodotto)); // adding new item
piattiAdapter.notifyItemInserted(size);
recyclerPiatti.scrollToPosition(size);
}else {
piattiItems.remove(prodottoAggiunto); // removing item
piattiAdapter.notifyItemRemoved(position);
}
}
The idea is:
RecyclerView should just be responsible for displaying your data, it shouldn't know the order of your List data.
Your List data should be responsible for the order.
So, after you add new item to your RecylerView, first update/resort your List, then call notifyDataSetChanged() on the Adapter object. Or just call sumitList() if you're using ListAdapter with DiffUtil, which will just update the changed items rather than updating the whole List like RecyclerView.Adapter.
It can be done by defining an order relation among the ItemPTERMs by implementing the Comparable<ItemPTERM> interface with ItemPTERM or by creating a Comparator that implements Comparator<ItemPTERM>.
In the latter, you should implement the compare method. I'm supposing that "TYPE" is an Enum to make things simpler; by the way if it's a String you should implement a specific algorithm for your goal.
When comparator is ready, you can add elements to piattiItems as always and call Collections.sort(piattiItems, comparator) to sort the ArrayList. Now you can get the index of the newly added item and use it to tell your RecyclerView the position of this item. RecyclerView will do the rest by showing the item at the correct position!
private Comparator<ItemPTERM> mComparator = new Comparator<>(){
#Override
public int compare(ItemPTERM o1, ItemPTERM o2) {
return o1.type.compareTo(o2.type); // supposing that type is enum
}
}
private void addOrRemove(int position, boolean add) {
Item prodotto = mFilteredList.get(position);
ItemPTERM prodottoAggiunto = piattiAdapter.getItemByCode(prodotto.getCodice());
if (add) {
ItemPTERM newItem = nuovoProdotto(prodotto);
piattiItems.add(newItem);
Collections.sort(piattiItems, mComparator); // sorts after adding new element
int index = piattiItems.indexOf(newItem); // gets the index of the newly added item
piattiAdapter.notifyItemInserted(index); // uses the index to tell the position to the RecyclerView
recyclerPiatti.scrollToPosition(index);
}else {
piattiItems.remove(prodottoAggiunto);
piattiAdapter.notifyItemRemoved(position);
}
}
how to store only selected item list data in recycler view from other activity list.
i used this code -- successfully select data but don't know how to add only selected data items in new activity recycler view -
i used this code snippet ite working fine-
//
https://en.proft.me/2018/03/3/multi-selection-recyclerview/
StringBuilder stringBuilder = new StringBuilder();
for (ExcercisesSelectedModel.DataBean data : getList()) {
if (selectedIds.contains(data.getId()))
stringBuilder.append("\n").append(data.getName());
Change
public interface OnClickAction {
public void onClickAction();
}
to
public interface OnClickAction {
public void onClickAction(Item item);
}
In adapter class, on item click
receiver.onClickAction(item);
In Activity class
private List<Item> selectedItem = new ArrayList()
public void onClickAction(Item item) {
selectedItem.add(item);
}
Now use this selected item list in new activity.
Edit: Don't forget to use or rename the interface to Callback, something along the lines of ActivityCallback or OnClickCallback.
This is for the sake of naming conventions.
A simple flag isSelected in parent list will save your day.
So whenever user select/unselect the item just change the value of isSelected flag to true or false.
Now you have a final list from which you can easily identify selected items, simple store in separated list named selectedItemList.
At last use the selectedItemList and fill your Recycleview. Hope it make sense.
I have some weird issue with RecyclerView since I changed my app to load the data from Room database.
I have a screen (a Fragment) which displays a Profile for a user. Basically it's a GridLayout with the first item (row) displaying some info about the user and then for each row I'm displaying three images.
The code for the RecyclerView is :
private void initializeRecyclerView() {
if (listAdapter == null) {
listAdapter = new PhotosListAdapter(getActivity(), userProfileAccountId, false);
}
rvPhotosList.setNestedScrollingEnabled(true);
rvPhotosList.getItemAnimator().setChangeDuration(0);
GridLayoutManager layoutManager = new GridLayoutManager(getContext(), ProfileFragment.GRID_COLUMNS,
LinearLayoutManager.VERTICAL, false);
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
// 3 spans for Header, 1 for photos
return listAdapter.isHeader(position) ? 3 : 1;
}
});
rvPhotosList.setLayoutManager(layoutManager);
int spacingInPixels = 1dp;
rvPhotosList.addItemDecoration(new PaddingItemDecoration(GRID_COLUMNS, spacingInPixels, true));
rvPhotosList.setAdapter(listAdapter);
}
and this is how I load data :
compositeDisposable.add(
repository.getAccount(accountId)
.doOnNext(account -> {
if (account != null && view != null) {
view.showCover(account.getCover());
view.showAccount(account);
}
})
.flatMap(s -> repository.getUserPosts(accountId))
.subscribe(posts -> {
if (view != null) {
view.showPosts(posts);
}
}, throwable -> {}));
Both calls return a Flowable, either a Flowable<Account> or a Flowable<Post> where Account and Post are Room's Entity classes.
The two methods showAccount() and showPosts() pass the previously mentioned entity classes to the adapter.
The problem is this : the moment the data are loaded it scrolls to the bottom of the screen (which is also the bottom of the RecyclerView).
I'm not sure why this happens. Is it because of Room's Flowable type?Cause previously when I was fetching the data from network and passing them to the adapter I didn't have this problem.
Edit : I'm using version 25.3.1 for RecyclerView
Edit 2 : This is how I'm updating my adapter class with Account and Post data :
public void addPhotos(List<Post> posts) {
dataset.addAll(posts);
notifyDataSetChanged();
}
public void addProfileItem(Account account) {
this.account = account;
notifyItemInserted(0);
}
Did you try this? Setting descendantFocusability property of parent RecyclerView to blocksDescendants.
What this will do is, it will no more focus on your dynamically loaded child views inside recycler view, as a result, the automatic scroll will not take place.
android:descendantFocusability="blocksDescendants"
Note: The property descendantFocusability can be used with other views as well like FrameLayout, RelativeLayout, etc. So if you set this property to your parent/root layout, it will no more scroll to bottom.
If you have some initial items, that should be at the bottom after new items loaded, then you need to give them new ids, if you don't want to recycler scrolls to the bottom.
It happens because the recycler remember that this items was visible to the user before update and after update he restore his state so that initial items at the bottom will be displayed
In my case this line was culprit,
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, SPAN_COUNT, GridLayoutManager.VERTICAL, true);
Specifically , last parameter which is setting reverseLayout attribute to true. Make it false and RecyclerView doesn't scroll to bottom of screen.
I have a requirement, where I should download the ad item while scrolling and update the list. Since calling notifyDatasetChnaged(), resets everything, I'm calling notifyItemInserted(position). But, calling this duplicated the items in the list. I found that there are no repeated items in the list. But after calling notifyItemInserted, it duplicates the item. I'm not getting how to resolve this issue. This what I'm doing:
mNewsList.add(mPreviousAdPosition, newsItem);
mAdapter.notifyItemInserted(mPreviousAdPosition);
If I call, it works properly, there are no repeated items. But I don't want my list items to recreate. What can be the issue ?
I had the same problem for exactly the same use case, the solution is:
Implement this method in your Adapter :
#Override
public long getItemId(int position) {
//Return the stable ID for the item at position
return items.get(position).getId();
}
Call this method in the Constructor of your Adapter :
//Indicates whether each item in the data set can be represented with a unique identifier
setHasStableIds(true);
You can add the object at the end of the array with each object having a position along with it where it needs to be shown in the recycler view. Sort this array on the basis of position before calling notifyItemInserted(position). In this way only required data will be drawn.I have recenlty followed this approach and works very well with dynamic sections added in between in recycler view.
You should add the item at the end of the list.
mNewsList.add(newsItem);
and then notify like this.
mAdapter.notifyItemInserted(mNewsList.size()-1);
Create a temporary list and add items as mentioned below:
List<YourModel> mTmpList = new ArrayList<YourMdel>();
//add items (from 0 -> mPreviousAdPosition) to mTmpList;
for(int i=0; i<mPreviousAdPosition; i++) {
mTmpList.add(mNewsList.get(i));
}
//add item at mPreviousAdPosition
mTmpList.add(newsItem);
//add remaining items and set i<=mNewsList.size() because we ha
for(int i=mPreviousAdPosition; i<=mNewsList.size(); i++) {
mTmpList.add(mNewsList.get(i - 1)); //because we have added item at mPreviousAdPosition;
}
mNewsList = mTmpList;
mAdapter.notifyDataSetChanged();
You code should be written like this:
public class RecyclerViewAdapter extends RecyclerView.Adapter{
...
public void addData(int position, Item newsItem) {
mNewsList.add(position, newsItem);
notifyItemInserted(position);
}
...
}
and then you need to call the fun addData
I'm working on a note taking app. I add a note, and it get's added to the bottom of the list. As the last assertion in the espresso test, I want to make sure that the ListView displays a listItem that has just been added. This would mean grabbing the last item in the listView. I guess you might be able to do it in other ways? (e.g. get the size of adapted data, and go to THAT position? maybe?), but the last position of the list seems easy, but I haven't been able to do it. Any ideas?
I've tried this solution, but Espresso seems to hang. http://www.gilvegliach.it/?id=1
1. Find the number of elements in listView's adapter and save it in some variable. We assume the adapter has been fully loaded till now.:
final int[] numberOfAdapterItems = new int[1];
onView(withId(R.id.some_list_view)).check(matches(new TypeSafeMatcher<View>() {
#Override
public boolean matchesSafely(View view) {
ListView listView = (ListView) view;
//here we assume the adapter has been fully loaded already
numberOfAdapterItems[0] = listView.getAdapter().getCount();
return true;
}
#Override
public void describeTo(Description description) {
}
}));
2. Then, knowing the total number of elements in listView's adapter you can scroll to the last element:
onData(anything()).inAdapterView(withId(R.id.some_list_view)).getPosition(numberOfAdapterItems[0] - 1).perform(scrollTo())