I wish to delete an object from Firebase on Swipe left. Everything works fine now with the swipe and it it removed from the view, but it stays in the database.
I've added the following to my onCreate:
ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
Toast.makeText(ListBox.this, "Item removed", Toast.LENGTH_SHORT).show();
//Remove swiped item from list and notify the RecyclerView
}
};
and this is how i populate my ViewHolder.
#Override
protected void populateViewHolder(final BoxViewHolder viewHolder, final Box model, int position) {
viewHolder.setTitle(model.getTitle());
final String boxUniqueKey = model.getBoxkey();
final DatabaseReference postRef = getRef(position);
final String postKey = postRef.getKey();
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Launch BoxItem view
final Intent intent = new Intent(ListBox.this, AddBoxItem.class);
String boxkey = model.getBoxkey();
String boxName = model.getTitle();
startActivity(intent);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
itemTouchHelper.attachToRecyclerView(allBoxes);
}
});
}
but how (and where) do I get the position of the item, and how can I send the remove query to the Firebase Database?
I have coded something very similar to what you are trying to achieve. This is one way you could achieve it.
First, extend the ItemTouchHelper.SimpleCallback class to make your own custom class.
public class SwipeToDeleteCallback extends ItemTouchHelper.SimpleCallback {
private RecyclerAdapter adapter; // this will be your recycler adapter
private DatabaseReference root = FirebaseDatabase.getInstance().getReference();
/**
*
Make sure you pass in your RecyclerAdapter to this class
*/
public CallBack(int dragDirs, int swipeDirs, RecyclerAdapter adapter) {
super(dragDirs, swipeDirs);
this.adapter = adapter;
}
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition(); // this is how you can get the position
Object object = adapter.getObject(position); // You will have your own class ofcourse.
// then you can delete the object
root.child("Object").child(object.getId()).setValue(null);// setting the value to null will just delete it from the database.
}
Calling viewHolder.getAdapterPosition() returns the position of the view in the adapter. You can use this position to get the Object from the ArrayList contained in your recycler adapter.
In my adapter, I have created a getObject method. This just returns the object from the ArrayList that my adapter has. Once I have the object, I can call the associated Firebase Realtime Database method and delete the object. In my Object class, I have stored the unique key within the object so I can easily delete it. I get the unique id by calling getId(). I pass this to the associated Firebase Realtime Database method and set the value to null which deletes it.
After doing this you can add it to your recycler view like this.
ItemTouchHelper.SimpleCallback swipeToDeleteCallback = new
SwipeToDeleteCallback(0, ItemTouchHelper.RIGHT, choreRecyclerAdapter, getContext()); // Making the SimpleCallback
ItemTouchHelper touchHelper = new ItemTouchHelper(swipeToDeleteCallback);
touchHelper.attachToRecyclerView(recyclerView); // then attach it to your recycler view
First, you make a simple callback and make sure you instantiate the custom class that you extended. Be sure to pass your recycler adapter.
Notice I only support right swipe by passing ItemTouchHelper.Right. You can support left or pass in both left and right.
Then create an ItemTouchHelper object and pass it your simple callback.
Lastly, you attach your touch helper to your recycler view and that's all.
Related
I am trying to show a list of user in recyclerView and trying to connect the textview layout in bind method in groupie library and i don't know how to link the id of layout to recyclerview viewHolder? and also how to use picasso library in viewholder?
private void fetchUser(){
DatabaseReference fireBaseReference = FirebaseDatabase.getInstance().getReference().child("/userList");
fireBaseReference.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
GroupAdapter groupA = new GroupAdapter<ViewHolder>();
for (DataSnapshot snapshot : dataSnapshot.getChildren()){
Log.d("userList",snapshot.toString());
Users string = snapshot.getValue(Users.class);
groupA.add(new UserItem());
}
recyclerView.setAdapter(groupA);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
}
class UserItem extends Item<ViewHolder>{
// public UserItem(Users users){
// }
private Users users = new Users();
#Override
public void bind(#NonNull ViewHolder viewHolder, int position) {
viewHolder.itemView.findViewById(R.id.user_name_from_user_list);
viewHolder.
Picasso.get().load(users.getUri()).into(viewHolder.itemView.findViewById(R.id.user_photo_from_user_list));
}
#Override
public int getLayout() {
return R.layout.user_list_view_layout;
}
}
Groupie abstracts away the complexity of multiple item view types. Each Item declares a view layout id, and gets a callback to bind the inflated layout. That's all you need; you can add your new item directly to a GroupAdapter and call it a day.
Item with Kotlin
The Item class gives you simple callbacks to bind your model object to the generated fields. Because of Kotlin Android extensions, there's no need to write a view holder.**
class SongItem(private val song: Song) : Item() {
override fun getLayout() = R.layout.song
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.title.text = song.title
viewHolder.artist.text = song.artist
}
}
Item with data binding:
The Item class gives you simple callbacks to bind your model object to the generated binding. Because of data binding, there's no need to write a view holder.
If you're converting existing ViewHolders, you can reference any named views (e.g. R.id.title) directly from the binding instead.
#Override public void bind(SongBinding binding, int position) {
binding.title.setText(song.getTitle());
}
or you can do this way
#Override
public void bind(#NonNull final ViewHolder viewHolder, final int position) {
circleImageView = viewHolder.itemView.findViewById(R.id.circleImageViewForLatestMessage);
userMessage = viewHolder.itemView.findViewById(R.id.textView2);
userName = viewHolder.itemView.findViewById(R.id.textView41);
userName.setText(name);
userMessage.setText(messages);
You can also mix and match BindableItem and other Items in the adapter, so you can leave legacy viewholders as they are by making an Item.
For more information visit Groupie Library
Basically we need to get the email from the recyclerview
We tried adding onClickListener on the TextView in the RecyclerView, but when there are more than one entries in the RecyclerView, we cannot get the value.
Is there any way that I can store the values in variables before populating the RecyclerView and passing the values to another Activity?
I tried getting the values from the TextView in the RecyclerView, but I always show the text on the first row
Intent doesn't work on the Adapter class because its not an activity
ArrayList<ModelClass> objModelClassArrayList;
public DatabaseRecyclerAdapter(ArrayList<ModelClass> objModelClassArrayList) {
this.objModelClassArrayList = objModelClassArrayList;
}
#NonNull
#Override
public DatabaseViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType)
{
View singleRow= LayoutInflater.from(parent.getContext()).inflate(R.layout.single_row,parent,false);
return new DatabaseViewHolder(singleRow);
}
#Override
public void onBindViewHolder(#NonNull DatabaseViewHolder holder, int position)
{
ModelClass objModelClass=objModelClassArrayList.get(position);
holder.userNameTV.setText(objModelClass.getName());
holder.userLocation.setText(objModelClass.getAddress());
String e1=objModelClass.getEmail();
holder.userEmail.setText(e1);
}
#Override
public int getItemCount() {
return objModelClassArrayList.size();
}
public static class DatabaseViewHolder extends RecyclerView.ViewHolder
{
TextView userNameTV,userLocation,userEmail;
public DatabaseViewHolder(#NonNull View itemView)
{
super(itemView);
userNameTV=itemView.findViewById(R.id.sr_userNameTV);
userLocation=itemView.findViewById(R.id.sr_location);
userEmail=itemView.findViewById(R.id.sr_email);
}
}
I want to extract the email from a single row and pass it to another page where I can pass it in the DB query to get the results from the database.
first, need to pass context in the constructor of the adapter which can help you to open another activity from the recyclerView adapter. Then Inside BindViewHolder, you can create clickListener like as below,
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String emailText=holder.userEmail.getText().trim();
Intent intent=new Intent(context,activity_name_you _want_ to_open);
intent.setExtra("emailName",emailText);
(name of activity contains RV)context.startActivity(intent)
//Note: here context needs to be typecasted to activity from which we want to open new activity.
}
});
I have the problem that I want to update a nested RecyclerView with dynamically loading data. The outer recyclerView is vertical and the inner recyclerView is horizontal. So, I have created 2 adapters.
The main activity:
public class GroupScreenActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private OuterRecyclerViewAdapter adapter;
// the service connection
private ServiceConnection connection = new ServiceConnection() {
... // code that handles the service connection (not relevant for my question)
}
};
#Override
protected void onStart(){
// code that bind the service to the activity (not really relevant for my question)
}
#Override
protected void onStop(){
// code that unbinds the service from the activity (not really relevant for my question)
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_group_screen);
recyclerView = (RecyclerView) findViewById(R.id.recycler_View);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
updateUI();
}
// a Handler that calls a method of a bound service to retrieve the data of interest
private void updateUI(final String token){
final Handler handler = new Handler();
handler.post(new Runnable() {
#Override
public void run() {
if(bound && (mDownloadCliquesService != null)){
// holds the list of the statement lists
ArrayList<ArrayList<Statement>> myList = mDownloadCliquesService.getDataOfUser();
if(adapter == null){
// data is passed to the outer recyclerView adapter
adapter = new OuterRecyclerViewAdapter(this, myList);
recyclerView.setAdapter(adapter);
}
else{
// notify that the data is changed
adapter.notifyDataSetChanged();
}
}
// repeat the whole after 5 seconds
handler.postDelayed(this, 5000);
}
});
}
}
As you can see: The main activity just retrieves some data from a bound service and passes it to the outer recycler view. The data is a list of lists of type Statement. The number of lists in myList gives the rows of the outer recyclerview and the items in each list will define the number of columns of each inner recyclerview.
The outer recycler view looks like the following:
public class OuterRecyclerViewAdapter extends RecyclerView.Adapter<OuterRecyclerViewAdapter.InnerRecyclerViewHolder> {
// some instance variables
public OuterRecyclerViewAdapter( ... ) {
...
}
#Override
public InnerRecyclerViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(R.layout.inner_recyclerview_layout, parent, false);
return new InnerRecyclerViewHolder(view);
}
#Override
public void onBindViewHolder(final InnerRecyclerViewHolder holder, int position) {
// here, I create the inner recycler view adapter. Do I need to update it too???
adapter = new InnerRecyclerViewAdapter(context, items, position);
holder.recyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false));
holder.recyclerView.setAdapter(adapter);
}
#Override
public int getItemCount() {
return items.size();
}
public class InnerRecyclerViewHolder extends RecyclerView.ViewHolder {
private RecyclerView recyclerView;
private Button mAddButton;
private Button mSendButton;
private TextView tvCliqueName;
private ArrayList<Object> mList;
public InnerRecyclerViewHolder(View itemView) {
super(itemView);
// using 'itemView', get a reference to the inner recycler view.
recyclerView = (RecyclerView) itemView.findViewById(R.id.inner_recyclerView);
// get a reference to the clique name
tvCliqueName = (TextView) itemView.findViewById(R.id.cliqueName);
// get a reference to the send button
mSendButton = (Button) itemView.findViewById(R.id.send);
// get a reference to the add button
mAddButton = (Button) itemView.findViewById(R.id.add);
}
}
}
For the sake of brevity, I do not post the code for the inner recyclerview adapter because there is no adapter reference to update.
So, after every 5 second the main activity gets fresh data from my bound service and passes it to the outer recyclerview adapter which looks how many lists exist in the nested array list. Each list is then passed to the inner recycler view adapter which then shows the elements of each list.
So, my problem is the following: After each update the list is scrolling to the beginning. Let's say I have 5 elements in the first list, and I scroll to the 3rd one, after the update inner recycler view goes to the 1st automatically. Here is a short GIF how the output looks like:
I have checked out the following StackOverflow posts:
How to save RecyclerView's scroll position using RecyclerView.State?
Nested Recyclerview scrolls by itself
How to save scroll position of recyclerview in fragment
How to save scroll position of RecyclerView in Android?
But without success. How do I need to update so that the scroll position is not affected ?
Thanks and best regards
Save your horizontal scroll value:
outerRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
//save dx
}
});
and restore after update:
outerRecyclerView.setScrollX(dx)
I have implemented my RecyclerView with it's Custom Adapter as follows
Global Declarations as follows
private LinearLayoutManager linearLayoutManager;
private int pastVisibleItems, visibleItemCount, totalItemCount;
private CustomRecyclerViewAdapter customRecyclerViewAdapter;
First I created Adapter Instance inside onCreate() method which has Empty Array inside it and set it to recyclerView
linearLayoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(linearLayoutManager);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(
Utility.ItemDecorationConst);
recyclerView.addItemDecoration(dividerItemDecoration);
customRecyclerViewAdapter = new CustomRecyclerViewAdapter(getActivity());
recyclerView.setAdapter(customRecyclerViewAdapter);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
visibleItemCount = linearLayoutManager.getChildCount();
totalItemCount = linearLayoutManager.getItemCount();
pastVisibleItems = linearLayoutManager.findFirstVisibleItemPosition();
if (loading) {
if ((visibleItemCount + pastVisibleItems) >= totalItemCount) {
loading = false;
customRecyclerViewAdapter.addProgressBarEntry();
controller.getNextPage(PublisherAppContainerFragment.this);
}
}
}
});
After rendering complete View when I get data from AsyncTask for filling in recyclerView
I call following method of the Adapter to fill data
customRecyclerViewAdapter.addAll(myArray);
note : addAll() is not any overridden method
following is code of my CustomRecyclerViewAdapter
class CustomRecyclerViewAdapter extends RecyclerView.Adapter<CustomRecyclerViewAdapter.ViewHolder> {
ArrayList<MyModel> arrayList = new ArrayList<>();
Context context;
public CustomRecyclerViewAdapter(Context context) {
this.context = context;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ViewHolder viewHolder = null;
//inflated some view
return viewHolder;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
//binded data to holder
}
#Override
public int getItemCount() {
return arrayList.size();
}
public void addAll(ArrayList myArray) {
this.arrayList.addAll(myArray)
}
public void clear() {
arrayList.clear();
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public CardView cardView;
public ViewHolder(View view) {
super(view);
this.cardView = (CardView) view.findViewById(R.id.card_view);
this.cardView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
//handle operations
}
}
}
So whenever I get data from AsynTask I call method addAll() and recyclerView works like charm.
Now, My question is how it's working very well even though I have never called notifyDataSetChanged() on the adapter. Are there any previously registered Observers for the adapter? who observes if the dataset which has been returned in public int getItemCount() has been changed?
As I have read from documentation
void notifyDataSetChanged ()
Notify any registered observers that the data set has changed.
that means even though there are some observers registered you need to notify them using notifyDataSetChanged(). Right?
I also called
boolean flag = customRecyclerViewAdapter.hasObservers();
to know if there are any observers registered? Flag is True.
So anyone would please help me understand how exactly these things work?
If you look at the source of RecyclerView setAdapter call you will find a method setAdapterInternal(adapter, false, true);which is responsible for
Replaces the current adapter with the new one and triggers listeners.
This method is responsible for swapping the old adapter with the new one and internally it also registers for the custom Data Observer. This is the reason you are getting the flag as true
Based on what I can see of your code, I would say that there are not any observers attached to your RecyclerView that are picking up changes and keeping the list updated. What is more likely is that you are just getting "lucky" as when you scroll through the list the layout manager is continually calling getItemCount() on the adapter to determine if it should show more items. Whenever you call addAll(), you silently update the item count and it just happens to appear that observers were notified of the changes.
This is definitely a bug, and you would more likely see its effects in your implementation if you were dependent on a particular observer to monitor some aspect of the list, or doing more than just appending new items to the bottom (for example altering or inserting between existing items). The correct implementation as you pointed out is to call notifyDataSetChanged() whenever the list is updated, or even better be more specific with what changed if you can. For example, you can use:
public void addAll(ArrayList myArray) {
int positionStart = getItemCount() - 1;
this.arrayList.addAll(myArray);
notifyItemRangeInserted(positionStart, myArray.size());
}
public void clear() {
int oldSize = getItemCount();
arrayList.clear();
notifyItemRangeRemoved(0, oldSize);
}
My question is how it's working very well even though I have never called notifyDataSetChanged() on the adapter
It's because the addAll method by default calls the notifyDataSetChanged().
public void addAll(T ... items) {
synchronized (mLock) {
if (mOriginalValues != null) {
Collections.addAll(mOriginalValues, items);
} else {
Collections.addAll(mObjects, items);
}
}
if (mNotifyOnChange) notifyDataSetChanged();
}
And
public void addAll(#NonNull Collection<? extends T> collection) {
synchronized (mLock) {
if (mOriginalValues != null) {
mOriginalValues.addAll(collection);
} else {
mObjects.addAll(collection);
}
}
if (mNotifyOnChange) notifyDataSetChanged();
}
Here's the link - https://github.com/android/platform_frameworks_base/blob/master/core/java/android/widget/ArrayAdapter.java
EDIT - I see that you have your own addAll method which is calling addAll method of ArrayList.
This is how addAll method works -
private ArrayList<String> ex1 = new ArrayList();
private ArrayList<String> ex2 = new ArrayList();
private ArrayList<String> ex3 = new ArrayList();
ex1.add("one");
ex2.add("two");
ex3.addAll(ex1);
ex3.addAll(ex2);
System.out.println(ex3);
OUTPUT - [one, two]
This is what happening in your case.
I have shown progress bar and once I fetch data I hide the progress bar and make recyclerView visible - If in layout or code you set RecyclerView visibility GONE then layout will not happen and that is why Adapter.getItemsCount() not get called. So if you fetch data and populate adapter array with it and then change RecyclerView visibility from GONE to VISIBLE it will trigger update.
In case you don't call notifyDataSetChanged() RecyclerView will not know about update. I guess there is something else in your code that trigger RecyclerView update. To clarify this behavior let's use some dummy adapter:
private class DummyViewHolder extends RecyclerView.ViewHolder {
public DummyViewHolder (View itemView) {
super(itemView);
}
}
private class Adapter extends RecyclerView.Adapter<DummyViewHolder> {
private int mDummySize = 5;
#Override
public DummyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.dummy_view, parent, false);
return new DummyViewHolder(view);
}
#Override
public void onBindViewHolder(DummyViewHolder holder, int position) {
}
void setSize(int size) { this.mDummySize = size; }
#Override
public int getItemCount() {
return mDummySize;
}
}
And in onCraete() :
ViewHolder v = ...
final Adapter adapter = ..
...
//postpone adapter update
(new Handler()).postDelayed(new Runnable() {
#Override
public void run() {
adapter.setSize(10);//and nothing happend only 5 items on screen
}
}, 5000);
I thought they were the same, but they're not. The following code gives an indexOutOfBounds exception when I try to access the "position" index of my dataset, in this case a list of a model I created called Task:
public class TaskAdapter extends RecyclerView.Adapter<TaskAdapter.TaskViewHolder> {
private List<Task> taskList;
private TaskAdapter thisAdapter = this;
// cache of views to reduce number of findViewById calls
public static class TaskViewHolder extends RecyclerView.ViewHolder {
protected TextView taskTV;
protected ImageView closeBtn;
public TaskViewHolder(View v) {
super(v);
taskTV = (TextView)v.findViewById(R.id.taskDesc);
closeBtn = (ImageView)v.findViewById(R.id.xImg);
}
}
public TaskAdapter(List<Task> tasks) {
if(tasks == null)
throw new IllegalArgumentException("tasks cannot be null");
taskList = tasks;
}
// onBindViewHolder binds a model to a viewholder
#Override
public void onBindViewHolder(TaskViewHolder taskViewHolder, int pos) {
final int position = pos;
Task currTask = taskList.get(pos);
taskViewHolder.taskTV.setText(currTask.getDescription());
**taskViewHolder.closeBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d("TRACE", "Closing task at position " + position);
// delete from SQLite DB
Task taskToDel = taskList.get(position);
taskToDel.delete();
// updating UI
taskList.remove(position);
thisAdapter.notifyItemRemoved(position);
}
});**
}
#Override
public int getItemCount() {
//Log.d("TRACE", taskList.size() + " tasks in DB");
return taskList.size();
}
// inflates row to create a viewHolder
#Override
public TaskViewHolder onCreateViewHolder(ViewGroup parent, int pos) {
View itemView = LayoutInflater.from(parent.getContext()).
inflate(R.layout.list_item, parent, false);
Task currTask = taskList.get(pos);
//itemView.setBackgroundColor(Color.parseColor(currTask.getColor()));
return new TaskViewHolder(itemView);
}
}
Deleting from my recyclerview gives unexpected results sometimes. Sometimes the element ahead of the one clicked is deleted, other times an indexOutOfBounds exception occurs at "taskList.get(position)".
Reading https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html and https://developer.android.com/training/material/lists-cards.html did not give me any more insight into why this was happening and how to fix it.
It looks like RecyclerView recycles the rows, but I wouldn't expect an indexoutofbounds exception using a smaller subset of numbers to index my list.
RecyclerView does not rebind views when their positions change (for obvious performance reasons).
For example, if your data set looks like this:
A B C D
and you add item X via
mItems.add(1, X);
notifyItemInserted(1, 1);
to get
A X B C D
RecyclerView will only bind X and run the animation.
There is a getPosition method in ViewHolder but that may not match adapter position if you call it in the middle of an animation.
If you need the adapter position, your safest option is getting the position from the Adapter.
update for your comment
Add a Task field to the ViewHolder.
Change onCreateViewHolder as follows to avoid creating a listener object on each rebind.
// inflates row to create a viewHolder
#Override
public TaskViewHolder onCreateViewHolder(ViewGroup parent, int type) {
View itemView = LayoutInflater.from(parent.getContext()).
inflate(R.layout.list_item, parent, false);
final TaskViewHolder vh = new TaskViewHolder(itemView);
taskViewHolder.closeBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// delete from SQLite DB
Task taskToDel = vh.getTask();
final int pos = taskList.indexOf(taskToDel);
if (pos == -1) return;
taskToDel.delete();
// updating UI
taskList.remove(pos);
thisAdapter.notifyItemRemoved(pos);
}
});
}
so in your on bind method, you do
// onBindViewHolder binds a model to a viewholder
#Override
public void onBindViewHolder(TaskViewHolder taskViewHolder, int pos) {
Task currTask = taskList.get(pos);
taskViewHolder.setTask(currTask);
taskViewHolder.taskTV.setText(currTask.getDescription());
}
Like yigit said, RecyclerView works like that:
A B C D
and you add item X via
mItems.add(1, X);
notifyItemInserted(1, 1);
you get
A X B C D
Using holder.getAdapterPosition() in onClickListener() will give you the right item from dataset to be removed, not the "static" view position. Here's the doc about it onBindViewHolder
Why dont you use a public interface for the button click and controle the action in the MainActivity.
In your adapter add:
public interface OnItemClickListener {
void onItemClick(View view, int position, List<Task> mTaskList);
}
and
public OnItemClickListener mItemClickListener;
// Provide a suitable constructor (depends on the kind of dataset)
public TaskAdapter (List<Task> myDataset, OnItemClickListener mItemClickListener) {
this.mItemClickListener = mItemClickListener;
this.mDataset = mDataset;
}
plus the call in the ViewHolder class
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public ViewHolder(View v) {
super(v);
...
closeBtn = (ImageView)v.findViewById(R.id.xImg);
closeBtn.setOnClickListener(this);
}
#Override
public void onClick(View v) {
// If not long clicked, pass last variable as false.
mItemClickListener.onItemClick(v, getAdapterPosition(), mDataset);
}
}
In your MainActivity change your adapter to handle the call
// set Adapter
mAdapter = new TaskAdapter(taskList, new TaskAdapter.OnItemClickListener() {
#Override
public void onItemClick(View v, int position) {
if (v.getId() == R.id.xImg) {
Task taskToDel = taskList.get(position);
// updating UI
taskList.remove(position);
thisAdapter.notifyItemRemoved(position);
// remove from db with unique id to use delete query
// dont use the position but something like taskToDel.getId()
taskToDel.delete();
}
}
});
Thanks to #yigit for his answer, his solution mainly worked, I just tweaked it a little bit so as to avoid using vh.getTask() which I was not sure how to implement.
final ViewHolder vh = new ViewHolder(customView);
final KittyAdapter final_copy_of_this = this;
// We attach a CheckChange Listener here instead of onBindViewHolder
// to avoid creating a listener object on each rebind
// Note Rebind is only called if animation must be called on view (for efficiency)
// It does not call on the removed if the last item is checked
vh.done.setChecked(false);
vh.done.setOnCheckedChangeListener(null);
vh.done.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
buttonView.setEnabled(false);
final int pos2 = vh.getAdapterPosition(); // THIS IS HOW TO GET THE UPDATED POSITION
// YOU MUST UPDATE THE DATABASE, removed by Title
DatabaseHandler db = new DatabaseHandler(mContext);
db.remove(mDataSet.get(pos2).getTitle(), fp);
db.close();
// Update UI
mDataSet.remove(pos2);
final_copy_of_this.notifyItemRemoved(pos2);
}
});
Notice instead to get the updated position, you can call vh.getAdapterPosition(), which is the line that will give you the updated position from the underlying dataset rather than the fake view.
This is working for me as of now, if someone knows of a drawback to using this please let me know. Hope this helps someone.
Personally, I don't like this concept of RecyclerViews. Seems like it wasn't thought of completely.
As it was said when removing an item the Recycler view just hides an item. But usually you don't want to leave that item in your collection. When deleting an item from the collection "it shifts its elements towards 0" whereas recyclerView keeps the same size.
If you are calling taskList.remove(position); your position must be evaluated again:
int position = recyclerView.getChildAdapterPosition(taskViewHolder.itemView);