Android: how do I test for a RecyclerView list? - android

I have a UI screen that creates CardViews that are saved to a RecyclerView list. Initially, there is no RecyclerView list because there are no CardViews initially. When the first CardView is saved it creates a RecyclerView list to show the CardView data.
I want to be able to test if there is a RecyclerView list already created. If not, launch the CardView activity for user input. If there is a RecyclerView list because a CardView(s) was previously created, then launch the RecyclerView activity. Any ideas on how to test for the existence of the RecyclerView list that I can use with an if/then statement to launch the correct activity?

If you are using a custom recycler adapter which i think you are(Provide more code if not) , Override getItemCount() function like
public int getItemCount() {
if(list!=null)
return this.list.size();
else
return -1;
}
And check the list size and do the functionalities in an if else
Edit
Check this in the activity like
if(adapter.getItemCount()>0)
{
//Do what you want
}
else
{
//Launch the activity
}

Related

Adapter list item update issue

I'm working on a screen where I have to display twice the same collection of items. I'm using two RecyclerView's each with a different instance of the same Adapter
Besides the two RecyclerViews in the screen I have a continue button to go to the next screen.
I want to put some constraints on the button so that the user will not be able to proceed to the next screen if he doesn't select an item from each RecyclerView
The following method is called when the user taps on an item.
fun setSelection(item: Item) {
list.forEach {
it.isSelected = false
if (it.id == item.id) it.isSelected = true
}
notifyDataSetChanged()
}
And to verify if the list has at least one selected item I use this:
fun isAnySelected(): Boolean {
return list.any { item -> item.isSelected }
}
In the button's clickListener I added this verification:
if ((first_recycler.adapter as CustomAdapter).isAnySelected() &&
(second_recycler.adapter as CustomAdapter).isAnySelected())
{ //go to next screen }
else{
// select an item before continue }
My problem is that if I select an item only from one RecyclerView, when I tap on the continue button both of them will return true.
Why does (second_recycler.adapter as CustomAdapter).isAnySelected() returns true if only
(first_massage_display.mdl_recycler.adapter as MassageAdapter).setSelection(item)
Is called?
Sounds like the lists in each adapter are referencing the same list item instances. This could be because you passed the same list to both adapters, or simply that you have not created distinct list item instances in each list. If two lists reference the same list items, than changing a list item in one list also changes it in the other.
I recommend making Item a data class so you can easily make copies. Then create a copy of the list where every member of the list is also copied, and pass that to the second Adapter. You can use map() to do both steps of copying the list and the items within it all at once:
val secondList = firstList.map(Item::copy)

RecyclerView notifyItemRangeInserted not maintaining scroll position

I have a simple recyclerview with items (tips) and a loading spinner at the bottom.
here's how the item count and item view type methods look:
#Override
public int getItemViewType(int position) {
if (position == getItemCount() - 1) { // last position
return LOADING_FOOTER_VIEW_TYPE;
}
else {
return TIP_VIEW_TYPE;
}
}
#Override
public int getItemCount() {
return tips.size() + 1; // + 1 for the loading footer
}
basically, i just have a loading spinner under all my items.
I create the adapter once like so:
public TipsListAdapter(TipsActivity tipsActivity, ArrayList<Tip> tips) {
this.tipsActivity = tipsActivity;
this.tips = tips;
}
and then once i have fetched additional items, i call add like so:
public void addTips(List<Tip> tips) {
// hide the loading footer temporarily
isAdding = true;
notifyItemChanged(getItemCount() - 1);
// insert the new items
int insertPos = this.tips.size(); // this will basically give us the position of the loading spinner
this.tips.addAll(tips);
notifyItemRangeInserted(insertPos, tips.size());
// allow the loading footer to be shown again
isAdding = false;
notifyItemChanged(getItemCount() - 1);
}
What's odd here is that when i do that, the scroll position goes to the very bottom. It almost seems like it followed the loading spinner. This only happens on the first add (i.e. when there is only the loading spinner showing initally). subsequent adds maintains the proper scroll position (the position where the items were inserted).
This doesn't happen if i change notifyItemRangeInserted() to notifyItemRangeChanged() like so:
public void addTips(List<Tip> tips) {
// hide the loading footer temporarily
isAdding = true;
notifyItemChanged(getItemCount() - 1);
// insert the new items
int insertPos = this.tips.size(); // this will basically give us the position of the loading spinner
this.tips.addAll(tips);
notifyItemRangeChanged(insertPos, tips.size());
// allow the loading footer to be shown again
isAdding = false;
notifyItemChanged(getItemCount() - 1);
}
Nor does it happen if i simply call notifyDataSetChanged() like so:
public void addTips(List<Tip> tips) {
this.tips.addAll(tips);
notifyDataSetChanged();
}
Here's the code for setting the adapter in my Activity:
public void setAdapter(#NonNull ArrayList<Tip> tips) {
if (!tips.isEmpty()) { // won't be empty if restoring state
hideProgressBar();
}
tipsList.setAdapter(new TipsListAdapter(this, tips));
}
public void addTips(List<Tip> tips) {
hideProgressBar();
getAdapter().addTips(tips);
restorePageIfNecessary();
}
private TipsListAdapter getAdapter() {
return (TipsListAdapter) tipsList.getAdapter();
}
Note:
I don't manually set scroll position anywhere.
I call setAdapter() in onResume()
addTips() is called after I fetch items from the server
Let me know if you need any additional parts of my code.
This only happens on the first add (i.e. when there is only the loading spinner showing initally). subsequent adds maintains the proper scroll position (the position where the items were inserted).
RecyclerView has built-in behavior when calling the more-specific dataset change methods (like notifyItemRangeInserted() as opposed to notifyDataSetChanged()) that tries to keep the user looking at "the same thing" as before the operation.
When the data set changes, the first item the user can see is prioritized as the "anchor" to keep the user looking at approximately the same thing. If possible, the RecyclerView will try to keep this "anchor" view visible after the adapter update.
On the very first load, the first item (the only item) is the loading indicator. Therefore, when you load the new tips and update the adapter, this behavior will prioritize keeping the loading indicator on-screen. Since the loading indicator is kept at the end of the list, this will scroll the list to the bottom.
On subsequent loads, the first item is not the loading indicator, and it doesn't move. So the RecyclerView will not appear to scroll, since it doesn't have to do so to keep the "anchor" on-screen.
My recommendation is to check insertPos and see if it is zero. If it is, that means this is the first load, so you should update the adapter by calling notifyDataSetChanged() in order to avoid this anchoring behavior. Otherwise, call notifyItemRangeInserted() as you're currently doing.
Remove the setAdapter code from onResume ASAP as you are setting new TipsListAdapter(this, tips);
Every time a new reference of the adapter is created...make field mAdapter and then set it in onCreate . RecyclerView doesnt remember the scrolled position because everytime a new reference of adapter is being created.. onResume gets called infinitely when activity is in running state..
So either you setAdapter in onCreate using new operator to create reference for adapter or,
in onResume use mAdapter field variable reference..

Do not go back to top RecyclerView Firestore

I have a simple code with a RecyclerView, CardView, and an adapter. And I retrieve the data from the Firestore.
Problem:
I have a publishing feed, for example. And in this feed I have a button that takes another screen.
In this second screen I use as a return method: Finish.
But the problem is that the feed is large and coming back from the second screen to the first screen, RecyclerView goes to the beginning.
Is there any way I can not start the RecyclerView on top when I return to the activity that contains the feed?
Sample Image:
1 - First screen where it is clicked to see users who enjoyed a post. (This screen is a fragment.)
2 - List screen of users who liked the post.
3 - The first screen, but after you return. This screen always returns to the top.
Thanks!!
EDIT:
Code adapter + recyclerview in Fragment:
/* Recycler */
mCardFeedList = (RecyclerView) view.findViewById(id.cardFeedUser_list);
mCardFeedList.setHasFixedSize(true);
mCardFeedList.setItemViewCacheSize(20);
mCardFeedList.setDrawingCacheEnabled(true);
mAdapter = new PostsAdapter(mQueryNew, this){
#Override
protected void onDataChanged() {
if (getItemCount() == 0) {
mCardFeedList.setVisibility(View.GONE);
//mTxtVazio.setVisibility(View.VISIBLE);
} else {
mCardFeedList.setVisibility(View.VISIBLE);
//mTxtVazio.setVisibility(View.GONE);
}
}
};
mCardFeedList.setLayoutManager(new LinearLayoutManager(getActivity().getApplication()));
mCardFeedList.setAdapter(mAdapter);
To solve this, you need to override the following method from RecyclerView.Adapter:
onBindVIewHolder(viewholder, position)
Called by RecyclerView to display the data at the specified position.
So you can use it to bind the data you have to this view and set the correct viewstate on the view.

How to get recycler view list item count using Espresso

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

Android RecyclerView infinity scrolling: how to add more items to dataset

I have implemented my RecyclerView and even added an onscrolllistener to support infinity scrolling and now I'm stuck with a, hopefully, easy problem: How can I add the newly loaded data to the existing dataset?
My current approach: I create a new array with the length of the existing dataset + the length of the newly loaded data. I System.arraycopy my existing dataset and add the new content with a for-loop.
This works but the list is always reset (scrolls back to the top) and I assume my way to add additional content is overly complicated/wrong, though the tutorials I have looked at seem to pass over this "detail".
Update: I'm currently calling "scrollToPosition" on the UI-Thead after the data has been loaded, but I doubt this is the correct way of doing this or am I wrong?
You shouldn't be adding stuff to your dataset, you will sooner or later run out of memory. What you can do is return a big number (I used Short.MAX_VALUE) item in getItemCount inside your adapter and in the method that requests a view for postion you should do position % list.size();
It is not a truly endless RecyclerView this way, but good enough. I will paste some code tomorrow, I don't have it here now :/
I think you have to add items inside your adapter. Let`s say
class Adapter extends Recycler.Adapter<Recycler.ViewHolder>{
List<YourCustomObject> list;
public Adapter(){
list = new ArrayList<>();
}
public void addItem(YourCustomObject item){
list.add(item);
notifyItemDateSetChanged(); //This method for adapter to notice that list size have been changed
}
// Here your views
}
There is implementation of Your fragment or Activity where you retrieve data from internet.Let` say
class MainActivity extends AppCompactActivity{
Adapter adapter = new Adapter();
List<YourCustomObjects> objects;
public void onCreateView(){
//////// Something yours
}
public void onLoadMore(){
///// Your operation to retrieve data and init it to your list objects
for(YourCustomObject object : objects){
adapter.addItem(object);
}
}
}

Categories

Resources