Undo deleted item from RecyclerView and Firebase - java

After removing the item from the Realtime Database and RecyclerView, I wish to add an UNDO function to recover the deleted item just now. May I know how to achieve it?
Below is my source code in Adapter class:
public Adapter(#NonNull FirebaseRecyclerOptions options) {
super(options);
}
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate
(R.layout.retrieved_layout,parent,false);
return new MyViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull MyViewHolder holder, int position, #NonNull Model model) {
holder.name.setText(model.getTask());
holder.date.setText(model.getDate());
}
public static class MyViewHolder extends RecyclerView.ViewHolder {
TextView name = itemView.findViewById(R.id.taskTv);
TextView date = itemView.findViewById(R.id.dateTv);
public MyViewHolder(#NonNull View itemView) {
super(itemView);
}
}
And here is my MainActivity:
recyclerView = findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setHasFixedSize(true);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setReverseLayout(true);
linearLayoutManager.setStackFromEnd(true);
loader = new ProgressDialog(this);
mAuth = FirebaseAuth.getInstance();
onlineUserID = mAuth.getCurrentUser().getUid();
reference = FirebaseDatabase.getInstance().getReference().child("task").child(onlineUserID);
add = findViewById(R.id.add);
add.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
addTask();
}
});
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleCallback);
itemTouchHelper.attachToRecyclerView(recyclerView);
// add task to recycler view
FirebaseRecyclerOptions<Model> options = new FirebaseRecyclerOptions.Builder<Model>()
.setQuery(reference, Model.class)
.build();
adapter = new Adapter(options);
}
// add task to real time database
private void addTask() {
AlertDialog.Builder myDialog = new AlertDialog.Builder(this);
LayoutInflater inflater = LayoutInflater.from(this);
View view = inflater.inflate(R.layout.input, null);
myDialog.setView(view);
AlertDialog dialog = myDialog.create();
final EditText task = view.findViewById(R.id.taskName);
ImageView upload = view.findViewById(R.id.upload);
upload.setOnClickListener((v) -> {
String mTask = task.getText().toString().trim();
String id = reference.push().getKey();
String date = DateFormat.getDateInstance().format(new Date());
if(TextUtils.isEmpty(mTask)){
task.setError("Task name required");
return;
}
else {
loader.setMessage("Adding your data");
loader.setCanceledOnTouchOutside(false);
loader.show();
Model model = new Model(mTask,id,date);
reference.child(id).setValue(model).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if(task.isSuccessful()) {
Toast.makeText(MainActivity.this, "Task added successfully !", Toast.LENGTH_SHORT).show();
loader.dismiss();
}
}
});
}
dialog.dismiss();
});
dialog.show();
}
// add task to recycler view
#Override
protected void onStart() {
super.onStart();
recyclerView.setAdapter(adapter);
adapter.startListening();
}
// swipe to delete task
ItemTouchHelper.SimpleCallback simpleCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
#Override
public boolean onMove(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder, #NonNull RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(#NonNull RecyclerView.ViewHolder viewHolder, int direction) {
if (direction == ItemTouchHelper.LEFT) {
reference.child(key).removeValue();
Snackbar.make(recyclerView,"Task deleted", Snackbar.LENGTH_SHORT)
.setAction("Undo", new View.OnClickListener() {
#Override
public void onClick(View v) {
}
})
.show();
}
}

#Eyosiyas's answer will work but it has a downside. If you want to get all children of a node where the property "isActive" holds the value of true, it will work perfectly fine. However, if you need to add another filter, for example, to filter the data according to a "time" property, this won't work, because the Firebase Realtime Database does not support queries on multiple properties.
What options do you have?
You can perform the delete operation, and if the user clicks on an "UNDO" button, write the data back. It's not the best solution, as it implies a delete operation followed by a write operation. Or you can simulate the delete operation. How? When the user clicks on the delete button, don't perform the actual delete operation. Wait to see if the user clicks "UNDO". If the user clicks "UNDO" don't delete anything, otherwise, delete the element.

If you delete it from the real-time database then it is gone.
If you want to achieve the undo functionality, I suggest you create another value isActive.
Then when the user deletes you simply change isActive to false and don't show that data.
If the user wants the data back you simply change isActive to true and the data is back.
So the overall process is that you don't actually delete the data but you deactivate it.

Eyosiyas's answer is correct but you can delete the item after undo option is no longer available. Let's say you have a field in your data model named 'deleted' and when user removes the item, you set it to true. At this point all selects from database should exclude items flagged as deleted. I noticed you show a SnackBar with undo action. When undo is clicked you should set deleted back to false and when SnackBar is dismissed you can actually delete item from database (because there is no way user can redo deletion without SnackBar and its action being available).
// Set 'deleted' to true in database
snackbar = Snackbar.make(
binding.root,
getString(R.string.item_deleted),
Snackbar.LENGTH_LONG
).setAction(getString(R.string.undo)) {
// Set 'deleted' back to false in database
}.addCallback(object : Snackbar.Callback() {
override fun onDismissed(transientBottomBar: Snackbar, event: Int) {
if (event != DISMISS_EVENT_ACTION)
// Delete item from database
}
}).setDuration(5000)
snackbar.show()

Related

Why firebase is not able to query data from collection in recycler view?

I am able to get data in firebase collection but it does not query that data in recycler view. Recyclerview does not showing anything
This is the comment_list class.
public class comment_list {
public comment_list(String comments) {
this.comments = comments;
}
public String getComments() {
return comments;
}
public void setComments(String comments) {
this.comments = comments;
}
String comments;
}
This is comment_adapter class
public class comment_adapter extends FirestoreRecyclerAdapter<comment_list, comment_adapter.comment_holder> {
public comment_adapter(#NonNull FirestoreRecyclerOptions<comment_list> options) {
super(options);
}
#Override
protected void onBindViewHolder(#NonNull comment_holder holder, int position, #NonNull comment_list model) {
holder.commment_on_post.setText(model.getComments());
}
#NonNull
#Override
public comment_holder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.comment_recycler_dsign, parent, false);
return new comment_holder(v);
}
public class comment_holder extends RecyclerView.ViewHolder{
TextView commment_on_post;
public comment_holder(#NonNull View itemView) {
super(itemView);
commment_on_post = itemView.findViewById(R.id.commenttextview);
}
}
This is Comments class. In this I am able to get data in firebase collection but it is not query that data in recycler view.
public class Comments extends AppCompatActivity {
ImageView profileimage;
EditText addcommenttext;
TextView postcommenttext;
FirebaseFirestore db;
RecyclerView comment_recycler_view;
comment_adapter adaptercomment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_comments);
profileimage = findViewById(R.id.Addcommentprofileimage);
addcommenttext = findViewById(R.id.addcommenttext);
postcommenttext = findViewById(R.id.postcomment);
comment_recycler_view = findViewById(R.id.commentsrecycler);
postcommenttext.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (addcommenttext.equals("")) {
Toast.makeText(Comments.this, "Comment can't be empty", Toast.LENGTH_SHORT).show();
} else {
String commentText = addcommenttext.getText().toString();
CollectionReference commentref = FirebaseFirestore.getInstance() .collection("CommentDetails");
commentref.add(new comment_list(commentText));
FirebaseFirestore fbfs = FirebaseFirestore.getInstance();
CollectionReference commentrefs = fbfs.collection("CommentDetails");
Query query = commentrefs;
FirestoreRecyclerOptions<comment_list> options = new FirestoreRecyclerOptions.Builder<comment_list>()
.setQuery(query, comment_list.class)
.build();
adaptercomment = new comment_adapter(options);
comment_recycler_view.setHasFixedSize(true);
comment_recycler_view.setLayoutManager(new LinearLayoutManager(getApplication()));
comment_recycler_view.setAdapter(adaptercomment);
finish();
Toast.makeText(Comments.this, "Commented", Toast.LENGTH_SHORT).show();
}
}
});
}
First of all, let's reconfigure your Comments activity class. It would be recommended to initialise the recycle adapter in your onCreate method and not in the overridden onClick method. With the current set up, a new comment_adapter is initialised every time the onClick listener is triggered. It's best that we set-up only one. Here's how things look after the changes (I've added comments for clarity):
NOTE: I have renamed classes, variables and methods to use java and android conventions for clarity. Learning these will help you a lot in being able to read others code and save you a lot of headaches with your own code.
public class CommentsActivity extends AppCompatActivity {
FirebaseFirestore db;
CommentAdapter commentAdapter;
ImageView profileImageView;
EditText commentEditText;
RecyclerView commentRecyclerView;
Button addCommentButton; // Replaces the text view you are using
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
profileImageView = findViewById(R.id.add_comment_profile_image);
commentEditText = findViewById(R.id.comment_edit_text);
addCommentButton = findViewById(R.id.add_comment_button);
commentRecyclerView = findViewById(R.id.comments_recycle_view);
// Enables firestore debugging which will help a lot when trying to troubleshoot
FirebaseFirestore.setLoggingEnabled(true);
// We are now setting up our query directly within the OnCreate method.
db = FirebaseFirestore.getInstance();
Query query = db.collection("CommentDetails").orderBy("timestamp").limit(50);
FirestoreRecyclerOptions<Comment> options = new FirestoreRecyclerOptions.Builder<Comment>()
.setQuery(query, Comment.class)
.build();
// Setting up the recycle adapter in onCreate
commentAdapter = new CommentAdapter(options);
commentRecyclerView.setLayoutManager(new LinearLayoutManager(this));
commentRecyclerView.setAdapter(commentAdapter);
// Set up your onClickListener just as before.
addCommentButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Note that the previous null check is unsuccessful. Previously, the object instance
// was being checked, and not the contents of the edit text. This resolves that issue. (:
if (commentEditText.toString().isEmpty()) {
Toast.makeText(CommentsActivity.this, "Comment can't be empty", Toast.LENGTH_SHORT).show();
} else {
String commentText = commentEditText.getText().toString();
CollectionReference commentColRef = FirebaseFirestore.getInstance().collection("CommentDetails");
commentColRef.add(new Comment(commentText));
Toast.makeText(CommentsActivity.this, "Commented", Toast.LENGTH_SHORT).show();
}
}
});
}
#Override
protected void onStart() {
super.onStart();
commentAdapter.startListening();
}
#Override
protected void onStop() {
super.onStop();
commentAdapter.stopListening();
}
}
You will notice the addition of two new methods: onStart and onStop. Within these methods we start and stop the query listeners attached to the FirestoreRecyclerAdapter. It will be really helpful to refer to the FirebaseUI for Cloud Firestore read-me.
It is important to note in the code above I also renamed your data model from comment_list to Comment. The reason for this, is that an instance of this class only stores the state of one comment. It does not store a list of comments. I think it might cause confusion when you are trying to debug your code. In the case of using FirebaseUI, the actual list of comments (the comments list) which is bound to your recycle view is built for you by the FirebaseUI code, in the form of an array of Comment class instances.
In order to understand clearly how this is done, it might be useful to spend a couple of hours implementing a simple recycle view and adapter that is not connected to Firestore. That way a greater understanding as to how FirebaseUI is doing things can be developed. There are docs on that here.
Finally - here is a replacement to the comment_list class:
public class Comment {
String comment;
#ServerTimestamp Date timestamp;
// A zero argument constructor is required by firestore.
public Comment() {
}
public Comment(String comment) {
this.comment = comment;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public Date getTimestamp() {
return timestamp;
}
public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
The only difference here is there is a zero-args (no argument) constructor, which is required by firestore.
A word to the wise - I haven't seen your view model item layout (comment_recycler_dsign), but just check that the root layout does not have a height of "match_parent". This is a common mistake. It is a good idea to check this first if you see only one recycle item being rendered.
Place a listener
fire base will automatically call once the upload is complete
firestore.collection("").add(Any()).addOnCompleteListener {
// do all your work here
}

How do I save the state of a selected item from recycle view?

I have multiple items in my chatListLinearLayout, I would like to update my messageStatus to say something such as "Item selected" for the selected item. I managed to do this successfully but I would like to save this so the next time the user reopens the app it will remain.Could someone assist me?
displayMessagesRecycleView.addOnItemTouchListener(new RecyclerItemClickListener(getContext(), new RecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, final int position) {
CircleImageView profileImage = view.findViewById(R.id.users_profile_image);
LinearLayout chatListLinearLayout = view.findViewById(R.id.chatListLinearLayout);
final TextView messageStatus = view.findViewById(R.id.user_status);
chatListLinearLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
messageStatus.setText("Item selected");
}
});
You can setSelected(true); for view and set one boolean in you object model
and for save selected item state you need to save your list into sharedPrefs or sqlite database

how to save the state of those items which have been selected in recyler view even after the app is closed

What I want to do is to show the same selected items on a recycler view even after the activity has been closed and only change items color when I again click on it. For now I have achieved changing the color on click but the state doesn't get saved?
This is my adapter:
public class LightsRecyclerViewAdapter extends
RecyclerView.Adapter<LightsRecyclerViewAdapter.ViewHolder> {
// private List<Integer> mViewColors;
private List<String> mAnimals;
private LayoutInflater mInflater;
private ItemClickListener mClickListener;
// data is passed into the constructor
LightsRecyclerViewAdapter(Context context, List<String>
animals) {
this.mInflater = LayoutInflater.from(context);
this.mAnimals = animals;
}
// inflates the row layout from xml when needed
#Override
#NonNull
public ViewHolder onCreateViewHolder(#NonNull ViewGroup
parent, int viewType) {
View view = mInflater.inflate(R.layout.item, parent,
false);
return new ViewHolder(view);
}
// binds the data to the view and textview in each row
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int
position) {
// int color = mViewColors.get(position);
String animal = mAnimals.get(position);
// holder.myView.setBackgroundColor(color);
holder.myTextView.setText(animal);
}
// total number of rows
#Override
public int getItemCount() {
return mAnimals.size();
}
// stores and recycles views as they are scrolled off screen
public class ViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener {
View myView;
TextView myTextView;
ViewHolder(View itemView) {
super(itemView);
// myView = itemView.findViewById(R.id.colorView);
myTextView =
itemView.findViewById(R.id.tvAnimalName);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
if (mClickListener != null)
mClickListener.onItemClick(view, getAdapterPosition());
}
}
// convenience method for getting data at click position
public String getItem(int id) {
return mAnimals.get(id);
}
// allows clicks events to be caught
public void setClickListener(ItemClickListener
itemClickListener) {
this.mClickListener = itemClickListener;
}
// parent activity will implement this method to respond to click events
public interface ItemClickListener {
void onItemClick(View view, int position);
}
}
And this is my activity:
public class DevicesList extends AppCompatActivity implements
LightsRecyclerViewAdapter.ItemClickListener{
private LightsRecyclerViewAdapter adapter,adapter1;
TextView title;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_devices_list);
title = (TextView)findViewById(R.id.textGrid);
// data to populate the RecyclerView with
ArrayList<Integer> viewColors = new ArrayList<>();
viewColors.add(Color.BLUE);
viewColors.add(Color.YELLOW);
viewColors.add(Color.MAGENTA);
viewColors.add(Color.RED);
viewColors.add(Color.BLACK);
ArrayList<String> Lab1LightsList = new ArrayList<>();
Lab1LightsList.add("Light 1");
Lab1LightsList.add("Light 2");
Lab1LightsList.add("Light 3");
Lab1LightsList.add("Light 4");
Lab1LightsList.add("Light 5");
ArrayList<String> Lab1ACList = new ArrayList<>();
Lab1ACList.add("AC 1");
Lab1ACList.add("AC 2");
Lab1ACList.add("AC 3");
Lab1ACList.add("AC 4");
Lab1ACList.add("AC 5");
ArrayList<String> Lab2LightsList = new ArrayList<>();
Lab2LightsList.add("Light 1");
Lab2LightsList.add("Light 2");
Lab2LightsList.add("Light 3");
Lab2LightsList.add("Light 4");
Lab2LightsList.add("Light 5");
Lab2LightsList.add("Light 6");
ArrayList<String> Lab2ACList = new ArrayList<>();
Lab2ACList.add("AC 1");
Lab2ACList.add("AC 2");
Lab2ACList.add("AC 3");
Lab2ACList.add("AC 4");
// set up the RecyclerView
RecyclerView recyclerView = findViewById(R.id.list1);
RecyclerView recyclerView1 =findViewById(R.id.list2);
LinearLayoutManager horizontalLayoutManagaer
= new LinearLayoutManager(DevicesList.this, LinearLayoutManager.HORIZONTAL, false);
LinearLayoutManager horizontalLayoutManager
= new LinearLayoutManager(DevicesList.this, LinearLayoutManager.HORIZONTAL, false);
recyclerView.setLayoutManager(horizontalLayoutManagaer);
recyclerView1.setLayoutManager(horizontalLayoutManager);
Intent mIntent = getIntent();
int intValue = mIntent.getIntExtra("labno", 0);
if(intValue==0) {
adapter = new LightsRecyclerViewAdapter(this, Lab1LightsList);
adapter1 = new LightsRecyclerViewAdapter(this, Lab1ACList);
adapter.setClickListener(this);
adapter1.setClickListener(this);
recyclerView.setAdapter(adapter);
recyclerView1.setAdapter(adapter1);
}
if(intValue==1) {
adapter = new LightsRecyclerViewAdapter(this, Lab2LightsList);
adapter1 = new LightsRecyclerViewAdapter(this, Lab2ACList);
adapter.setClickListener(this);
adapter1.setClickListener(this);
recyclerView.setAdapter(adapter);
recyclerView1.setAdapter(adapter1);
}
}
#Override
public void onItemClick(View view, int position) {
Toast.makeText(this, "You clicked " +
adapter.getItem(position) + " on item position " + position,
Toast.LENGTH_SHORT).show();
view.setBackgroundColor(getResources().getColor(R.color.colorPrimaryDark));
}
}
Please help on this.
Create one selected item position list and store it in prefs when an app goes to background or closed. Load that list when launching an app and compare that list in an adapter's onBindViewHolder's position parameter and marks it selected/unselected based on a comparison.
As per my understandings about your question, you want to save the state of the selected items even after the app is closed, and then you want to reload it whenever the app is launched again. You need to refer to this link Android Save Data
For the above solution, there can be various ways to save state, I am mentioning a few below:
Use SQLite Database to save the selected items. Then, whenever the app is loaded, fetch all the selected data from the DB and then mark them selected with whatever colour you want on the list.
You can also use Shared Preferences, to store the selection. And, same as above, you can reload the data when the app is launched.
You can also store the data in a specific format, maybe CSV, JSON, XML etc., in a file and save it either in Internal Storage or External Storage of the device. And when the app is launched, fetch all the selected values from the file and process accordingly.
You can also use a web server, Firebase Storage, or other cloud storage services to save the data and then fetch the data on new app launch.
Do note: All these techniques require you to save the state before the app is closed. So it is better to store the states, either on click of the item, or onPause method of the activity.
If you face any problems with these solutions, you can post another comment and I will give it a look.
Save these clicked item position in a hashmap in Shareprefence. suppose u close the activity after u coming back the activity just pass the saved list with ur data in adapter and compare the shareprefence list with ur data list if position or data match than make the itemview layout colored.
// save clicked item is a list and save it sharePreference.
List<Integer> clikedList = new ArrayList<>();
if (clicked item){
ClikedList.add(position)
}
String value = gson.toJson(list);
SharedPreferences prefs = context.getSharedPreferences("mylist",
Context.MODE_PRIVATE);
Editor e = prefs.edit();
e.putString("list", value);
e.commit();
// for getting cliked position list from SharePreference
SharedPreferences prefs = context.getSharedPreferences("mylist",
Context.MODE_PRIVATE);
String value = prefs.getString("list", null);
GsonBuilder gsonb = new GsonBuilder();
Gson gson = gsonb.create();
MyObject[] list = gson.fromJson(value, MyObject[].class);
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
// suppose clicked position 4 u get from shaved cliked list
in here u neddd to retreive cliked list position and clored those item
int select = 4;
if (select == position) {
holder.itemView.setBackgroundColor(Color.BLUE);
Toast.makeText(context, "" + position, Toast.LENGTH_SHORT).show();
} else {
holder.itemView.setBackgroundColor(Color.parseColor("#214F4B"));
Toast.makeText(context, "" + position, Toast.LENGTH_SHORT).show();
}
holder.tv_title.setText(data.get(position));
}

How to handle dynamically created buttons in android?

I am new to android development, in fact its my first application. I have created a dynamic layout in my project based on Json. Each object includes an "id" key and some more string keys. every object in my json should be transformed to a cardview inside a recylcerview and each cardview has a button.
My problem is handling these dynamic buttons. Is it possible to determine which button was clicked?
View.id is an integer, and you shouldn't set arbitrary values to it if the View is generated dinamically, what you can use though is View.tag. So you can assign the id defined in the JSON to tag and then check the tag value when the View is clicked. E.g.
val view1 = View(context)
view1.tag = "id from JSON 1"
view1.setOnClickListener(this::onViewClicked)
val view2 = View(context)
view2.tag = "id from JSON 2"
view2.setOnClickListener(this::onViewClicked)
// ...
private fun onViewClicked(view: View){
val jsonId = view.tag as? String
// ...
}
If your min sdk level at least 17, another option would be to generate ids dinamically with View.generateViewId() and store them in a Map together with your JSON ids
Use a Tag to differentiate the buttons. Add OnClickListener to all Button and set different String tag to button.
button.setOnClickListener(this);
Add ClickListener
#Override
public void onClick(View view){
String tag = (String)view.getTag();
if(tag.equals(tag1)){
// action here
}
//.......
.....
}
Yeah pretty straight, all you have to do is to give them a unique id
I assume you must have a JSON array for dynamic buttons creation.
sample code
public Button createButton(Context context,String text,int buttonNo){
//here set the properties
Button bt = new Button(context);
bt.setText(text);
bt.setId(buttonNo);
bt.setTag(buttonNo);
bt.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View view){
//handle the button click, unique id , you can use to differentiate
int id = view.getId();
}
});
return bt;
}
call this above method in for loop or as many times you want to create.
you can also set a tag as mentioned above to store more information.
Finally I put the OnClickListener in onBindViewHolder event inside my customAdapter class as below:
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> implements View.OnClickListener {
private Context context;
private List<MyData> my_data;
public CustomAdapter(Context context, List<MyData> my_data) {
this.context = context;
this.my_data = my_data;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.card,parent,false);
return new ViewHolder(itemView);
}
#Override
public void onBindViewHolder(final ViewHolder holder, int position) {
holder.match_date.setText(my_data.get(position).getMatch_date());
holder.home_name.setText(my_data.get(position).getHome_name());
holder.away_name.setText(my_data.get(position).getAway_name());
holder.button.setId(my_data.get(position).getId());
holder.button.setTag(my_data.get(position).getStringId());
holder.button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int intHomeGoals = Integer.parseInt(holder.edtHomeGoals.getText().toString());
int intAwayGoals = Integer.parseInt(holder.edtAwayGoals.getText().toString());
if (intHomeGoals == intAwayGoals)
{
Toast.makeText(context, "00000", Toast.LENGTH_SHORT).show();
}
}
});
Glide.with(context).load(my_data.get(position).getHome_logo()).into(holder.home_logo);
Glide.with(context).load(my_data.get(position).getAway_logo()).into(holder.away_logo);
}

Selected items in RecyclerView change on scrolling

I have a RecyclerView with each element representing an event. I want to let the user select events by clicking it. Once selected, the event(s) and a report button will be colored:
UI before performing a click: click here.
UI After performing a click: click here.
It's pretty simple and allegedly works; I set an OnClickListener for each ViewHolder which is responsible for coloring the item, and when fired it's triggering another event in the owning activity named onOccurrenceSelected, which is responsible for changing the button's state.
However, when scrolling through the RecyclerView's items, other irrelevant items are colored like their OnClickListener was triggered (though it wasn't), and when scrolling back the selected event is colored as not selected. While this is happening, the only event that's supposed to color the items is not triggered.
Any explanation for such behavior? Thanks!
EDIT: Here are some relevant code from the adapter:
private List<Occurrence> mDataSet;
private Activity activity;
public <OccurrencesActivity extends OnOccurrenceSelectedListener> OccurrencesAdapter(OccurrencesActivity occurrencesActivity, List<Occurrence> occurrences) {
this.activity = (android.app.Activity) occurrencesActivity;
mDataSet = occurrences;
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
Occurrence instance = mDataSet.get(position);
...
setOnClickListener(holder, instance);
}
private void setOnClickListener(final ViewHolder holder, final Occurrence occurrence) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (!occurrence.isSelected()) {
holder.itemView.setBackgroundColor(App.getContext().getResources().getColor(R.color.turquoise));
holder.titleTextView.setTextColor(App.getContext().getResources().getColor(R.color.white));
holder.statusTextView.setTextColor(App.getContext().getResources().getColor(R.color.white));
holder.dateTextView.setTextColor(App.getContext().getResources().getColor(R.color.white));
holder.timeTextView.setTextColor(App.getContext().getResources().getColor(R.color.white));
} else {
holder.itemView.setBackgroundColor(App.getContext().getResources().getColor(R.color.white));
holder.titleTextView.setTextColor(App.getContext().getResources().getColor(R.color.turquoise));
holder.statusTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.dateTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.timeTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
}
occurrence.setSelected(!occurrence.isSelected());
((OnOccurrenceSelectedListener)activity).onOccurrenceSelected(mDataSet);
}
});
}
Recyclerview always resuse views while scrolling so you have to store selected positions into temporary arraylist and then keep condition check into onBindViewHolder that whether that particular position is already exists in arraylist or not? I updated your adaper. find the below changes with comment
private List<Occurrence> mDataSet;
private Activity activity;
//Added here temporary ArrayList
private ArrayList<String> mSelectedPosition = new ArrayList<String>;
public <OccurrencesActivity extends OnOccurrenceSelectedListener> OccurrencesAdapter(OccurrencesActivity occurrencesActivity, List<Occurrence> occurrences) {
this.activity = (android.app.Activity) occurrencesActivity;
mDataSet = occurrences;
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
//Set ViewTag
holder.itemView.setTag(position);
//Check everyposition during view binding process
if(mSelectedPosition.contains(String.valueOf(position))){
holder.itemView.setBackgroundColor(App.getContext().getResources().getColor(R.color.white));
holder.titleTextView.setTextColor(App.getContext().getResources().getColor(R.color.turquoise));
holder.statusTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.dateTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.timeTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
}else{
holder.itemView.setBackgroundColor(App.getContext().getResources().getColor(R.color.white));
holder.titleTextView.setTextColor(App.getContext().getResources().getColor(R.color.turquoise));
holder.statusTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.dateTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.timeTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
}
Occurrence instance = mDataSet.get(position);
...
setOnClickListener(holder, instance);
}
private void setOnClickListener(final ViewHolder holder, final Occurrence occurrence) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// Get Position
int position = (int) view.getTag();
//Remove SelectedPosition if Already there
if(mSelectedPosition.contains(position))
mSelectedPosition.remove(String.valueOf(position));
else
mSelectedPosition.add(String.valueOf(position));
notifyDataSetChanged();
//Not sure about this lines
occurrence.setSelected(!occurrence.isSelected());
((OnOccurrenceSelectedListener)activity).onOccurrenceSelected(mDataSet);
}
});
}
Its the default behaviour of recyclerview. it will recycle/reuse views which are not in use currently. If you want to save the state which is colored or not. Then save a parameter in your List<Object> per position. and as per position in onBindViewHolder method use that position to change the color.
Try by Setting Tag to your item in onBindViewHolder of Adapter
holder.yourItem.setTag(position);
And then Inside the onClickListener,Just save that position in shared Pref. if it's selected, whenever you set adapter then before setting values just check that is it selected or not based on shared Pref. and perform action for same.
public void onClick(View view) {
if (!occurrence.isSelected()) {
//save position in share pref.
}
}

Categories

Resources