Listview items getView repeating itself again and again - java

I am using following code as Cusotm Array adapter of a ListView in android
In View new MyAsyncTask(url, callback); will be run again and again, How can i make it so that the query to the server will be performed only once.
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
final Question question = quesions.get(position);
final OpenionUser myUser = question.getUser();
View view = convertView;
ViewHolder viewHolder = new ViewHolder();
if (convertView == null)
{
view = inflator.inflate(R.layout.question_adapter_layout, parent, false);
viewHolder.firstPhotoPercent = (TextView) view.findViewById(R.id.firstPhotoProgressText);
viewHolder.secondPhotoPercent = (TextView) view.findViewById(R.id.secondPhotoProgressText);
viewHolder.comments = (LinearLayout) view.findViewById(R.id.commentsListView);
viewHolder.profilePic = (ImageButton) view.findViewById(R.id.profile);
viewHolder.leftPic = (ImageButton) view.findViewById(R.id.photo1);
viewHolder.rightPic = (ImageButton) view.findViewById(R.id.photo2);
viewHolder.firstPhotoBg = (RelativeLayout) view.findViewById(R.id.firstPhotoProgress);
viewHolder.secondPhotoBg = (RelativeLayout) view.findViewById(R.id.secondPhotoProgress);
view.setTag(viewHolder);
}
else
viewHolder = (ViewHolder) view.getTag();
// //imageLoader.displayImage(myUser.getProfilePicture(), viewHolder.profilePic, options);
//
viewHolder.username.setText(myUser.getUserName());
viewHolder.question.setText(question.getQuestion());
imageLoader.displayImage(question.getRightThumbnailLink(), viewHolder.rightPic, options);
imageLoader.displayImage(question.getLeftThumbnailLink(), viewHolder.leftPic, options);
String url = String.format(Constants.GET_QUESTION_COMMENTS,
question.getId(),
0,
2);
ResponseCallback callback = new ResponseCallback()
{
#Override
public void onSuccess(HttpResponse response)
{
try {
JSONObject obj = new JSONObject(Utilities.convertStreamToString(response.getEntity().getContent()));
JSONArray array = obj.getJSONArray("comments");
ArrayList<Comment> comments = new ArrayList<Comment>();
for (int i = 0; i < array.length(); i ++)
{
Comment comment = Utilities.getCommentFromJSON(array.getJSONObject(i));
comments.add(comment);
}
addFeaturedViews(comments, viewHolder.comments);
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
}
#Override
public void onFailure(HttpResponse exception)
{
}
};
new MyAsyncTask(url, callback);

"there is absolutely no guarantee on the order in which getView() will be called nor how many times" (Romain Guy # here). This method is called for example, when an element goes off-screen and then appears again after scrolling the ListView. So you should never make a call to your server from getView: instead, you should execute your asynctask outside the adapter (in a fragment or activity, for example, before instantiating the adapter), and then call notifyDataSetChanged on your list.

you could try this:
if(position==0)
{
new MyAsyncTask(url, callback);
}
*Modifying the answer after seeing the information given by Dhanesh Budhrani in the comment below *
If you want to run new MyAsyncTask(url, callback); only once, you could try this:
if(firsttime)
{
firsttime = false;
new MyAsyncTask(url, callback);
}
here firsttime is a boolean variable initialized to true in the adapter's constructor.

As already mentioned in the comments, the adapter's task is to provide views for the ListView to render. You should handle the data requests in a different class, provide the data to the Adapter and then call notifyDataSetChanged on the ArrayAdapter.

Since the adapter class is directly interacting with ListView, the correct way is not to call any threads directly on it. As you will scroll your ListView/View, the getView() method will be called up again and again and AsyncTask will be called up hindering the smooth flow of ListView.
In order to deal with the situation, call the AsyncTask in your Activity class and as soon as the result is fetched in onSuccess(), define the CustomAdapterClass and call the listView.setAdapter(customAdapter) in it and pass the data in ArrayList as a Constructor of CustomArrayAdapter(ArrayList arrayListJsonResponse) and inflate the data.
Hope it helps. Thanks.

Related

Search listview but instead of filtering, just inflate and display the valid items

I have a arraylist of buttons (reserveButtons) that I can display in a listview. I have made a search function which searches in my database and outputs a list of integers (resultID). They correspond to the indexes of reserveButtons I want to display.
Simply put, I want to do something like this when the search button is clicked:
ArrayAdapter<ReserveButton> adapter = new MyListAdapter();
ListView list = (ListView) myView.findViewById(R.id.resultslist);
list.setAdapter(adapter);
for (int result : resultID) {
adapter.add(reserveButtons.get(result));
}
So, for each result, I want to add the corresponding button to the listview.
Here is the private class MylistAadapter :
private class MyListAdapter extends ArrayAdapter<ReserveButton> {
public MyListAdapter() {
super(getActivity(), R.layout.list_item, reserveButtons);
}
#NonNull
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View itemView = convertView;
if(itemView == null) {
itemView = gettActivity().getLayoutInflater().inflate(R.layout.list_item, parent, false);
}
ReserveButton currentButton = reserveButtons.get(position);
//the resultItem is the id of the buttons
Button butt = (Button) itemView.findViewById(R.id.resultItem);
butt.setBackground(currentButton.getImage());
return itemView;
}
}
I know that getView will just display every reserveButton, but I want the code in getView to be executed when I add each button, but the position doesn't change since position = result in the for loop of the first code block.
//This code is inside MyListAdapter
#Override
public void add(ReserveButton object) {
/* What do I write here to inflate a list_item and give it
the background image reserveButton.get(result).getImage() */
super.add(object);
}
How do I override the add method of MyListAdapter so that I can add a reserveButton and change its background image for each result in the resultID list.
If the same thing can be accomplished without the add method, please do tell.
P.S: I do not want to just list every reserveButton and then filter them with the search; I want to display ONLY the buttons that the user is looking for.
I figured it out myself!
Basically, what I did was create a separate ArrayList of ReserveButtons and do the foreach loop like so:
int index = 0;
for (int result : resultID) {
//result is the single ID of an answer
answerButtons.add(index,reserveButtons.get(result));
index ++;
}
populateListView();
So I end up storing ONLY the buttons I want to display in the answerButtons list. And here is what happens in populateListView()
private void populateListView() {
ArrayAdapter<ReserveButton> adapter = new MyListAdapter();
ListView list = (ListView) myView.findViewById(R.id.resultslist);
list.setAdapter(adapter);
}
and the getView() method:
public View getView(int position, View convertView, ViewGroup parent) {
View itemView = convertView;
if(itemView == null) {
itemView = getActivity().getLayoutInflater().inflate(R.layout.list_item, parent, false);
}
//Just set the image of the corresponding answerButton
ReserveButton currentButton = answerButtons.get(position);
Button butt = (Button) itemView.findViewById(R.id.resultItem);
butt.setBackground(currentButton.getImage());
return itemView;
}
Problem solved. I haven't seen any answers to a problem like this, so this post should make it easily google-able for any newcomer who stumbles upon this problem.

setProgressbar not updating inside getView form a thread running on activity?

I have a listview with each item having a ProgressBar on top. My own created thread running and downloading the data in background and have to show download progress in getView part for each item, but i'm not able to update the setProgressbar in getView method of adapter.
But when user scrolls up and down(away from particular view) then its update the progress and freezes and not increasing based on download percentage.
My thread code
downloading = Ion.with(LoadTumblrActivity.this)
.load(item.getGifUrl())
// can also use a custom callback
// can also use a custom callback
.progress(new ProgressCallback() {
#
Override
public void onProgress(long downloaded, long total) {
float progressVal = ((float) downloaded / (float) total);
item.setProgress((int)(100 * progressVal));
}
})
.write(
new File(Environment.getExternalStorageDirectory().getPath() + "/sdcard" + fileName))
.setCallback(new FutureCallback < File > () {
#
SuppressLint("SdCardPath")# Override
public void onCompleted(Exception e, File file) {
{
if (e != null) {
Toast.makeText(LoadTumblrActivity.this, "Error downloading file", Toast.LENGTH_LONG).show();
System.out.println(e);
return;
}
}
}
});
My BaseAdapter
public View getView(int position, View convertView, ViewGroup parent) {
View rowView = convertView;
// reuse views
if (rowView == null) {
LayoutInflater inflater = activity.getLayoutInflater();
rowView = inflater.inflate(R.layout.feed_item, parent, false);
// configure view holder
ViewHolder viewHolder = new ViewHolder();
viewHolder.progressBar = (ProgressBar) rowView.findViewById(R.id.progress);
rowView.setTag(viewHolder);
}
holder.progressBar.setProgress(item.getProgress());
}
Your Problem is that you call
holder.progressBar.setProgress(item.getProgress());
inside your getView and getView is only called for the items visible on the Screen. That's why your ProgressBar only updates when you scroll for the Items which Comes to Screen due to scrolling! You Need to call this line outside of the getView for example in an AsyncTask when you call publishProgress(); in onProgressUpdate to give user a real experience of how Long it takes till the item appears/finished downloading.
Another Approach would be to set the progressBar to intermediate= "false" and only Show a spinning progressbar! ;)
Hope it helps
Use runOnuiThread to update progress bar:
runOnUiThread(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
progressDialog.setMessage(ii+" Apps Loaded...");
ii++;
}
});
Source code from Helper+

Mismatched images in table cells with Picasso Library

I'm trying to fix an issues that I've seen previously and fixed on iOS, but am unable to fix on android. In iOS, I used the SDWebImage library to download and cache images. However, when scrolling through a long list of cells, the images would come up in the wrong cells. I was able ot fix this by doing the following:
#property (weak) id <SDWebImageOperation> imageOperation;
...
- (void)setFriend:(TAGUser *)friend {
...
self.imageOperation = [[SDWebImageManager sharedManager] downloadWithURL:[NSURL URLWithString:friend.profileImageUrl] options:SDWebImageRetryFailed
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) {
if(!error) {
self.profileImageView.image = image;
}
}];
...
}
- (void)prepareForReuse {
[super prepareForReuse];
if (self.imageOperation) {
[self.imageOperation cancel];
}
self.imageOperation = nil;
}
On Android, I am using the Picaso Libray and trying to achieve the same result like so:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View vi = convertView;
if (vi == null) {
vi = inflater.inflate(R.layout.friend_row, parent, false);
ViewHolder holder = new ViewHolder();
holder.friendImage = (ImageView) vi.findViewById(R.id.friend_image);
}
final ViewHolder holder = (ViewHolder) vi.getTag(); //Bad naming convention in my project I know, but it's a built-in method
//THIS SHOULD IN THEORY CANCEL THE REQUEST OF THE OLD FRIEND URL
Picasso.with(mActivity.getBaseContext()).cancelRequest(holder.friendImage);
holder.friendImage.setImageDrawable(mActivity.getResources().getDrawable(R.drawable.background_circle));
final TagUser user = (TagUser) getItem(position);
Picasso.with(mActivity.getBaseContext()).load(user.getProfileUrl()).transform(new RoundedTransformation(ViewUtility.convertPxToDp(mActivity, 23), 0)).fit().into(holder.friendImage,
new Callback() {
#Override
public void onSuccess() {
holder.friendInitials.setVisibility(View.INVISIBLE);
}
#Override
public void onError() {
holder.friendInitials.setVisibility(View.VISIBLE);
}
});
}
Even with cancelRequeset getting called, the profile images are still getting mismatched. Any help would be greatly appreciated!
You don't need to call cancel. Picasso automatically sees when views are being re-used and will cancel old downloads for that image view.
I would also recommend using Picasso's .placeholder API for the background circle.
You seem to be missing a call to setTag when the layout is inflated. Hopefully that's just an error in copying to the post.
Lastly, create the RoundedTransformation once and re-use the same instance for all calls to Picasso.
In the end your code should look like this:
private final Transformation roundTransform;
// Set the following in constructor:
// roundTransform = new RoundedTransformation(ViewUtility.convertPxToDp(mActivity, 23), 0)
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View vi = convertView;
if (vi == null) {
vi = inflater.inflate(R.layout.friend_row, parent, false);
ViewHolder holder = new ViewHolder();
vi.setTag(holder)
holder.friendImage = (ImageView) vi.findViewById(R.id.friend_image);
}
final ViewHolder holder = (ViewHolder) vi.getTag(); //Bad naming convention in my project I know, but it's a built-in method
final TagUser user = (TagUser) getItem(position);
Picasso.with(mActivity)
.load(user.getProfileUrl())
.placeholder(R.drawable.background_circle)
.transform(roundTransform)
.fit()
.into(holder.friendImage,
new Callback() {
#Override
public void onSuccess() {
holder.friendInitials.setVisibility(View.INVISIBLE);
}
#Override
public void onError() {
holder.friendInitials.setVisibility(View.VISIBLE);
}
});
}
I would advice to use Volley by Google. I have used it in many projects and it never gave mismatched results even in case I had nearly 1000 images in 3 column gridView. Volley

Android AutoCompleteTextView showing wrong suggestions

I had the AutoCompleteTextView working perfectly until I decided to use a custom adapter, that way I could customize the look of each row. Here is what now happens:
As you can see, the suggestion is wrong. What is happening is as I type, it shows the correct number of suggestions, but the actual names of them are the first ones in the list. So at this point it should show only one suggestion which is Texas A&M, but it instead just shows the first one in the list. Here is how I am implementing my adapter.
//setup filter
List<String> allCollegesList = new ArrayList<String>();
for(College c : MainActivity.collegeList)
{
allCollegesList.add(c.getName());
}
AutoCompleteDropdownAdapter adapter = new AutoCompleteDropdownAdapter(main, R.layout.list_row_dropdown, allCollegesList);
//the old way of using the adapter, which worked fine
//ArrayAdapter<String> adapter = new ArrayAdapter<String>(main, android.R.layout.simple_dropdown_item_1line, allCollegesList);
textView.setAdapter(adapter);
As well as my actual adapter class:
public class AutoCompleteDropdownAdapter extends ArrayAdapter<String>{
MainActivity main;
int rowLayout;
List<String> allCollegesList;
public AutoCompleteDropdownAdapter(MainActivity main, int rowLayout, List<String> allCollegesList) {
super(main, rowLayout, allCollegesList);
this.main = main;
this.rowLayout = rowLayout;
this.allCollegesList = allCollegesList;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
try{
if(convertView==null){
// inflate the layout
LayoutInflater inflater = ((MainActivity) main).getLayoutInflater();
convertView = inflater.inflate(rowLayout, parent, false);
}
// object item based on the position
String college = allCollegesList.get(position);
// get the TextView and then set the text (item name) and tag (item ID) values
TextView collegeTextView = (TextView) convertView.findViewById(R.id.dropDownText);
collegeTextView.setText(college);
collegeTextView.setTypeface(FontManager.light);
} catch (NullPointerException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return convertView;
}
}
What happens here is that when the AutoCompleteTextView calls the getFilter method of the ArrayAdapter a Filter is returned, that takes care of filtering the ArrayAdapter, but not your allCollegesList. When you type your first characters, the methods of the Filter are called and ArrayAdapter has filtered elements at the first positions (0, 1, ...). However when the AutoCompleteTextView uses your implementation to get the Views. You use your list as if no filtering was done and use the first elements of the unfiltered list.
You can filter your own list too by overriding the getFilter method of your adapter. But that would be more coding than necessary.
You can use the ArrayAdapter's methods and not your own list, instead:
Use
String college = getItem(position);
instead of
String college = allCollegesList.get(position);
BTW:
you can get the context from parent too, using the getContext() method. That way you can decouple the Adapter from MainActivity.

How can you add multiple button listeners all at once?

Scroll down for my answer. The question doesn't really matter and the code is just confusing.
Is there a function that will allow me to fetch the id labels so I can loop a button listener easily? Currently I have in my main "container" xml that houses fragments this line of code:
public static final Map<String, Integer> RMAP = createMapR();
// Map of widget id's in R
private static Map<String, Integer> createMapR() {
Map<String, Integer> result = new HashMap<String, Integer>();
for (Field f : R.id.class.getDeclaredFields()) {
String key = f.getName();
Integer value = 0;
try {
value = f.getInt(f);
}
catch (IllegalArgumentException e) {
e.printStackTrace();
}
catch (IllegalAccessException e) {
e.printStackTrace();
}
result.put(key, value);
}
return Collections.unmodifiableMap(result);
}
and then one of my fragments will pick up the RMAP, and cross that with the labels I have specified. I'm doing this because I have a few buttons and specifying a huge list of listeners seemed inefficient, then I got sidetracked on this code.
public class BottomFragment extends Fragment {
private final String[] LABELS = {"button_do_1", "button_woah_2", "button_foo_1",
"button_hi_2"};
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.bottom_fragment, container, false);
for (String item : LABELS) {
if (Container.RMAP.containsKey(item)) {
((Button) v.findViewById(Container.RMAP.get(item)))
.setOnClickListener(this);
}
}
return v;
}
However if there was a way to iterate through the list of android:id items specifically to BottomFragment() I wouldn't have to even need the first block of code and it would eliminate the manual list of ID's I've typed in.
Here's a rough logic sketch (in C#)... It basically does a single, top down pass over the layout, building an id-to-view map that can be looped through afterwards. There is know general way to do this, and so will have to be maintained along with the layout file (both need to be modified together).
Say you want to do this in OnCreate of the Activity that is hosting your fragment(s) (it could possibly be done in the Fragments OnMeasure or OnLayout overrides too)
Assuming your fragment's layout file has the following structure
LinearLayout
TextView
TextView
RelativeLayout
EditText
EditText
TextView
public override OnCreate()
{
Map<int, view> idLabels = new Map<int, view>();
View v = fragment.View;
LinearLayout ll = (LinearLayout) v;
idLabels.Add(ll.Id, ll)
TextView tv1 = (TextView) ll.GetChildAt(0);
idLabels.Add(tv1.Id, tv1)
TextView tv2 = (TextView) ll.GetChildAt(1);
idLabels.Add(tv2.Id, tv2)
RelativeLayout rl = (RelativeLayout) ll.GetChildAt(2);
idLabels.Add(rl.Id, rl)
// notice the numbers being passed to GetChildAt, its a traversal of Views and Viewgroups !
EditText et1 = (EditText) rl.GetChildAt(0);
idLabels.Add(et1.Id, et1)
EditText et2 = (EditText) rl.GetChildAt(1);
idLabels.Add(et2.Id, et2)
// now, go back "up" to top-most, linear layout viewgroup and get remaining views, if any
TextView tv3 = (TextView) ll.GetChildAt(3);
idLabels.Add(tv3.Id, tv3)
...
foreach(int id in idLabels)
{
if(id == R.id.myViewIdLabel) // your R file will have all your id's in the literal form.
{
View v = idLabels.Map(id);
v.SetOnClickListener(this);
}
}
}
This may not be the best way, but it should work.
I figured it out. The solution lies within the children of a ViewGroup. This code adds listeners for every button in that particular view. In this case the view is a fragment of only buttons. This code is handy for whenever you have lots of buttons and you want to add a listener to each one.
public static void addButtonListeners(View v, OnClickListener clickListener)
{
ViewGroup widgets = (ViewGroup) v;
for(int i = 0; i < widgets.getChildCount(); ++i)
{
View child = widgets.getChildAt(i);
if (child instanceof Button)
{
Button nextButton = (Button) child;
nextButton.setOnClickListener(clickListener);
}
}
}
You need to pass the view and listener to this function. The listener can by passed by just passing "this", i.e:
addButtonListeners(v, this);

Categories