I am retrieving some data in an Async class called from a Custom ArrayAdapter. When i add a new comment, i update the comment text view and that works ok, but after i reload the entire list the updates don't appear anymore. I can see in the logcat that there is a new comment nr, but not on the UI.
Shouldn't this : answersListView.invalidateViews be enough? I am trying to update that single row from the listview, to escape the issue with not updating the comment nr after a while.
private void updateView(int index) {
System.out.println("index: " + index);
View v = answersListView.getChildAt(index - answersListView.getFirstVisiblePosition());
if (v == null)
return;
final TextView nrComments = (TextView) v.findViewById(com.dub.mobile.R.id.showCommentsTxt);
if (nrComments != null) {
if (nrComments.getText().toString().trim().length() == 0) {
// first comment
nrComments.setText("1 comments");
} else {
// comments exist already
int newNr = Integer.parseInt(nrComments.getText().toString().trim()
.substring(0, nrComments.getText().toString().trim().indexOf("comments")).trim()) + 1;
nrComments.setText(newNr + " comments");
}
System.out.println("final nr of comments: " + nrComments.getText());
nrComments.setVisibility(View.VISIBLE);
}
answersListView.getAdapter().getView(position, v, answersListView);
v.setBackgroundColor(Color.GREEN);
answersListView.invalidateViews();
}
And :
updateView(position);
adapter.notifyDataSetChanged();
The answer is pretty much what #Rami said in a comment to your question. You don't update directly the views, you just need to update the data. In your adapter, you override the getView method, in there is where you make all this changes, you don't need the updateViews method.
Let me try to explain how it works.
The listView uses an Adapter.
The List view ask the Adapter "give me the view in X position" with the getView method.
The adapter creates that view, is returned to the ListView, and that what is shown.
The Adapter itself, should contain a List with the data you want to show.
Those views (rows) are created and destroyed everytime one of those views become visible or invisible in the screen, or if you call the notifyDataSetChanged method.
So now, the thing is, if you change, let's say, the object in the position 5, of the adapter List for a different object with new data, then next time the ListView ask the Adapter to give me the view in the position 5, the adapter is going to create the View (row) with the new data. That's it.
you need to update the data in the UI thread.
if you have context in your Async class use
runOnUiThread(new Runnable() {
#Override
public void run() {
//update here
}
});
or
android.os.Handler handler = new android.os.Handler();
handler.post(new Runnable() {
#Override
public void run() {
//update here
}
});
That's not how you change data for an item in a list view. You must have used some array of strings to fill data in textview in the getView function of the adapter. You only need to change that array and then call notifydatasetchanged on adapter. Views are recycled by adapter so above does not make sense
getView (...)
{
TextView tv = new TextView(context);
tv.setText(myData[index]); // You need to change myData array contents
}
Related
Basically I have a spinner where you can sort your listview, the code i am using for sorting:
if(arg2==0)
{
Sorting_class.QueueSort(mList);
}
else if(arg2==1)
{
Sorting_class.AlphaSort(mList);
}
After sorting and logging the list, it looks sorted without any problem! But as soon as I call adapter.notifydatasetchanged it messes up, for example it overrides the info of the last element with the first element.
If my objects has a string name like this:
z
s
a
after calling alpha sort it looks like this from the logcat:
a
s
z
but after calling adapter.notifydatasetchanged to display the new info, it looks like this:
a
s
a
and it keeps on doing this after each sort until all elements get the same info. After sorting for the second time, my listview looks like this:
a
a
a
this is the code i am using for the sorting:
public static void QueueSort(ArrayList<item_base> mList)
{
Collections.sort(mList, new Comparator<item_base>() {
#Override
public int compare(item_base lhs, item_base rhs) {
return lhs.GetTimeMil() < rhs.GetTimeMil() ? -1 : 1;
}
});
}
public static void AlphaSort(ArrayList<item_base> mList)
{
Collections.sort(mList, new Comparator<item_base>() {
#Override
public int compare(item_base lhs, item_base rhs) {
return lhs.getmName().compareTo(rhs.getmName());
}
});
}
this is the top part of the getview function " it's very long "
if(convertView == null)
convertView = getActivity().getLayoutInflater().inflate(R.layout.listview_shopping, parent,false);
final item_base item = mList.get(pos);
Log.d("sorting" , "getview = " + item.getmName() + " pos = " + pos);
the only solution I found is to set the adapter again after each sort:
mAdapter = new Listview_customAdapter(getActivity(), mList, R.layout.listview_shopping);
mListView.setAdapter(mAdapter);
sort before you set the adapter
I mean after here:
if(arg2==0)
{
Sorting_class.QueueSort(mList);
}
else if(arg2==1)
{
Sorting_class.AlphaSort(mList);
}
//Set the adapter here (after sorting has occured)
mAdapter=new Listview_customAdapter(getActivity(),mList,R.layout.listview_shopping);
mListView.setAdapter(mAdapter);
According to your comment that this will affect the performance of the adapter:
If you are using recycle() view practice, that won't affect the perfromance that much.
So in getView() function:
instead of:
View rootView = LayoutInflater.from(context)
.inflate(R.layout.banana_phone, parent, false);
if you use:
if (convertView == null) {
convertView = LayoutInflater.from(context)
.inflate(R.layout.banana_phone, parent, false);
}
ListView recycles the views that are not shown any more, and gives them back through convertView
For further details you can read here
You need to create two ArrayLists.
So when you use the sort algorithm you are creating a second ArrayList and not overwriting the original ArrayList.
ArrayList list1 and ArrayList list2.
copy the values of list1 into list2.
Then perform any sorting on list2.
Depending on whether the user selects the sorted list or the original list, set your adapter with the selected list. This means you alter the list in your adapter and then set it each time a different sort is selected.
If you are concerned about setting the adapter each time. You can see another way to sort the items of your adapter bypassing this in this code, which comes from this answer here.
I am not sure if this a more cost effective way to achieve what you are wanting to achieve. That choice is ultimately yours.
I have a listview in my android program that gets its information from an ArrayList adapter.
I have three methods that call listview.invalidateViews().
Two of these methods work without fail, and the third seems to freeze the listview. The information is correctly saved when backing out of the activity and on a screen rotate. But without taking these actions, the listview does not update.
Any Ideas?
UPDATE:
These instances work:
public void onItemClick(AdapterView<?> a, View v, int index, long id) {
al.remove(index);
adapter.notifyDataSetChanged();
}
public void addToList(View view) {
EditText et = (EditText) findViewById(R.id.ListText1);
if (et.getText().toString().equals("")) {
//do nothing
}
else {
al.add(et.getText().toString());
adapter.notifyDataSetChanged();
et.setText(null);
}
}
This method does not work:
public void resetList(View view) {
al = new ArrayList<String>();
adapter.notifyDataSetChanged();
}
you are using invalidateViews() differently, if you want to change the view of the listview's child then you can use the invalidateViews() but if you are changing the data/content of the listview's adapter you need to use notifyDataSetChanged() method.
the difference of the two are discussed in this question
ListView.invalidateViews()
is used to tell the ListView to invalidate all its child item views (redraw them). Note that there not need to be an equal number of views than items. That's because a ListView recycles its item views and moves them around the screen in a smart way while you scroll.
Adapter.notifyDataSetChanged()
on the other hand, is to tell the observer of the adapter that the contents of what is being adapted have changed. Notifying the dataset changed will cause the listview to invoke your adapters methods again to adjust scrollbars, regenerate item views, etc...
and with your method
public void resetList(View view) {
al = new ArrayList<String>();
adapter.notifyDataSetChanged();
}
making a new object of ArrayList<String>(); will not reset the data of your list view. just because the al ArrayList that you passed on your adapter is now different to your al = new ArrayList<String>(); what you need to do now is to access your current arraylist then clearing its content with al.clear() method.
Is there a way to call notifyDataSetChanged() on a custom adapter without refreshing the list or disturbing the UI?
I have a ListView with a custom Adapter behind it, using a List of Guest objects as its dataset. When a Guest marks his attendance by tapping on his name, a tick is supposed to appear next to the guest's name in the UI. This I can do, but when I call notifyDataSetChanged(), the list of names is pushed all the way to the top, presumably because the list "refreshes".
If I don't call notifyDataSetChanged(), however, the tick disappears when I scroll past the updated entry and scroll back again. This is due to the ListView's "recycling" of Views as I understand, but it sure doesn't make my job any easier.
How would one call notifyDataSetChanged() without making the entire ListView refresh itself?
Better to have one boolean field in your Guest Class :isPresent.
whenever user taps on list item you can get the selected item using adapter.getItemAtPosition().
update the value isPresent to true. and make show the tick mark.
In your adapter class. check for isPresent value. If it is marked to true then show the tick mark else hide it.
This is how you can achieve the both. Show Tick Mark on ListItem click and if you scroll the listview and come back to the same item you tickmark show/hide will be taken care by Adapter.
you could retain the position like this
// save index and top position
int index = mList.getFirstVisiblePosition();
View v = mList.getChildAt(0);
int top = (v == null) ? 0 : v.getTop();
// ...
// restore
mList.setSelectionFromTop(index, top);
Actually possible if you don't want to make a "selected item" here is the code
public void updateItem(ListView listView, Activity activity) {
if (mData == null) return;
DebugLog.i("A", "firstCell: " + listView.getFirstVisiblePosition() + " lastCell: " + listView.getLastVisiblePosition());
for (int firstCell = listView.getFirstVisiblePosition(); firstCell <= listView.getLastVisiblePosition(); firstCell++) {
final DataItem item = (DataItem) getItem(firstCell); // in this case I put the this method in the Adapter and call it from Activity where the adapter is global varialbe
View convertView = listView.getChildAt(firstCell);
if (convertView != null) {
final TextView titleTextView = (TextView) convertView.findViewById(R.id.item_title);
// here is the most important to do; you have to use Main UI thread to update the view that is why you need activity parameter in the method
mActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
titleTextView.setText( item + " updated");
}
});
}
}
}
I want to update the UI from a timer. That's not a problem at all but when it comes to Gallery/ListViews it gets difficult. I have a Gallery with custom BaseAdapter. I need a counter for every (gallery) item (every item counts different depending on the items data). That counter should run outside of the main thread. In addition I don't want to run 10 threads for 10 items of the gallery when just one item is visible. It's not a problem to define a Handler and start a thread (the counting) in the adapters getView()-method when a item/view gets visible. I can think of something like the following code:
public class MyAdapter extends BaseAdapter {
static class ViewHolder {
//...
public Handler myHandler;
}
public View getView(int position, View convertView, ViewGroup parent) {
//...
// getView() gets called indefinite so first remove callback because it may be added already
holder.myHandler.removeCallbacks(myRunnable);
holder.myHandler.postDelayed(myRunnable, 0);
//...
}
}
The problem is to remove the callback for a view thats not visible anymore because in getView() I get noticed when a view becomes visible but I have no clue how to get the view (and thereby the holder and it's handler) that became invisible to remove the callback.
Is there a(nother) approach to solve that?
Found a clean solution (if someone needs something like that). I needed to update a TextView to set the new counter value (every second).
In BaseAdapters getView I add the TextViews(s) to a WeakHashMap. The TextView is the key of the map. A key/value mapping will be removed if the key is no longer referenced. So I do not cause memory leaks. The GarbageCollector does the work.
A thread counts "counter objects" down and refreshes the TextViews with the corresponding values (GarbageCollector runs all the time so in my case the map consists mostly of 3 items because the gallery shows just one item. Due to GarbageCollection the map gets immediately cleared by "unused" TextViews specially when scrolling fast through the list/gallery)
BaseAdapter's getView():
private WeakHashMap<TextView, Integer> counterMap = new WeakHashMap<TextView,Integer>();
private Handler counterHandler = new Handler();
public View getView(...) {
//...
counterMap.put(holder.tvCounter, position);
//...
}
The counter:
// Thread decrementing the time of the counter objects and updating the UI
private final Runnable counterTimeTask = new Runnable() {
public void run() {
//...
// decrement the remaining time of all objects
for (CounterData data : counterDataList)
data.decrementTimeLeftInSeconds();
// iterate through the map and update the containing TextViews
for (Map.Entry<TextView, Integer> entry : counterMap.entrySet()) {
TextView tvCounter = entry.getKey();
Integer position = entry.getValue();
CounterData data = counterDataList.get(position);
long timeLeftInSeconds = data.getTimeLeftInSeconds();
tvCounter.setText(" " + timeLeftInSeconds);
}
if (yourCondition)
counterHandler.postDelayed(this, 1000);
}
};
Start/stop the counter:
public void startCounter() { counterHandler.post(counterTimeTask); }
public void stopCounter() { counterHandler.removeCallbacks(counterTimeTask); }
I have an android activity that consists of a List View and a Text View. The Text view displays information about the summed contents of the list view, if the items in the list view are updated then the Text View needs to reflect the change.
Sort of like an observer pattern but with one component reflecting the changes of many rather than the other way round.
The tasks are displayed as items in the list view and the progress can be updated using a seekbar to set the new progress level.
The following method populates the TextView so I need to call this again from inside the SeekBar onStopTrackingProgress Listener.
private void populateOverviewText() {
TextView overview = (TextView) findViewById(R.id.TaskOverview);
if (!taskArrayList.isEmpty()) {
int completion = 0;
try {
completion = taskHandler.getProjectCompletion();
overview.setText("Project Completion = " + completion);
} catch (ProjectManagementException e) {
Log.d("Exception getting Tasks List from project",
e.getMessage());
}
}
}
I realise if I want to call the method from the List Adapter then I'll need to make the method accessible but I'm not sure the best approach to do this.
Either make this method public
or you can pass activity to adapter through which you can find the button and update text, or can directly pass Button to adapter...
Using DataSetObserver
I was looking for a solution that didn't involve altering the adapter code, so the adapter could be reused in other situations.
I acheived this using a DataSetObserver
The code for creating the Observer is incredibly straight forward, I simply add a call to the method which updates the TextView inside the onChanged() method.
Attaching the Observer (Added to Activity displaying list)
final TaskAdapter taskAdapter = new TaskAdapter(this, R.id.list, taskArrayList);
/* Observer */
final DataSetObserver observer = new DataSetObserver() {
#Override
public void onChanged() {
populateOverviewText();
}
};
taskAdapter.registerDataSetObserver(observer);
Firing the OnChanged Method (Added to the SeekBar Listener inside the adapter)
public void onStopTrackingTouch(SeekBar seekBar) {
int progress = seekBar.getProgress();
int task_id = (Integer) seekBar.getTag();
TaskHandler taskHandler = new TaskHandler(DBAdapter
.getDBAdapterInstance(getContext()));
taskHandler.updateTaskProgress(task_id, progress);
mList.get(position).setProgress(progress);
//need to fire an update to the activity
notifyDataSetChanged();
}