How to get specific position view in RecyclerView? - android

I have a recyclerView. One of recyclerView items has an onClick method. In this method, I setVisibility for one Item to Visible(default is gone), also get the position and add it to an arrayList.
onClick :
public void onClickImageView(ImageView imageView, int position) {
imageView.setVisibility(View.VISIBLE);
positionArrayList.add(position);
}
Before i go to another activity, I want to the ImageView visibility be reset to default (gone) so I use an interface to send positionArrayList to the recyclerView activity to get views by position and reset ImageViews to default.
Interface :
public interface RecyclerViewMainActivityImp {
void resetImageViewToDefault(ArrayList<Integer> positionArrayList);
}
Where I use interface method in adapter :
public void OnClickNextActivity() {
Intent intent = new Intent();
intent.setClass(context, ClassActivity.class);
context.startActivity(intent);
recyclerViewInterface.resetImageViewToDefault(positionArrayList);
}
Activity :
#Override
public void resetImageViewToDefault(ArrayList<Integer> positionArrayList) {
for (int position = 0; position < positionArrayList.size(); position++) {
View row = binding.recyclerView.getLayoutManager().findViewByPosition(positionArrayList.get(position));
ImageView imageView = row.findViewById(R.id.imageView);
imageView.setVisibility(View.GONE);
}
}
When i scroll the recyclerView, and if some items are not in view anymore their views going to null so some positions in positionArrayList no longer have their view.
My problem is some views are null after scrolling and I don't know how to get access to them.
I searched on google and found some solutions but I could not solve my problem
Note : I have used data binding

there's workaround you can try for this,
create a function in your activity
function void updateRecyclerView(position){
yourArrayListForVisibility.set(position,'hide');
mAdapter.notifyDataSetChanged();
}
#Override
public void onResume(){
super.onResume();
mAdapter.notifyDataSetChanged();
}
call this function in your adapter before intent and in your adapter check for this arraylist value to hide or show content, always set default value to 'yourArrayListForVisibility' for every position

Any Activity that restarts has its onResume() method executed first.
So, call notifyDataSetChanged() of RcyclerView from onResume()
#Override
public void onResume(){
super.onResume();
mAdapter.notifyDataSetChanged();
}

Related

Update TextView in Fragment when RecyclerView's data changes

In my Fragment, I have 2 views. A Textview and a RecyclerView. The Textview essentially displays the current size of the RecyclerView. So, when a row is removed in the adapter class, I need to update the TextView's value accordingly.
The row is removed successfully when removeBtn is clicked, but I need to update the TextView in the Fragment accordingly.
FRAGMENT
titleText.text = "SIZE (" + arrayStringList().size.toString() + ")"
//...
//...
//Set RecyclerView Adapter
mRecyclerView.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
val adapter = MRecyclerView(context!!, arrayStringList)
mRecyclerView.adapter = adapter
RECYCLERVIEW
holder.removeBtn{
mData.removeAt(position)
notifyItemRemoved(position)
}
Is there some sort of listener that I can put in my fragment to detect when the data changes? Or is there a way to send data from the recyclerview back to the fragment when removeBtn is clicked?
Create a interface in recyclerView class , and implement that interface in fragment . Once you click the remove button , call this interface . In Fragment class , update the text view with adapter.getItemCount.
In Adapter
interface ItemCallback{
void updateTextView();
}
This interface will be implemented in fragment class ,where you can update your textView with itemCount .
public class Fragment implements Adapter.ItemCallback{
#Override
public void updateTextView() {
tvTextView.setText(adapter.getItemCount()); // This will return the current item count of adapter
}
}
You can do this via call back. Make an interface and implement that on Fragment. And pass the reference of the interface to the adapter. When you trigger the event to delete item from recycler view call the interface method (That you implemented on Activity). And in that call back method, you need to set the value to your text view. To set value on TextView, you can call size() method and set the value whatever is returned from it. For example the List you are passing to the adapter is mData. When you get call back the call size method on the reference and set the value as mentioned below code.
titleText.setText(String.valueOf(mData.size())).
Hope this will help you.
NOTE: You need to call String.valueOf method because size method returns integer value so it needs to be cast.
"Or is there a way to send data from the recyclerview back to the fragment when removeBtn is clicked?" the answer is yes
One approach is to pass your adapter your own click listener (an interface), this way you can control the click from the fragment something like this,
A1) create an interface
public interface ItemTouchListener {
void onCardClick(View view, int position);
boolean onCardLongClick(View view, int position);
}
A2) implement this interface in your fragment
public class YourFragment extends Fragment implements ItemTouchListener
A3) implement the methods you created in your fragment
#Override
public void onCardClick(View view, int position) {
if (view.getId() == R.id.removeBtn){
//update your text
}else{
//other buttons
}
}
#Override
public boolean onCardLongClick(View view, int position) {
if (view.getId() == R.id.removeBtn){
//update your text
} else {
//other buttons
}
return true;
}
A4) create this interface in your adapter and set it to your button
private ItemTouchListener onItemTouchListener;
public YourAdapter(List<Object> list, ItemTouchListener onItemTouchListener) {
this.onItemTouchListener = onItemTouchListener;
this.list = list;
}
removeBtn = view.findViewById(R.id.removeBtn);
removeBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onItemTouchListener.onCardClick(v, getAdapterPosition());
}
});
removeBtn.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
onItemTouchListener.onCardLongClick(view, getAdapterPosition());
return true;
}
});
A5) and now when you create your adapter it will ask for the ItemTouchListener which you can give it by just passing 'this'
YourAdapter adapter = new YourAdapter(this);
A6) you may also want to give your adapter a method something like getCount which return the list size
public int getCount(){
list.size();
}
and then you can call this like myAdapter.getCount

How to keep data in recycler view when screen orientation from portrait to landscape?

I have a custom object array to show in recycler view and I'm using GridLayout manager for my recylerview and showing (2x4),(3x3) etc. squares.
When user click these squares it's color changed.
But when screen orientate, view is refreshing.
My question is how to keep this data (selected square's color ) when screen orientation ?
Create an interface:
public interface ClickCallback {
void onItemClicked(String id);
}
In your activity create a callback and pass it to your adapter like this:
ClickCallback callback = new ClickCallback() {
#Override
public void onItemClicked(String id) {
//your list item object must have a method to get color
color = yourDataList.get(id).getColor();
}
};
protected void onSaveInstanceState(Bundle bundle) {
super.onSaveInstanceState(bundle);
bundle.putInt("color", color);
}
YourAdapter adapter = new YourAdapter (yourDataList, callback)
In your adapter don't forget to set OnClickListener and call appropriate method, it could be like this:
public ListItemViewHolder(View itemView) {
super(itemView);
//your code
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
callback.onItemClicked(getAdapterPosition());
}
});
}
}
In OnCreate extract your color data:
public void onCreate(Bundle bundle) {
if (bundle != null) {
value = bundle.getInt("color");
}
}
You can store statuses of each square in a proper format and save them
on onSaveInstanceState and retrieve them on onCreate method and manipulate the adapter as for your requirement.
Read more...
Hope this helps!
"But when screen orientate, view is refreshing."
on screen rotation a new instance of the activity is created
You can have a ViewModel class which stores your arraylist of data including their current state. When activity is recreated via rotation this will be persisted and you will get the correct state.
Ref : https://medium.com/androiddevelopers/viewmodels-a-simple-example-ed5ac416317e

How do I get a list in an android RecyclerView to update after an activity that modifies what it was showing?

I've seen some RecyclerView examples that simply modify an item within the click listener. My problem is that I start an activity from a click on an item, and the activity can change or delete the clicked item from the list. So the view needs to be updated after the activity is finished.
This code mostly from another developer passes the serialized item and position of the item to the activity as extra data. The position was intended to use to update the view later.
However, I found these comments:
"Do not treat position as fixed; only use immediately and call holder.getAdapterPosition() to look it up later. RecyclerView will NOT call onBindViewHolder again when the position of the item changes in the data set unless the item itself is invalidated or the new position cannot be determined. For this reason, you should only use the position parameter while acquiring the related data item inside this method, and should NOT keep a copy of it. If you need the position of an item later (eg. in a click listener), use getAdapterPosition() which will have the updated adapter position."
In the Adapter:
#Override
public void onBindViewHolder (#NonNull ItemAdapter.MyViewHolder holder, final int position)
{
final Item item = items.get(position);
holder.itemTitle.setText(item.getTitle());
holder.lay_item.setOnClickListener (new View.OnClickListener () {
#Override
public void onClick(View v) {
Intent intent = new Intent(context, ItemDetailActivity.class);
intent.putExtra("Item", item);
intent.putExtra("position", position);
context.startActivity (intent);
// TODO is this the place to refresh the view?
//holder.getAdapterPosition();
}
});
}
and for the activity:
Item currentItem;
protected void onCreate (Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_item_detail);
currentItem = (Item)getIntent().getSerializableExtra("Item"));
position = getIntent().getExtras().getInt("position");
...
}
I realize that the object held by the view is serialized into the extra data, so currentObject in the activity is not the same object.
I don't know why they did it like that. If that is the norm, please tell me how the list view is updated for changes to the object in the activity. If that is not the norm, how should it be done?
Therein lies the basis of the question, stated in the title:
How do I get a list in an android RecyclerView to update after an activity that modifies what it was showing?
Within the Activity, there is this click listener for the "save" button to update the database:
btn_save.setOnClickListener (new View.OnClickListener () {
#Override
public void onClick(View v) {
// ... stuff that updates the item attributes from the view elements...
// Save it. It is this object that should then be in the list in the RecyclerView.
try
{
MyApp.getDatabase().updateItem(item);
// TODO: This might work if currentItem was actually the one in the list
//adapter.notifyDataSetChanged();
}
catch (PersistenceException ex)
{
// notify user of failure
Toast.makeText (EditItemActivity.this, getResources().getString(R.string.item_update_fail), Toast.LENGTH_LONG).show ();
finish();
return;
}
Toast.makeText(EditItemActivity.this, getResources().getString(R.string.item_update_success), Toast.LENGTH_LONG).show ();
finish ();
}
});
The adapter was created in the fragment like this (where "rv_items" is the RecyclerView):
adapter = new ItemAdapter(itemList, getActivity());
rv_items.setAdapter(adapter);
The Item class is declared as:
class Item implements Serializable
In general, any time you're talking about "modifying a RecyclerView", that's a hint that you're looking at things the wrong way. You should always think about modifying the data, and then realize that the RecyclerView is just one way to display that data. Of course, you'll need to call methods like notifyDataSetChanged() whenever you modify the data so that the RecyclerView can know to update its display to pick up the changes, but you should still always think about the data first.
That being said, the root of the problem here is that you need some way to uniquely identify your item in your list of data items. Generally, I'd lean towards using some sort of unique ID here (instead of position). Then, when your second activity finishes and returns its result, you can use the ID to update your data list and dispatch the changes to the RecyclerView.
With all that, you'd have something like this:
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == MY_REQUEST) {
if (resultCode == Activity.RESULT_OK && data != null) {
String id = data.getStringExtra("id");
for (int i = 0; i < items.size(); ++i) {
if (items.get(i).getId().equals(id)) {
// the item at position i was updated.
// insert your code here...
// at the end, notify the adapter
adapter.notifyItemChanged(i);
}
}
}
}
}

RecyclerView changes multiple rows although only one row was targeted

I got a RecyclerView and want to change the appearance of any clicked row. For that I have a callbackFunction in my Activity which I pass to the Adapter, which then is called inside the Adapter, as soon as I click on any row in the RecyclerView.
The clicked row is then changed, but it happens, that not only the clicked rows are changed but also other rows, that weren't clicked and were never clicked before. I checked the ArrayList that contains the data, but everything is fine there. Only the clicked elements contain the trigger to change the appearance of the row.
What is causing the other rows to change, although they have not been clicked?
Interface inside activity for callback
public interface onHeaderClickListener{
void onHeaderClicked(int index);
}
Inside RecyclerView Adapter
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, final int position) {
if (holder instanceof ViewHolderHeader){
((ViewHolderHeader)holder).dateHeaderTextView.setText( Integer.toString(((objClass_offerDateHeader) arrayList.get(position)).getDate()));
if(((objClass_offerDateHeader) arrayList.get(position)).isSelected()){
((ViewHolderHeader)holder).dateHeaderTextView.setBackgroundColor(Color.parseColor("#b642f4"));
}
((ViewHolderHeader)holder).dateHeaderTextView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onHeaderClickListener.onHeaderClicked(position);
}
});
}
}
Adapter initialisation inside activity
customAdapterRecyclerViewAddOffersTo = new customAdapterRecyclerViewAddOffers(offerArrayList,"dragTo", new onHeaderClickListener() {
#Override
public void onHeaderClicked(int index) {
if (offerArrayList.get(index) instanceof objClass_offerDateHeader){
if(((objClass_offerDateHeader) offerArrayList.get(index)).isSelected()){
((objClass_offerDateHeader) offerArrayList.get(index)).setSelected(false);
}
else {
((objClass_offerDateHeader) offerArrayList.get(index)).setSelected(true);
}
customAdapterRecyclerViewAddOffersTo.notifyDataSetChanged();
}
}
});
In your onBindViewHolder method you have to set the background of the unselected cell, keep in mind the the cells are reused and you only set the background of selected cells so when it is reused the background is not returned to the normal color
So in code you will have to add an else condition
if(((objClass_offerDateHeader) arrayList.get(position)).isSelected()){
((ViewHolderHeader)holder).dateHeaderTextView.setBackgroundColor(Color.parseColor("#b642f4"));
} else {
((ViewHolderHeader)holder).dateHeaderTextView.setBackgroundColor(Color.parseColor("#FFFFFF")); // I assume you need it to be white you can change it to any other color
}
You need to add an else condition here:
if(((objClass_offerDateHeader) arrayList.get(position)).isSelected()){
((ViewHolderHeader)holder).dateHeaderTextView.setBackgroundColor(Color.parseColor("#b642f4"));
}
Viewholders get recycled, so you cannot be sure of the current state when onBindViewHolder is called.

How to reset recyclerView position item views to original state after refreshing adapter

I have a RecyclerView with rows that have views that when clicked will be disabled for that row position.
The problem is after I update the adapter like this:
adapterData.clear();
adapterData.addAll(refreshedAdapterData);
notifyDataSetChanged();
After refreshing the data, the disabled views at the previous recycler position still remain disabled even though the data is refreshed.
How can I reset the views to the original state after refreshing adapter data.
Use below code.
adapterData.clear();
adapterData.addAll(refreshedAdapterData);
adapter.notifyDataSetChanged();
OR
recyclerView.invalidate();
When you call notifyDataSetChanged(), the onBindViewHolder() method of every view is called. So you could add something like this in the onBindViewHolder() of your Adapter method:
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, final int position) {
if (refreshedAdapterData.get(position).isInDefaultState()) {
//set Default View Values
} else {
//the code you already have
}
}
I have resolved this by putting a conditional statement inside onBindViewHolder method instructing all positions to reset the disabled views if data meets the required conditions for a refreshed data.
#Christoph Mayr, thanks for your comments. It helped point me in the right direction.
I cleared the data then notify change but the selected checkbox doesn't reset but just moves up one position. Let say I selected item #1 , move out of the RecyclerView, came back and it will auto select item #0.
So, I created new adapter again at onResume(), i worked for me but i don't know if it's the right way to handle this situation.
#Override
public void onResume() {
super.onResume();
if(selectedItems != null && selectedItems.size() > 0){
selectedItems.clear(); // if no selected items before then no need to reset anything
if(adapter != null && recyclerView != null){
// to remove the checked box
adapter = null;
adapter = new MyAdapter(items, new MyAdapter.MyAdapterListener() {
#Override
public void onSelected(int pos) {
selectedItems.add(items.get(pos));
}
#Override
public void onUnSelected(int pos) {
selectedItems.remove(items.get(pos));
}
});
recyclerView.setAdapter(adapter);
}
}
}

Categories

Resources