I have a Runnable class that calculates model values for my composite list views, in that runnable there's a UI thread inside of a custom thread. There I have adapter's notifyDataSetChanged() call, but after notifyDataSetChanged() I try updating some TextView value in the main layout. The problem is when running TextView gets updated first and only then ListViews and getting updated. That means notifyDataSetChanged() of the Adapter custom class gets updated last which is not suitable for me. Is there any possibility to synchronize those screen updates?
Here's the sample code:
public class TestRunnable implements Runnable {
private TestAdapter adapter;
#Override
public void run() {
Context.runOnUiThread(new Runnable() {
#Override
public void run() {
adapter.notifyDataSetChanged();
MainActivity.setTextViewValue("Something...");
}
});
}
}
public class TestAdapter extends ArrayAdapter<TestModel> {
#Override
public View getView(final int position, View view, ViewGroup parent) {
View row = view;
TestHolder holder;
Boolean rowIsNew = false;
if (row == null) {
rowIsNew = true;
LayoutInflater layoutInflater = ((Activity) context)
.getLayoutInflater();
row = layoutInflater.inflate(layoutResourceId, parent, false);
holder = new TestHolder();
...
row.setTag(holder);
} else {
holder = (TestHolder) row.getTag();
}
TestModel testModel = data.get(position);
holder.property = testModel.property;
...
if (rowIsNew) {
holder.....setTypeface(...);
holder.....setTypeface(...);
}
return row;
}
}
I have revised the source code of ArrayAdapter and I see no way of executing code after it has called the onChanged() on it's observer so my answer would be:
Implement your own even on onChanged() being called
Call ListView.setAdapter with a brand new adapter with the new dataset
P.S. Number 1 is the optimum solution but number 2 is the easy solution, depending on your time and performance requirement use what you need, but I recommend taking some time and implementing Number 1.
I presume your MainActivity.setTextViewValue("Something..."); line is trying to print some data from the adapter and you're getting the old value, is that so?
I'm not 100% sure of this, perhaps someone else can help confirm this, but I think notifyDataSetChanged() only marks the current data on the adapter as dirty, so the adapter will know that it has to refresh the data, but it doesn't do it immediately when you do the call.
EDIT:
If my first paragraph is correct, you should try to update the text view with data from the data source instead of the adapter, this would be a nicer way to solve the problem.
Related
Actually I wanna get view of particular item in RecyclerView in my fragment.class. For this purpose I tried to set a getter in my adapter class then tried to access it in my fragment but I'm unable to access the views .
Code of Adapter Class :
private List<ViewHolder> holder_list=new ArrayList<>();
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder_list.add(holder);
}
public ViewHolder getViewHolder(int position){
return holder_list.get(position);
}
Fragment Code:
MessageAdapter.ViewHolder holder= msgAdp.getViewHolder(msgAdp.getItemCount()-1);
//Here holder.mMessageView is a text view
Toast.makeText(ctx,holder.mMessageView.getText().toString(),Toast.LENGTH_SHORT).show();
Here is the easiest method
If you want get ViewHolder of item.
RecyclerView.ViewHolder viewHolder = rvList.getChildViewHolder(rvList.getChildAt(0));
or if you want get View object of item.
View view = rvList.getChildAt(0);
Use the one you need. You can get view or ViewHolder. You can manipulate them as you need.
Edit:
getChildAt method is reliable as i also face issue some time, may be it is not yet fixed.
You can use this code
RecyclerView.ViewHolder holder = (RecyclerView.ViewHolder)
recyclerView.findViewHolderForAdapterPosition(position);
if (null != holder) {
holder.itemView.findViewById(R.id.xyz).setVisibility(View.VISIBLE);
}
Edit 2: Note
This is known issue that if you call findViewHolderForAdapterPosition just after setting list then you get NullPointerException.
if notifyDataSetChanged() has been called but the new layout has not
been calculated yet, this method will return null since the new
positions of views are unknown until the layout is calculated.
link
For solving this you can do like this.
recyclerView.postDelayed(new Runnable() {
#Override
public void run() {
RecyclerView.ViewHolder holder = (RecyclerView.ViewHolder)
recyclerView.findViewHolderForAdapterPosition(position);
if (null != holder) {
holder.itemView.findViewById(R.id.xyz).setVisibility(View.VISIBLE);
}
}
}, 50);
I am trying to get child view by position. I could get view when one item is clicked:
rvSellRecords.addOnItemTouchListener(new RecyclerItemClickListener(getActivity(), new RecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, int position) {
((MainActivity) getActivity()).showSellRecordFragment(position, view);
}
}));
Now I cannot get child view, without click - let's say by position for example:
rvSellRecords.someMagicalMethodWhichReturnsViewByPosition(5);
Question: How to get child view from RecyclerView?
EDIT FOR BOUNTY:
I have RecyclerView to show products list. When I click on it, I am adding new Fragment where I show product information. While opening I am updating toolbar with view from RecyclerView - this is working perfectly:
rvSellRecords.addOnItemTouchListener(new RecyclerItemClickListener(getContext(), new RecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, int position) {
sellPresenter.onSellRecordSelected(position, view);
}
}));
When I click blue button with "+", I am incrementing quantity by 1.
public void onIncrementButtonClicked(){
sellRecord.setCount(sellRecord.getCount() + 1);
showQuantity();
bus.post(new SellRecordChangedEvent(sellRecord, sellRecordPosition));
}
Then I am posting updated sellRecord to first fragment using EventBus. There I am updating list data. I supposed that updating value(sell) automatically updates adapter. Now I am getting view from adapter using custom method(getView) which was created by me(you can find it below).
#Subscribe
public void onEvent(SellRecordChangedEvent event){
sell.getSellRecords().set(event.getSellRecordPosition(), event.getSellRecord());
sell.recalculate();
int position = event.getSellRecordPosition();
View view = adapter.getView(position);
bus.post(new TransactionTitleChangedEvent(null, view));
}
This is my adapter class - I changed adapter little bit to collect view in list and added method which returns view for respective position:
public class SellRecordsAdapter extends RecyclerView.Adapter<SellRecordsAdapter.ViewHolder> {
.....
.....
.....
List<View> viewList;
public SellRecordsAdapter(List<SellRecord> sellRecordList) {
.....
viewList = new ArrayList<>();
}
.....
.....
.....
#Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
.....
.....
.....
viewList.add(i, viewHolder.itemView);
}
public View getView(int position){
return viewList.get(position);
}
}
My problem: when I updating view in toolbar, I am getting old view. When quantity is 3, I am getting view with 2. When quantity 10 - view is with 9.
My question: how to get view from recycler view using position of item(without on click listener)?
Use recyclerView.findViewHolderForLayoutPosition(position) or
reyclerView.findViewHolderForAdapterPosition(position) to get the viewholder for postion. Then you can access any child from your viewholder.
Checkout Recyclerview
RecyclerView.ViewHolder holder = recycleView.findViewHolderForAdapterPosition(position);
ImageView imageView = holder.itemView.findViewById(R.id.iv_product);
This is a supplement to #Ravi Teja's answer. You can get the viewHolder from the recyclerView using position of the particular item, then get a particular view from the viewHolder as shown above
You can use RecyclerView's LayoutManager for it.
View view = layoutManager.findViewByPosition(position)
Hope this helps someone:
I was getting null pointer exceptions with:
recyclerView.findViewHolderForAdapterPosition
recyclerView.findViewHolderForItemId
layoutManager.findViewByPosition.
The reason was that there is a slight delay for the viewholder to be created.
I found the solution here: https://stackoverflow.com/a/33414430/7952427
I post an answer because which is really complex to findviews() from RecyclerView.
#Joe: After spending 4hours found one answer. Which gives me the proper view of the index.
mAdapter is adapter of RecyclerView
View v = recyclerView.findViewHolderForItemId(mAdapter.getItemId(index/position)).itemView;
Now just access your views by:
v.findViewById(R.id.edittext) OR any id.
it helped me, make a 100 ms delay before manipulate it, like this:
Handler handler = new Handler();
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
// rcv is my recyclerview
rcvStatus.getChildAt(1).setBackground(getActivity().getResources().getDrawable(R.drawable.disabled));
// or:
rcvStatus.getChildAt(1).setClickable(false);
}
}, 100);
Write this method in adapter.
public Object getItem(int position) {
return yourArrayList.get(position);
}
and you just need to call it like
yourAdapter.getItem(2);
pass your required position.
Hope it solves your problem.
just put this method in your code and you can call it as you likes
void someMagicalMethodWhichReturnsViewByPosition(int position){
//I assumes child views are CardView
CardView c = (CardView)rvSellRecords.getItem(int position);
///optional codes
//////////
}
now I understand your problem. you need to use interface for join recyclerview item and activity.
you must define an interface class like below:
public interface IViewClick {
public void onClickButtonAdd();
}
add this parameter to your adapter class:
private IViewClick mListener;
and initialize it in constructor with value that get from inputs.
when user click on PLUS button, you send event to activity by this line:
mListener.onClickButtonAdd();
in your activity class you must implements IViewClick interface and add your code there, like this:
#Override
public void onClickButtonAdd() {
/// TODO every thing that you want.
/// change your toolbar values.
}
it is not good solution for you.
RecyclerView.ViewHolder holder =
mRecyclerView.findViewHolderForItemId(mAdapter.getItemId(i));
I wouldn't recommend tracking the view list yourself. It could lead to weird issues with item updates, position updates, etc.
Instead on your SellRecordChangedEvent, use findViewHolderForAdapterPosition() instead of adapter.getView().
#Subscribe
public void onEvent(SellRecordChangedEvent event){
sell.getSellRecords().set(event.getSellRecordPosition(), event.getSellRecord());
sell.recalculate();
int position = event.getSellRecordPosition();
View view = yourrecyclerview.findViewHolderForAdapterPosition(position);
bus.post(new TransactionTitleChangedEvent(null, view));
}
http://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#findViewHolderForAdapterPosition(int)
And as a side note, it's better to implement an actual item click listener to the itemView on the ViewHolder instead of using touch listener. There's lots of examples of this online.
So the recyclerview and your product information are in 2 different fragments yes? You are expecting the recyclerview's views to update when they are not even in foreground? also you are changing adapter data item's data at position event.getSellRecordPosition() , but you are not notifying the adapter that its dataset changed, either by adapter.notifyDataSetChanged() or the other notifyItemChanged(position) methods.
I'd modify your onEvent() like so:
#Subscribe
public void onEvent(SellRecordChangedEvent event){
sell.getSellRecords().set(event.getSellRecordPosition(), event.getSellRecord());
sell.recalculate();
int position = event.getSellRecordPosition();
MyViewHolder holder = adapter.onCreateViewHolder(yourRecyclerView, 0);
adapter.onBindViewHolder(holder,position);
View view = adapter.getView(position);
bus.post(new TransactionTitleChangedEvent(null, view));
}
Calling on createViewHolder and next BindViewHolder on your adapter will definitely update the views for that position, then your adapter.getView(position) should return you the latest view.
Here MyViewHolder is your viewholder class and yourRecyclerview, is the reference to your recycler view
for (int i = 0; i < recycler_view.getAdapter().getItemCount(); i++) {
View viewTelefone = recycler_view.getChildAt(i);
}
If you want to replace text on a particular edit text for same position:
for (int i = 0; i < recycler_view.getAdapter().getItemCount(); i++) {
if(adpterPostion==i)
{
View viewTelefone = recycler_view.getChildAt(i);
EditText et_mobile = (EditText) viewTelefone.findViewById(R.id.et_mobile);
et_mobile.setText("1111111");
}
}
This problem has encountered to me and lots of bodies here but yet i haven't seen an efficient answer.
i have a listview and a delete button for each item of the list. when i click on the button, the item is deleted from the database and also removed from the list but the listview doesn't refresh the items.
i have called the method notifyDataSetChanged() but has no result.(when the item is deleted the title of the next item comes up and it seems there is two item with one title, sorted one after another one).
myListView.invalidateViews() and myListView.invalidate() didn't work too.
whats the exact reason and whats the absolute solution?
Here is my code:
here are the whole codes:
public class AdapterNote extends ArrayAdapter {
public AdapterNote(ArrayList<StructNote> notes) {
super(G.context, R.layout.adapter_note, notes);
}
private class ViewHolder {
TextView txtTitle;
ImageView imgDelete;
public ViewHolder(View view) {
txtTitle = (TextView) view.findViewById(R.id.txtTitle);
imgDelete = (ImageView) view.findViewById(R.id.imgDelete);
}
public void fill(final ArrayAdapter<StructNote> adapter, final StructNote item, final int position) {
txtTitle.setText(item.title + "");
imgDelete.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
G.notes.remove(item);
adapter.remove(item);
deleteDataFromDatabase(item.id);
G.adapter.notifyDataSetChanged();
}
});
}
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
StructNote item = getItem(position);
if (convertView == null) {
convertView = G.inflater.inflate(R.layout.adapter_note, parent, false);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
}
else {
holder = (ViewHolder) convertView.getTag();
}
holder.fill(this, item, position);
return convertView;
}
}
Remove the item from the list or ArrayList belongs to the adapter according to the index of the list and then try to call notifyDataSetChanged()
Its a simple thing that you need to remove it from the adapter by calling yourAdapterObject.remove(POSITION);
Here, yourAdapterObject is the object of the adapter and POSITION is the position you want to remove from listview.
After that you need to refresh the adapter with the remaining data and information. So for that you need to call as below:
yourAdapterObject.notifyDataStateChanged();
In Addition here is the Animation demo given by the developer site for showing animation. But they also shows, how to remove item from list and add it into list. All works fine with nice animation effect.
I guess it will surly help you.
Enjoy Coding... :)
The problem you are facing concerning the 'two items with same title part' has to do with loss of synchronization between the listener and the current position. Try something like this, if your custom item is of type:
Object item
Put it in the view-holder alongside your text-views. After inflating the view, do this:
holder.item = getItem(position);
And then, instead of item.xx do it:
holder.item.xx
Just remove it from your array-list and set adapter to your list view again. do this in OnItemClickListener.
private OnItemClickListener listPairedClickItem = new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
arrlist.remove(2);
lv.setAdapter(adapter);
}
};
int pos = nameList.indexOf(object);
nameList.remove(pos);
listAdapter.notifyDataSetChanged();
Late to the discussion...
When I did it from a class where I use the adapter, the listview just won't refreshed. Even worse, at one point, sometimes it's refreshed, sometimes it's not. And I have called adapter.notifyDataSetChanged();.
Finally I got it work by making a method to delete item directly inside the adapter, like this:
CustomAdapter extends BaseAdapter {
public void deleteItem(AdapterItem ai) {
dataList.remove(ai); /*dataList = data source used by adapter*/
notifyDataSetChanged();
}
}
I'm attempting to use CommonsWare's MergeAdapter class and having limited success. In particular, I am not sure if 1) my ArrayAdapter is suitable for use, 2) if I am adding it correctly, and 3) if I am doing all that is necessary to wire everything up.
Here is my subclass of ArrayAdapter:
class PDLAdapter extends ArrayAdapter<PartnerDisease> {
public PDLAdapter(final Context context) {
super(context, 0);
}
#Override
public View getView(final int position, View convertView, final ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.partnerdisease_list_item, null);
viewHolder = new ViewHolder(convertView);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.populateViews(getItem(position));
return convertView;
}
}
Here is my object StructuredSubDisease (the name makes no sense if you actually consider it's a top-level object containing sub diseases, but whatever):
class StructuredSubDisease {
public String headingText;
public ArrayList<PartnerDisease> subDiseases;
public View headingView() {
View returnView = mInflater.inflate(R.layout.partnerdisease_list_item, null);
TextView t = (TextView) returnView.findViewById(R.id.tv_displayname);
t.setText(headingText);
return returnView;
}
}
...and here is where the "magic" is supposed to be happening.
for (StructuredSubDisease s : subDiseaseList) {
mMergeAdapter.addView(s.headingView()); // #Alex, <--- thing 1
PartnerDiseaseListAdapter adapter = new PartnerDiseaseListAdapter(this);
for (PartnerDisease p : s.subDiseases) {
adapter.add(p);
}
mMergeAdapter.addAdapter(adapter); // <--- and thing 2
}
I have Logged the count:
Log.i("mergecount", "" + mMergeAdapter.getCount());
This returns 1, where I would expect 2.
EDIT: I forgot to mention, the result of this is that the headingView() is displayed with the proper heading text, but there is no list beneath it.
Where am I going wrong?
my ArrayAdapter is suitable for use
It seems OK.
if I am adding it correctly
It seems OK.
if I am doing all that is necessary to wire everything up
You don't have any diseases, apparently.
because I added two things - the headingView() (which is rendered) and the adapter (which silently fails)
getCount() returns the number of total rows that should be in your ListView, not the number of things added to the MergeAdapter. In your case, it would appear that you have no diseases.
Start by putting your PartnerDiseaseListAdapter directly into your ListView, ignoring the MergeAdapter. Get that working. Then, switch back to the MergeAdapter.
I'm working on a newsreader, and I have two main activites, main and stories. Main calls asynctask and gets all the rss feeds, and then displays headlines when it's complete. Stories displays articles from specific feeds. There is a navbar in main that calls stories.
My downloaded and parsed articles are in a hashmap of arraylists of the feeds. I only have one adapter class, arrayAdapter. Everything works fine, and I create separate instances of arrayAdapter each time I create a new listview, but the problem is when I go back from stories to main and click on anything in the list, particularly when there are fewer items in stories then the headlines in main, it crashes with:
java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(2131099654, class android.widget.ListView) with Adapter(class package.package.ArticleListAdapter)]
I think my error is coming from the adapters not acting like separate instances, despite the fact that I think I've made them as separate. I looked around for help with this, but most of what I saw was about people changing their data from a background thread, not inside a separate activity.
Unfortunately, this code is paraphrased, partially for readability, and partially because I'm writing it for someone else, closed source yadda yadda...
class main{
public HashMap<Integer, ArrayList<ArticleHolder>> allArticles;
public ArticleListAdapter ArtListAdapter;
public void onCreate(Bundle savedInstanceState) {
allArticles = new HashMap<Integer, ArrayList<ArticleHolder>>();
myApp appstate = (myApp)getApplicationContext();
appState.setParsedArticles(allArticles); //Added this for universal availability of the articles
//download things
//background.execute();
//...
}
private class downloader extends Asynctask ...{
...
onPostExecute(){
ArticleListAdapter = new ArticleListAdapter(context c, list_item L, allArticles.get(1));
listView1 = (ListView)findViewById(R.id.listView1);
listView1.setAdapter(ArtListAdapter);
}
}
class stories{
public HashMap<Integer, ArrayList<ArticleHolder>> allArticles;
public ArticleListAdapter ArtListAdapter;
public void onCreate(Bundle savedInstanceState) {
myApp appstate = (myApp)getApplicationContext();
allArticles = appState.getParsedArticles();
//set layout
//get article to display from Bundle extras
ListView LV = (ListView) findViewById(R.id.listView1);
ArtListAdapter = new ArticleListAdapter(StoriesScreenActivity.this, R.layout.article_list_item, allCleanArticles.get(buttonFeedNum));
LV = (ListView)findViewById(R.id.listView1);
LV.setAdapter(ArtListAdapter);
}
}
Code for the adapter as follows and is exact:
public class ArticleListAdapter extends ArrayAdapter<ArticleHolder>{
private static ArrayList<ArticleHolder> ArticleList;
private Context context;
private LayoutInflater mInflater;
public ArticleListAdapter(Context pcontext, int layoutResourceId, ArrayList<ArticleHolder> results) {
super(pcontext, layoutResourceId, results);
context = pcontext;
ArticleList = results;
mInflater = LayoutInflater.from(context);
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.article_list_item, null);
holder = new ViewHolder();
holder.txtMain = (TextView) convertView.findViewById(R.id.textView1);
holder.txtblurb = (TextView) convertView.findViewById(R.id.textView2);
holder.pic_view = (ImageView) convertView.findViewById(R.id.imageView1);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.txtMain.setText(ArticleList.get(position).getTitle());
holder.txtblurb.setText(ArticleList.get(position).getMainText());
holder.pic_view.setContentDescription(context.getString(R.string.GenImgDesc));
UrlImageViewHelper.setUrlDrawable(holder.pic_view, ArticleList.get(position).getPicName(),R.drawable.ic_placeholder);
return convertView;
}
public int getCount() {
return ArticleList.size();
}
public ArticleHolder getItem(int position) {
return ArticleList.get(position);
}
public long getItemId(int position) {
return position;
}
static class ViewHolder {
TextView txtMain;
TextView txtblurb;
ImageView pic_view;
}
}
Thanks for your help, I'm really stuck.
Also, many thanks and props to koush and Jwsonic on Github for their work on UrlImageViewer. It's awesome.
Edit:
Here's my AsyncTask.doInBackground(). Sorry I took so long, I was out of town for a while without my computer. Thanks again for all your help.
protected Boolean doInBackground(String... params) {
boolean succeeded = false;
String downloadPath = null;
for(Integer num: feedArray){
downloadPath = "example.com/rss/"+Integer.toString(num);
doSax(downloadPath, num);
}
for(Integer num: feedArray){
currentDirtyFeedArticles = allDirtyArticles.get(num);
for(ArticleHolder dirtyArticle: currentDirtyFeedArticles){
dirtyArticle.setTitle(textCleaner(dirtyArticle.getTitle()));
//some text cleaning to make it look pretty
}
}
succeeded = true;
return succeeded;
}
Alright, so I took Jedi_warriors advice and attempted to add the notifyDataSetChanged(), but I couldn't figure it how to get it to mesh with my custom adapter. It put me on the right path though, as I realized the listview on the homescreen wasn't getting updated when I returned to it. This led me to look into having an onResume method, and after some trial and error, I ended up calling the code to associate the view with adapter in onResume. The trick here was that the app crashed when it opened because that code wasn't ready, so I prevented the code in onResume from running until after the app was ready to go. As far as I can tell, that seems to have fixed the problem. Thanks for all your help!
use in asynctask:
#Override
protected Void doInBackground(String... arg0) {
// TODO Auto-generated method stub
}
cause this has acces of Ui .
And For ListView use listview.notifyDataSetChanged();
hope this helps