Spinner Displayed Value (Selection) Changing After Adapter Update (notifyDataSetChanged) - android

I have a Spinner. After an item is selected, and the adapter is updated by notifyDataSetChanged, the selected item displayed in the Spinner (Textview) changes. This functionality is by design. Because the selected position stayed the same but the value of the selected position changed due to the new content from updating the adapter.
I want to continue to display the originally selected item after the adapter updates.
I was hoping this solution would work, but the event/lister never fires. Maybe because I am using a Spinner and the list is not dispalyed?
https://stackoverflow.com/a/29173680/2330272
mListView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
mListView.removeOnLayoutChangeListener(this);
Log.e(TAG, "updated");
}
});
mAdapter.notifyDataSetChanged();
Update
Help with at least two questions:
How to determine when the Spinner has finished updating the view after the notifyDataSetChanged has been called for the adpater?
How to find the position (index) of a value (label) in the adapter? As the adapter be querued or elements loop thru?

If you want to maintain the current selected item, as opposed to the current selected position, then you will have to implement the adapter's getItemId(int position) callback such that:
Each item in the spinner has a unique id
The same item always has the same id
Common implementations of getItemId() just return zero, or maybe the item's position. Neither of these will work in this situation. Ideally you'll have an actual logical ID that you can return, but if the only thing you have is a String then you could use the string's hashCode() method:
#Override
public String getItem(int position) {
return list.get(position);
}
#Override
public long getItemId(int position) {
return list.get(position).hashCode();
}
Two different strings are unlikely to have the same hash code, but it's not impossible.

Related

Load the first item after the first item in recyclerview

I need to implement the recyclerview with functionality as to display the first item after last like circle
Suggest you to try following changes in your adapter class. Set getItemCount to return a very large value like Integer.MAX_VALUE
#Override
public int getItemCount() {
return Integer.MAX_VALUE;
}
Then to get the actual item position need a modulus operation
#Override
public void onBindViewHolder(ActionItemViewHolder holder, int position) {
position = position % SIZE_OF_LIST;
};

Get spinner selected item inside RecyclerView

I have added Spinner inside RecyclerView , when i am trying to get spinner selected item data, its getting another/wrong position data, any one suggest me to get correct selected item and position from Spinner onItemSelected
Here is my code
#Override
public void onBindViewHolder(final QuestionHolder holder, final int position) {
if (position % 2 == 1)
holder.itemView.setBackgroundColor(Color.parseColor("#F8F8F8"));
adapter = new ArrayAdapter<Option>(binding.getRoot().getContext(),
R.layout.item_spinner, questionList.get(position).getOptions());
adapter.setDropDownViewResource(R.layout.item_spinner);
binding.optionSpinner.setAdapter(adapter);
binding.serialNo.setText((position + 1) + ".");
binding.setQuestion(questionList.get(position));
binding.executePendingBindings();
binding.optionSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(holder.itemView.getContext(), position+" : "+binding.optionSpinner.getSelectedItem().toString(), Toast.LENGTH_SHORT).show();
spinnerData.setSelectedData(position, binding.optionSpinner.getSelectedItem().toString());
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
I think you have to try this
showSpinnerList.setOnItemSelectedListener(new
AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int
position, long id) {
// On selecting a spinner item
String item = parent.getItemAtPosition(position).toString();
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
// todo for nothing selected
}
});
Check this, can be helpful and may fix your problem. If this won't fix your problem at least you get rid of Lint error. Lint error “Do not treat position as fixed; only use immediately…”. So everywhere you are using final int position** change it to getAdapterPosition();
The documentation of RecyclerView.Adapter.onBindViewHolder()
states:
Note that unlike ListView, RecyclerView will not call this method
again if the position of the item changes in the data set unless the
item itself is invalidated or the new position cannot be determined.
For this reason, you should only use the position parameter while
acquiring the related data item inside this method and should not keep
a copy of it. If you need the position of an item later on (e.g. in a
click listener), use getAdapterPosition() which will have the updated
adapter position
So, technically items may be re-arranged and binding will not be
necessary because items are not invalidated yet. The position
variable received holds true only for the scope of bind function and
will not always point to correct position in the data set. That's why
the function getAdapterPosition() must be called everytime updated
position is needed.
IMHO, mLastPosition = holder.getAdapterPosition(); is still
potentially wrong. Because item may be re-arranged and mLastPosition
still points to old position.
About why lint is silent, perhaps Lint rule is not that thorough. Its
only checking whether position parameter is being copied or not.

Added element doesn't show in RecyclerView after deleting another with swipe

Edit: if anyone is having the same problem in the future it was a pretty easy fix. I used clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) to handle when the entire drag was finished and had forgotten to call it's super-method. That's why it didn't update properly.
Original question:
Here's the entire code: https://github.com/vustav/Ppaaiinntt/tree/master/app/src/main/java/com/decent/rvtest
Everything works fine except when I add an element right after deleting another. It does exist though. If I add another there's a properly sized space between the old list and the new element. I use a string where they add their names before a print and it shows there, and if I drag to change positions of the elements it shows up properly.
My reputation doesn't allow me to post images so an imgur-album will have to do:
http://imgur.com/a/bmb17
On the first image there's three elements and the string is printed at the bottom.
The second image is after the swipe. Notice the string is updated.
The third is after adding another "111". The string is correct but it doesn't show up in the view.
The fourth is after adding another. The string is still correct and the new element shows up in the view.
The last image is after dragging to change the position of the last two elements. Now everything is fine again.
These are the relevant methods (I think):
protected void add(PictureElement pe){
chain.add(pe);
notifyItemInserted(chain.size()-1);
}
public void remove(int position) {
chain.remove(position);
notifyItemRemoved(position);
}
protected void swap(int from, int to){
chain.swap(from, to);
notifyItemMoved(from, to);
}
Edit: onBindViewHolder, getItemCount and the ViewHolder:
#Override
public int getItemCount() {
return chain.size();
}
#Override
public void onBindViewHolder(PEViewHolder PEViewHolder, int i) {
PictureElement pe = chain.get(i);
PEViewHolder.name.setText(pe.getName());
}
protected static class PEViewHolder extends RecyclerView.ViewHolder {
protected TextView name;
public PEViewHolder(View v) {
super(v);
name = (TextView) v.findViewById(R.id.txtName);
}
}
Interesting quote on the different notify methods you are using
public final void notifyItemRemoved (int position) Notify any registered observers that the item previously located at position has
been removed from the data set. The items previously located at and
after position may now be found at oldPosition - 1. This is a
structural change event. Representations of other existing items in
the data set are still considered up to date and will not be rebound,
though their positions may be altered. Parameters position : Position
of the item that has now been removed.
public final void notifyItemRangeChanged (int positionStart, int itemCount) Notify any registered observers that the itemCount items
starting at position positionStart have changed. Equivalent to calling
notifyItemRangeChanged(position, itemCount, null);. This is an item
change event, not a structural change event. It indicates that any
reflection of the data in the given position range is out of date and
should be updated. The items in the given range retain the same
identity.
Could you try and comment this block of code and check if it works
//Called by the ItemTouchHelper when the user interaction with an element is over and it also
// completed its animation
/*
#Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
//update from where the action took place
mPEAdapter.updateChain(viewHolder.getLayoutPosition());
//clearView is called after onMove so any drags or swipes are complete
dragging = false;
mPEAdapter.setSwipe(false);
}
*/

Get visible position of listview item

I have a custom cursor adapter to populate a list view in my fragment.
I want to set the visibility of certain elements within the list view items, depending on whether the item is the first visible one in the list or not. Is it possible to get that info in the bindView() method of the Cursor adapter?
Adapter's purpose plan:
Create views
Attaching data to them
Return the view for the ListView to use.
Conclusion: Adapter doesn't know where the view it's creating will be shown.
However, the ListView does know about this and it's probably the only way you can get this working.
Example code to get you started:
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
int previousFirst = -1;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (previousFirst != firstVisibleItem) {
previousFirst = firstVisibleItem;
TextView textView = (TextView) view.findViewById(R.id.title);
if (textView != null) textView.setText("First: " + firstVisibleItem);
}
}
});
Problems with this code:
Once the first item changes, you need to set it's text back to the previous one.
Same goes with the view hierarchy. If you change how this view looks, after it's not the first one anymore you need to change it back.
ListView doesn't scroll upon creation. So the first item will not have the text changed until you scroll manually.
ListView doesn't include the options to customize the first visible item internally, that's why you have to use all these dirty hacks. However, it is possible :). I leave you to overcome these problems yourself.
Good luck!

StickyGridHeaders custom adapter

I'm trying to implement my own StickyGridHeadersBaseAdapter, my current source code here - http://paste.org.ru/?11jrjh, and I use it like
ModeAdapter adapter = new ModeAdapter(this);
modeGridView.setAdapter(adapter);
Problems which I have is that
1) I have no idea how to call notifyDataSetChanged() for this adapter, so I can't change items
2) And implementation of AdapterView.OnItemClickListener (http://paste.org.ru/?mvgt7b) works strange
Mode mode = (Mode) adapter.getItem(position);
returns null for items with 1st and 2nd positions, item on 3rd position is actual 1st item in adapter.
Where is my fault here?
One more question is why I can't cast adapterView.getAdapter() in my OnItemClickListener to my ModeAdapter class. What if I want to call notifyDataSetChanged() here?
I didn't find any examples for custom implementation of StickyGridHeadersBaseAdapter here.
Thanks in advance.
I had the same issue as you 2):
after the first header i got the item of the previous row, after the second header got the item of two rows up, etc...
The reason is the following:
StickyHeadersGridView:
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mOnItemClickListener.onItemClick(parent, view, mAdapter.translatePosition(position).mPosition, id);
}
The position is corrected. so in your onItemClick you get the corrected value of position.
If than you request the item with: Mode mode = (Mode) adapter.getItem(position);
you get StickyGridHeadersBaseAdapterWrapper.getItem(int pos)
#Override
public Object getItem(int position) throws ArrayIndexOutOfBoundsException {
Position adapterPosition = translatePosition(position);
if (adapterPosition.mPosition == POSITION_FILLER || adapterPosition.mPosition == POSITION_HEADER) {
// Fake entry in view.
return null;
}
return mDelegate.getItem(adapterPosition.mPosition);
}
In StickyGridHeadersBaseAdapterWrapper.getItem() position gets corrected for the second time which causes the wrong item to be returned...
I added a work-around:
In StickyHeadersBaseAdapterWrapper I added:
public Object getItemAtDelegatePosition(int pos) {
return mDelegate.getItem(pos);
}
And I use this in onItemClick:
Mode item = (Mode) ((StickyGridHeadersBaseAdapterWrapper)parent.getAdapter()).getItemAtDelegatePosition(position);
An easier way to get an item would be:
StickyGridHeadersBaseAdapterWrapper wrapper = (StickyGridHeadersBaseAdapterWrapper) parent.getAdapter();
Mode item = (Mode ) wrapper.getWrappedAdapter().getItem(position);

Categories

Resources