I'm trying to build a simple list using RecyclerView, that would allow me to add/remove items.
I'm able to remove the items, but it seems like the position is not being 're-calculated' after I remove an item.
For example, I have 20 items in the list, if I remove the last item, the position is 19 (as it should be). The item gets removed from the list, but when I click to remove the last item again, the position is still 19, it should be 18:
D/RecycleViewTest: Remove at Position: 19
D/RecycleViewTest: Remove at Position: 19
Which results in the following exception:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.recycleviewtest, PID: 1763
java.lang.IndexOutOfBoundsException: Index: 19, Size: 19
at java.util.ArrayList.remove(ArrayList.java:477)
at com.example.recycleviewtest.MainActivity$1.onRemoveClick(MainActivity.java:45)
at com.example.recycleviewtest.SimpleViewHolder$2.onClick(SimpleViewHolder.java:42)
at android.view.View.performClick(View.java:6205)
at android.view.View$PerformClick.run(View.java:23653)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6682)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1410)
I'm removing the items the following way:
itemList.remove( position );
adapter.notifyItemRemoved( position );
Do I need to make additional call to some kind of function, so that the list gets "re-calculated"?
Here is the MainActivity:
public class MainActivity extends AppCompatActivity
{
List<SimpleViewModel> itemList;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
itemList = generateSimpleList();
final SimpleAdapter adapter = new SimpleAdapter(itemList);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.simple_recyclerview);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setItemAnimator( new DefaultItemAnimator() );
recyclerView.setAdapter(adapter);
adapter.setOnItemClickListener(new SimpleAdapter.onItemClickListener() {
#Override
public void onItemClick(int position)
{
Log.d( "RecycleViewTest", "Click at Position: " + position );
adapter.notifyItemChanged( position );
}
#Override
public void onRemoveClick( int position )
{
Log.d( "RecycleViewTest", "Remove at Position: " + position );
itemList.remove( position );
adapter.notifyItemRemoved( position );
}
});
}
private List<SimpleViewModel> generateSimpleList()
{
List<SimpleViewModel> simpleViewModelList = new ArrayList<>();
for (int i = 0; i < 20; i++) {
simpleViewModelList.add(new SimpleViewModel(String.format(Locale.US, "This is item %d", i)));
}
return simpleViewModelList;
}
}
This is my RecyclerView.Adapter (SimpleAdapter):
public class SimpleAdapter extends RecyclerView.Adapter {
private List<SimpleViewModel> models = new ArrayList<>();
private onItemClickListener mListener;
public SimpleAdapter(final List<SimpleViewModel> viewModels)
{
if (viewModels != null) {
this.models.addAll(viewModels);
}
}
public void setOnItemClickListener( onItemClickListener listener )
{
mListener = listener;
}
public interface onItemClickListener
{
void onItemClick( int position );
void onRemoveClick( int position);
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
return new SimpleViewHolder(view, mListener);
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
((SimpleViewHolder) holder).bindData(models.get(position));
}
#Override
public int getItemCount() {
return models.size();
}
#Override
public int getItemViewType(final int position) {
return R.layout.item;
}
}
Here is my RecyclerView.ViewHolder (SimpleViewHolder):
public class SimpleViewHolder extends RecyclerView.ViewHolder {
private TextView simpleTextView;
private ImageView imgRemove;
public SimpleViewHolder(final View itemView, final SimpleAdapter.onItemClickListener listener) {
super(itemView);
simpleTextView = (TextView) itemView.findViewById(R.id.simple_text);
imgRemove = (ImageView) itemView.findViewById(R.id.imgRemove);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if( listener != null )
{
int position = getAdapterPosition();
if( position != RecyclerView.NO_POSITION )
{
listener.onItemClick( position );
}
}
}
});
imgRemove.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if( listener != null )
{
int position = getAdapterPosition();
if( position != RecyclerView.NO_POSITION )
{
listener.onRemoveClick( position );
}
}
}
});
}
public void bindData(final SimpleViewModel viewModel) {
simpleTextView.setText(viewModel.getSimpleText() );
}
}
Any hints please on where am I going wrong with this? Thank you!
UPDATE:
I just realized that even though the animation for removal is 'played', no actual items are removed, they simply 're-appear'..
The problem is in your Adapter's constructor:
private List<SimpleViewModel> models = new ArrayList<>();
private onItemClickListener mListener;
public SimpleAdapter(final List<SimpleViewModel> viewModels)
{
if (viewModels != null) {
this.models.addAll(viewModels);
}
}
The list you're passing into this constructor isn't retained by the adapter. Instead, the contents of the list are copied into the adapter's own, separate and different list.
That means that later on, when you execute
#Override
public void onRemoveClick( int position )
{
Log.d( "RecycleViewTest", "Remove at Position: " + position );
itemList.remove( position );
adapter.notifyItemRemoved( position );
}
The item is removed from itemList, but it is not removed from the list in the adapter.
You must either remove the item from the adapter's copied list, or you must change the adapter so that it shares the same list as the activity. Probably the easiest thing would be to change the adapter:
private List<SimpleViewModel> models = new ArrayList<>();
private onItemClickListener mListener;
public SimpleAdapter(final List<SimpleViewModel> viewModels)
{
if (viewModels != null) {
this.models = viewModels; // instead of copying the contents
}
}
Use this after remove the item
mRecyclerView.getRecycledViewPool().clear();
mAdapter.notifyDataSetChanged();
After digging around, it looks like other people have been using an additional method called notifyItemRangeChanged which sounds like what is going on. So after doing itemList.remove(position);
try also doing
adapter.notifyItemRemoved( position );
adapter.notifyItemRangeChanged(position, itemList.size());
The getAdapterPosition() return a range positions of 1 - n and your list has a range positions of 0 - n.
Try as follow
itemList.remove( position - 1 ); //to normalize
adapter.notifyItemRemoved( position );
Related
In the activity when user click on add client button I want to add new view to the screen which contains a spinner with list of client names retrieved from api and a button that will do some action on click.
So I thought I would use a recycleview and adapter for this but I think I'm wrong
in the activity I have the adapter
private ClientAdapter clientAdapter;
When I retrieve clients name from API I set the adapter as
clientRecyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
clientAdapter= new clientAdapter(clientList , this , this);
clientRecyclerView.setAdapter(podAdapter);
At this point I don't want the recycle view to render anything until user click on add new client button then I want to display one item that has spinner with client names and a button.
Then if he clicks again on add client button I want to show another spinner and button and so on.
However now I'm having 3 clients so recycleview render 3 view items which make sense.
But what the trick that I should do to achieve my goal?
Here's my adapter
public class ClintsAdapter extends RecyclerView.Adapter<ClintsAdapter.ViewHolder> {
private List<Clients> clientsList;
private EventListener listener;
public ClintsAdapter(List<Clients> clientsList, EventListener listener , Context context) {
this.clientsList = clientsList;
this.EventListener = listener;
}
#NonNull
#Override // To inflate view
public ClintsAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.listitem_client, parent, false);
ViewHolder viewHolder = new ViewHolder(view, listener);
return viewHolder;
}
#Override
public void onBindViewHolder(#NonNull ClintsAdapter.ViewHolder holder, int position) {
ClintsAdapter = new ArrayAdapter<Client>(context, R.layout.spinner_text_view, clientsList);
ClintsAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
holder.clientSpinner.setAdapter(ClintsAdapter);
holder.clientSpinner.setTitle("Choose client");
}
#Override
public int getItemCount() {
if (clientsList == null)
return 0;
return clientsList.size();
}
public interface PODListener {
void onClick(int position);
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private SearchableSpinner clientSpinner , collectMethodSpinner;
EventListener listener;
public ViewHolder(View itemView, final EventListener listener) {
super(itemView);
this.listener = podListener;
clientSpinner = itemView.findViewById(R.id.spinner_client);
btnComment = itemView.findViewById(R.id.btn_comment);
btnComment.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if ( listener != null ) {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION){
listener.onClick(position);
}
}
}
});
}
#Override
public void onClick(View v) {
}
}
}
and here's my list item
From comments:
The problem is I'm passing list of clients to the adapter (size of 3) then the adapter render 3 items. I don't want this behavior I want to have 0 item if user click on add I will render one item and so on
You are using a single ArrayList<Client> for two different purposes:
The list of clients to choose from in the spinner
The number of spinners to display in the RecyclerView.
These are two separate things, so you need two separate lists.
You can do that with just adding integer value for your ClientsAdapter. Set its default value as 0 and create a method for changing it's value. When you want to add new item (new Spinner and Button) use that method and notify your adapter.
Add a new field called count for your ClientsAdapter.
private int count;
Inside constructor assign its value to 0. So on start its value will be 0 and RecyclerView will show nothing.
public ClintsAdapter(List<Clients> clientsList, EventListener listener , Context context){
this.clientsList = clientsList;
this.EventListener = listener;
count = 0;
}
Change getItemCount method's return value. According to your code getItemCount returns size of your List. That List is for Spinner and has no relation with this method. Instead of returning your List's size return count.
#Override
public int getItemCount() {
return count;
}
Create a method for changing count's value. count starts with 0 (assigned it 0 inside constructor) and when you click Button (add new Spinner and Button) this method will change its value.
public void addItem(int count) {
this.count = count;
}
Whenever you click Button simply call addItem method and pass new count value and notify your clientAdapter.
addClient.setOnClickListener(v -> {
int count = clientRecyclerView.getChildCount();
clientAdapter.addItem(count+1);
clientAdapter.notifyItemInserted(count);
});
NOTE: I don't get it why you're setting podAdapter for RecyclerView.
clientRecyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
clientAdapter= new clientAdapter(clientList , this , this);
clientRecyclerView.setAdapter(podAdapter);
You're creating clientAdapter reference for your ClientsAdapter but while setting adapter for RecyclerView, you're using different reference (podAdapter).
Full code for ClientsAdapter:
public class ClintsAdapter extends RecyclerView.Adapter<ClintsAdapter.ViewHolder> {
private List<Clients> clientsList;
private EventListener listener;
private int count;
public ClintsAdapter(List<Clients> clientsList, EventListener listener , Context context) {
this.clientsList = clientsList;
this.EventListener = listener;
count = 0;
}
#NonNull
#Override // To inflate view
public ClintsAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.listitem_client, parent, false);
ViewHolder viewHolder = new ViewHolder(view, listener);
return viewHolder;
}
#Override
public void onBindViewHolder(#NonNull ClintsAdapter.ViewHolder holder, int position) {
ClintsAdapter = new ArrayAdapter<Client>(context, R.layout.spinner_text_view, clientsList);
ClintsAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
holder.clientSpinner.setAdapter(ClintsAdapter);
holder.clientSpinner.setTitle("Choose client");
}
public void addItem(int count) {
this.count = count;
}
#Override
public int getItemCount() {
return count;
}
public interface PODListener {
void onClick(int position);
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private SearchableSpinner clientSpinner , collectMethodSpinner;
EventListener listener;
public ViewHolder(View itemView, final EventListener listener) {
super(itemView);
this.listener = podListener;
clientSpinner = itemView.findViewById(R.id.spinner_client);
btnComment = itemView.findViewById(R.id.btn_comment);
btnComment.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if ( listener != null ) {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION){
listener.onClick(position);
}
}
}
});
}
#Override
public void onClick(View v) {
}
}
}
Inside my RecyclerView Adapter class I have 2 view types to display the results of my query:
#Query("SELECT l.log_id, l.junction_id ,l.date, l.workout_id, l.total_weight_lifted,
l.reps, l.set_number FROM log_entries_table
AS l LEFT JOIN exercise_workout_junction_table AS ej
ON ej.exercise_workout_id = l.junction_id WHERE ej.exercise_id = :exerciseID
ORDER BY substr(l.date, -4) DESC, substr(l.date, -7) DESC, (l.date) DESC")
LiveData<List<Log_Entries>> getAllExerciseHistoryLogs(int exerciseID);
The first view type is used to display all logEntries in which the date is unique:
The second view type is to display the rest of the logEntries which share the same date as the above:
My current code works fine, however every time I scroll down and the recyclerView updates, all the log-Entries with 'unique' dates (which should use the first viewType) get changed to display the second view type.
How can I stop my recyclerView view type from changing?
Before scroll -> After Scroll
RecyclerView Adapter
public class ExerciseHistoryAdapter2 extends RecyclerView.Adapter {
private OnItemClickListener listener;
private List<Log_Entries> allLogEntries = new ArrayList<>();
private List<String> uniqueDates = new ArrayList<>();
String logEntryDate;
public void setExercises(List<Log_Entries> allLogEntries) {
this.allLogEntries = allLogEntries;
notifyDataSetChanged();
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view;
if (viewType == 0) {
view = layoutInflater.inflate(R.layout.exercise_history_item, parent, false);
return new ViewHolderOne(view);
}
view = layoutInflater.inflate(R.layout.exercise_history_item_two, parent, false);
return new ViewHolderTwo(view);
}
#Override
public long getItemId(int position) {
return allLogEntries.get(position).getLog_id();
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
logEntryDate = allLogEntries.get(position).getDate();
if (uniqueDates.contains(logEntryDate)) {
// bindViewHolder2
ViewHolderTwo viewHolderTwo = (ViewHolderTwo) holder;
viewHolderTwo.textViewWeight.setText(String.valueOf(allLogEntries.get(position).getTotal_weight_lifted()));
viewHolderTwo.textViewReps.setText(String.valueOf(allLogEntries.get(position).getReps()));
} else {
uniqueDates.add(logEntryDate);
//bind viewholder1
ViewHolderOne viewHolderOne = (ViewHolderOne) holder;
viewHolderOne.textViewDate.setText(allLogEntries.get(position).getDate());
viewHolderOne.textViewWeight.setText(String.valueOf(allLogEntries.get(position).getTotal_weight_lifted()));
viewHolderOne.textViewReps.setText(String.valueOf(allLogEntries.get(position).getReps()));
}
}
#Override
public int getItemCount() {
return allLogEntries.size();
}
#Override
public int getItemViewType(int position) {
logEntryDate = allLogEntries.get(position).getDate();
if (uniqueDates.contains(logEntryDate)) {
return 1;
}
return 0;
}
class ViewHolderOne extends RecyclerView.ViewHolder {
private TextView textViewDate;
private TextView textViewWeight;
private TextView textViewReps;
public ViewHolderOne(#NonNull View itemView) {
super(itemView);
textViewDate = itemView.findViewById(R.id.textView_dateH);
textViewWeight = itemView.findViewById(R.id.textView_weightH);
textViewReps = itemView.findViewById(R.id.textView_repss);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = getAdapterPosition();
if (listener != null && position != RecyclerView.NO_POSITION) {
listener.onItemClick(allLogEntries.get(position));
}
}
});
}
}
class ViewHolderTwo extends RecyclerView.ViewHolder {
private TextView textViewWeight;
private TextView textViewReps;
public ViewHolderTwo(#NonNull View itemView) {
super(itemView);
textViewWeight = itemView.findViewById(R.id.textView_weightH2);
textViewReps = itemView.findViewById(R.id.textView_repss2);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = getAdapterPosition();
if (listener != null && position != RecyclerView.NO_POSITION) {
listener.onItemClick(allLogEntries.get(position));
}
}
});
}
}
public interface OnItemClickListener {
void onItemClick(Log_Entries log_entries);
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
}
Your getItemViewType and onBindViewHolder has some issues.
#Override
public int getItemViewType(int position) {
// type 0 = with date header
// type 1 = without date header
// if list is sorted chronologically
if (position == 0) {
return 0
}
String currentDate = allLogEntries.get(position).getDate();
String previousDate = allLogEntries.get(position - 1).getDate();
if (currentDate.equals(previousDate)) {
return 1
} else {
return 0
}
}
The first item will always have the header since the list is sorted chronologically. For the rest of the items, you need to check whether the date for the current item is the same as the previous item. Based on that condition you return the type.
You do not need to manage a list of unique dates. You have shared mutable states between the functions that are being called multiple times and are not synced. Just delete these and the references of them from onBindViewHolder
private List<String> uniqueDates = new ArrayList<>();
String logEntryDate;
I think to set holder.setIsRecyclable(false); would solve the issue, because the recycler view will then no longer recycle the items... But this is not a good solution for long lists.
EDIT:
I reviewd your code in onBindViewHolder()...
I think the problem comes with uniqueDates.add(logEntryDate); and that the onBindViewHolder method is called multiple times.
This is how the recycler view proceeds:
the first item in list will be unique because uniqueDates is empty. Therefore it will be added to the list.
the other items will be added correctly, as you see in your first screenshot
when you scroll down, the onBindViewHolder method will be executed for every item again
because the uniqueDates list already contains the first date, as it was added in step one, this item will now recognized as not-unique one
the wrong list will be displayed after scrolling as you see in your second screenshot
SOLUTION:
You will have to add a logic which identifies unique dates in another way, which is independet of the onBindViewholder method
OR
you would have to add code, that removes dates on a specific point, so that the list identifies the first item every time as unique and not just the first time.
I'm new in android and I have just screwed something.
I've used recycleView template and it worked fine but I wanted to change type of my object from string to "TagsManagerObject" which contains
private String tagName;
private String gender;
private String mAgeMin;
private String mAgeMax;
private String mDistance;
since then I'm stuck with this error:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.tinderapp, PID: 10921
java.lang.NullPointerException: Attempt to invoke interface method 'void com.example.tinderapp.Tags.TagsManagerAdapter$ItemClickListener.onDeleteClick(int)' on a null object reference
at com.example.tinderapp.Tags.TagsManagerAdapter$ViewHolder$1.onClick(TagsManagerAdapter.java:78)
at android.view.View.performClick(View.java:6614)
at android.view.View.performClickInternal(View.java:6587)
at android.view.View.access$3100(View.java:787)
at android.view.View$PerformClick.run(View.java:26122)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:201)
at android.app.ActivityThread.main(ActivityThread.java:6831)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:927)
My adapter looks like:
public class TagsManagerAdapter extends RecyclerView.Adapter<TagsManagerAdapter.ViewHolder>{
private LayoutInflater mInflater;
private ImageView mDeleteImage;
private List<TagsManagerObject> mTagsManagerObject;
private ItemClickListener mItemClickListener;
// data is passed into the constructor
public TagsManagerAdapter(Context context,List<TagsManagerObject> TagsManagerObject) {
this.mInflater = LayoutInflater.from(context);
this.mTagsManagerObject = TagsManagerObject;
}
// inflates the row layout from xml when needed
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.item_tags_manager, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
// String tag = TagsManagerObject.get(position);
holder.tagName.setText("#"+mTagsManagerObject.get(position).getTagName());
holder.gender.setText(mTagsManagerObject.get(position).getGender());
holder.distance.setText(mTagsManagerObject.get(position).getmDistance());
holder.tagAge.setText(mTagsManagerObject.get(position).getmAgeMin() + "-" + mTagsManagerObject.get(position).getmAgeMax());
}
// binds the data to the TextView in each row
// total number of rows
#Override
public int getItemCount() {
return mTagsManagerObject.size();
}
public void setClickListener(ItemClickListener mItemClickListener) {
this.mItemClickListener = mItemClickListener;
}
// stores and recycles views as they are scrolled off screen
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private TextView tagName,gender,tagAge,distance;
ViewHolder(View itemView) {
super(itemView);
tagName= itemView.findViewById(R.id.tag);
gender = itemView.findViewById(R.id.tag_gender);
distance = itemView.findViewById(R.id.tag_distance);
tagAge = itemView.findViewById(R.id.tag_age);
mDeleteImage = itemView.findViewById(R.id.tag_delete);
itemView.setOnClickListener(this);
mDeleteImage.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = getAdapterPosition();
if(position!=RecyclerView.NO_POSITION){
mItemClickListener.onDeleteClick(position); <<--- HERE IS THE ERROR
}
}
});
}
#Override
public void onClick(View view) {
if (mItemClickListener != null) mItemClickListener.onItemClick(view, getAdapterPosition());
}
}
// convenience method for getting data at click position
String getItem(int id) {
return mTagsManagerObject.get(id).toString();
}
// allows clicks events to be caught
// parent activity will implement this method to respond to click events
public interface ItemClickListener {
void onItemClick(View view, int position);
void onDeleteClick(int position);
}
}
So when i click delete button it crashes. Does anyone knows why?
Thanks.
You got this exception as you you call onDeleteClick(position) on an object that is null, so you have to set a value to the mItemClickListener before calling this method onDeleteClick(position)
In your code, you have to call setClickListener() of your adapter in your activity/fragment that uses this adapter.
You need to do two things.
First, you need to check mItemClickListener for null. Doing this will prevent from null pointer exception.
mDeleteImage.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = getAdapterPosition();
if(position!=RecyclerView.NO_POSITION){
if(mItemClickListener !=null){
mItemClickListener.onDeleteClick(position); <<--- HERE IS THE ERROR
}
}
}
}
);
And the second thing is you should set/call the setClickListener for the Activity/Fragment, using the instance of RecyclerviewAdapter.
Doing this will make the Activity/Fragment implement your ItemClickListener and onDeleteClick() method.
adapter.setClickListener(this);
Or, you can also use the Anonymous class here.
Thanks guys for help. The problem was that in my acvitivy I have:
1.Created adapter with empty taglist
2.set onclicklistener
adapter.setOnItemClickListener(new TagsManagerAdapter.OnItemClickListener() {
#Override
public void onItemClick(int position) {
}
#Override
public void onDeleteClick(int position) {
System.out.println("works?");
removeItem(position);
}
});
And then add items.
Instead
adapter.notifyDataSetChanged();
Ive used:
adapter = new TagsManagerAdapter(myTagsList);
and that caused problem, that it coudnt find my listener
How to hide an item from other spinners that is currently selected in one spinner?
I've tried removing the items via ArrayList of strings and ArrayAdapters, but I've noticed as soon as it's removed from the list, the selection is no longer referenced to the list item (because it does not exist anymore).
Now suppose, I have 4 spinner that are created dynamically and they all have the same ArrayList as their resource and now i would like to use this adapter to fetch the position of the selected item from 1 spinner and then hide it from 3 other spinners.
for (int i = 0; i < numberOfStops; i++) {
AddStopView stopView = new AddStopView(getActivity());
stopView.setCallback(BaseBookingFragment.this);
stopView.setPassengerNames(extraPassengerNames);
stopViews.add(stopView);
parent.addView(stopView, viewPosition);
}
In above code i am creating Stop Views dynamically and each Stop View having Passenger Name spinner. And these all spinners have the same ArrayList as their resource.
piece of code from AddStopView.java
public AddStopView(Context context) {
super(context);
initialize();
}
public void setCallback(StopViewCallback callback) {
this.callback = callback;
}
public void setPassengerNames(List<String> passengerNames) {
this.passengerNames = passengerNames;
passengerAdapter.setNames(passengerNames);
}
private void initialize() {
inflate(getContext(), R.layout.view_stop, this);
passengerAdapter = new ExtraPassengerAdapter(getContext());
passengerAdapter.setNames(passengerNames);
nameSpinner.setAdapter(passengerAdapter);
nameSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (view == null) {
return;
}
passengerName = (String) view.getTag();
if (position != 0)
callback.updatePassengerList(AddStopView.this, (position - 1));
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
code of call back of nameSpinner.setOnItemSelectedListener
#Override
public void updatePassengerList(AddStopView addStopView, int position) {
for (String passName : extraPassengerNames) {
if (addStopView.getPassengerName().equals(passName)) {
extraPassengerNames.remove(passName);
break;
}
}
for (AddStopView stopView : stopViews) {
if (!stopView.equals(addStopView))
stopView.setPassengerNames(extraPassengerNames);
}
}
code from ExtraPassengerAdapter.java
public class ExtraPassengerAdapter extends BaseAdapter {
private List<String> names = new ArrayList<>();
private Context context;
public ExtraPassengerAdapter(Context context) {
this.context = context;
names.add(get0Position());
}
public void setNames(List<String> names) {
this.names.clear();
this.names.add(get0Position());
this.names.addAll(names);
notifyDataSetChanged();
}
#Override
public int getCount() {
return names.size();
}
#Override
public String getItem(int position) {
return names.get(position);
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView = (TextView) LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_stop, parent, false);
String name = getItem(position);
textView.setText(name);
textView.setTag(name);
return textView;
}
private String get0Position() {
return context.getString(R.string.passenger_name);
}
}
I think the right way to solve you problem is to create one list with all possible options and 4 lists with 4 adapters for each spinner. When something selected you update each list according to logic you described and call adapter.notifyDataSetChanged() for each adapter.
Basically, I'm trying to programatically find out how many items are going to fit in a RecyclerView (and be visible to user, of course) in order to determine how many of them to fetch from cache.
I'm using a LinearLayoutManager.
Also, I'm aware of the LinearLayoutManager method findLastVisibleItemPosition, but obviously it's useless in this case since we're talking on before-initialization time, not after (so it returns -1).
Tried reading the docs or thinking on a creative but efficient idea, but I got nothing on my mind.
Any ideas?
This sounds actually pretty interesting, but only works if your height (vertical scroll) or width (horizontal scroll) is fixed, meaning no wrap_content.
No sample code and nothing tested here:
Create an Adapter with a setter for getCount that gets returned in case your data-source is null/empty
in getCount return at least 1 if your data is empty/null
make sure onBindViewHolder() can handle empty/non-existent data
add a OnChildAttachStateChangeListener to your RecyclerView, everytime the listener gets called, use the view to view.post(new Runnable() {...increase adapters getCount...adapter.notifyItemInserted()} (that runnable is necessary to avoid crash+burn)
OnChildAttachStateChangeListener gets called again >>> compare getCount and findLastVisibleItemPosition. If getCount > findLastVisibleItemPosition + 1 remove that listener. The number of fixed-size views fitting into ListView is findLastVisibleItemPosition + 1
Get your data and set it into you adapter, call notifyDataSetChanged
make sure getCount returns the data-source length from now on.
you could hide the listview behind a loadingscreen, or you can set the child views to invisible in onBindViewHolder
EDIT:
Create an Adapter which returns a ridiculous high count when no data is set and make sure it handles missing data correctly in onBindViewHolder
Extend LinearLayoutManager and Override onLayoutChildren() after the super call if getItemCount() > getChildCount() getChildCount() is the number of Views that would be visible in your RecyclerView
MainActivity.class
public class MainActivity extends AppCompatActivity {
private PreCountingAdapter mAdapter;
private RecyclerView mRecyclerView;
private PreCountLinearLayoutManager mPreCountLayoutManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mPreCountLayoutManager = new PreCountLinearLayoutManager(this,
LinearLayoutManager.VERTICAL, false);
mPreCountLayoutManager.setListener(new PreCountLinearLayoutManager.OnPreCountedListener() {
#Override
public void onPreCounted(int count) {
mPreCountLayoutManager.setListener(null);
loadData(count);
}
});
mRecyclerView.setLayoutManager(mPreCountLayoutManager);
mAdapter = new PreCountingAdapter();
mRecyclerView.setAdapter(mAdapter);
}
private void loadData(final int visibleItemCount) {
// load data here, probably asynchronously,
// for simplicity just an String Array with size visibleItemCount
final List<String> data = new ArrayList<>();
for (int i = 0; i < visibleItemCount; i++) {
data.add(String.format("child number #%d", i));
}
mRecyclerView.post(new Runnable() {
#Override
public void run() {
mAdapter.swapData(data);
}
});
}
#Override
protected void onDestroy() {
mPreCountLayoutManager.setListener(null);
super.onDestroy();
}
}
PreCountLinearLayoutManager.class
public class PreCountLinearLayoutManager extends LinearLayoutManager {
private OnPreCountedListener mListener;
public interface OnPreCountedListener {
void onPreCounted(int count);
}
public PreCountLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
#Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
super.onLayoutChildren(recycler, state);
if (getItemCount() > getChildCount()) {
if (mListener != null) {
mListener.onPreCounted(getChildCount());
}
}
}
public void setListener(OnPreCountedListener listener) {
mListener = listener;
}
}
PreCountingAdapter.class
public class PreCountingAdapter extends RecyclerView.Adapter<PreCountingAdapter.ViewHolder> {
private List<String> mData;
public void swapData(List<String> data) {
mData = data;
notifyDataSetChanged();
}
public class ViewHolder extends RecyclerView.ViewHolder {
View mItemView;
TextView mTextView;
public ViewHolder(View itemView) {
super(itemView);
mTextView = (TextView) itemView.findViewById(R.id.text_view);
mItemView = itemView;
}
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
return new ViewHolder(inflater.inflate(R.layout.recycler_child, parent, false));
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (mData == null) {
// we are in precounting stage
holder.mItemView.setVisibility(View.INVISIBLE);
} else {
String item = mData.get(position);
holder.mItemView.setVisibility(View.VISIBLE);
holder.mTextView.setText(item);
}
}
#Override
public int getItemCount() {
return mData == null ? Integer.MAX_VALUE : mData.size();
}
}