I get duplicated items in a ListView. Scrolling back and down sometimes changes the item order.
I googled and found many threads reporting this bug, but none of them helped me in fixing my issue.
Here is my code:
Activity:
package com.github.progval.SeenDroid;
import java.util.ArrayList;
import java.util.List;
import com.github.progval.SeenDroid.lib.Connection;
import com.github.progval.SeenDroid.lib.Message;
import com.github.progval.SeenDroid.lib.MessageFetcher;
import com.github.progval.SeenDroid.lib.Query.ParserException;
import android.app.Activity;
import android.app.ListActivity;
import android.content.SharedPreferences;
import android.os.Bundle;
public class ShowUserActivity extends ListActivity {
private Connection connection;
public ArrayList<Message> listMessages = new ArrayList<Message>();
public MessageAdapter adapter;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.profile);
this.connection = new Connection();
this.setTitle(R.string.homefeed_title);
this.listMessages = new MessageFetcher(this.connection).fetchUser();
this.bindUi();
}
private void bindUi() {
this.adapter = new MessageAdapter(this, this.listMessages);
this.setListAdapter(adapter);
// TODO Bind buttons
}
}
MessageAdapter:
package com.github.progval.SeenDroid;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.Inflater;
import com.github.progval.SeenDroid.lib.Message;
import android.content.Context;
import android.text.Layout;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
public class MessageAdapter extends BaseAdapter {
private Context context;
private List<Message> items = new ArrayList<Message>();
private int lastPosition = 0;
public MessageAdapter(Context context, List<Message> items) {
super();
this.context = context;
this.items = items;
}
public View getView(int position, View convertView, ViewGroup parent) {
if (null == convertView) {
LinearLayout view;
view = (LinearLayout) LinearLayout.inflate(this.context, R.layout.message, null);
Log.d("SeenDroid", String.format("Get view %d", position));
TextView title = new TextView(view.getContext());
title.setText(this.items.get(position).getTitle());
view.addView(title);
return view;
} else {
return convertView;
}
}
#Override
public int getCount() {
return this.items.size();
}
#Override
public Object getItem(int location) {
return this.items.get(location);
}
#Override
public long getItemId(int arg0) {
return arg0;
}
}
By the way, the output is:
D/SeenDroid(30939): Get view 0
D/SeenDroid(30939): Get view 1
D/SeenDroid(30939): Get view 2
D/SeenDroid(30939): Get view 3
D/SeenDroid(30939): Get view 4
D/SeenDroid(30939): Get view 5
D/SeenDroid(30939): Get view 6
D/SeenDroid(30939): Get view 7
D/SeenDroid(30939): Get view 8
D/SeenDroid(30939): Get view 0
D/SeenDroid(30939): Get view 16
Regards,
ProgVal
Try this:
public View getView(int position, View convertView, ViewGroup parent) {
if (null == convertView) {
LinearLayout view = (LinearLayout) LinearLayout.inflate(this.context,
R.layout.message, null);
Log.d("SeenDroid", String.format("Get view %d", position));
TextView title = new TextView(view.getContext());
title.setText(this.items.get(position).getTitle());
view.addView(title);
return view;
} else {
LinearLayout view = (LinearLayout) convertView;
TextView title = (TextView) view.getChildAt(0);
title.setText(this.items.get(position).getTitle());
return convertView;
}
}
Explanation: you got the duplicates because lists on Android reuse UI objects. You are expected to reuse convertView rather than create a new one if it is not null. Of course, you are responsible to set an appropriate value to the instance being reused. Otherwise the value is left from the last "usage".
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View gridView;
if (convertView == null) {
gridView = new View(context);
} else {
gridView = (View) convertView;
}
gridView = inflater.inflate(R.layout.worker_listmain, null);
// your source code here!!! Run 100%
// I got this problem also, I found out the way to solve it!
// Please use my source code :D SIMPLE is PERFECT :D
return gridView;
}
ListView does not guarantee uniqueness of items you are added there. It is your responsibility. You are using ArrayList to store items and it can store as many duplicate items as you want.
If you want to removed duplicates put your items into set and then into list again:
listMessages = new ArrayList<Messages>(new LinkedHashSet<Message>(listMessages))
The LinkedHashSet will remove duplicates preserving the initial order of items, ArrayList will allow access elements by position.
A suggestion - add a log statement after title.setText and write the value obtained from getTitle(). You might get to know why you are getting duplicate entries.
You should use the ViewHolder paradigma in your ListAdapter
a nice example can be found here:
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/view/List14.html
Related
I have been messing with tmdb and movies and I have run into an issue where when my fragment loads and populates my gridview adapter the loading of images takes place without any error, but physically I can't see it until I scroll the GridView down a few lines and the back-up again.
The image shows what it's like before scrolling. Then after scrolling all the other images appear.???
Here is my adapter code for my GridView.
package com.rwsw.livesofa.tv.gridviews;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.bumptech.glide.Glide;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import com.rwsw.livesofa.R;
import com.rwsw.livesofa.models.MovieModel;
import com.rwsw.livesofa.tv.fragments.FragmentRecommended;
import java.util.ArrayList;
import java.util.HashMap;
public class Grid_View_Recommended extends BaseAdapter {
Context context;
LayoutInflater inflater;
private static ArrayList<MovieModel> movieModelList;
MovieModel resultp;
public Grid_View_Recommended(Context context, ArrayList<MovieModel> arraylist) {
this.context = context;
movieModelList = arraylist;
}
public class ViewHolder {
public ImageView poster;
}
#Override
public int getCount() {
return movieModelList.size();
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
public View getView(final int position, View convertView, ViewGroup parent) {
View itemView = convertView;
Grid_View_Recommended.ViewHolder holder;
if (convertView == null) {
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
itemView = inflater.inflate(R.layout.item_movie, parent, false);
holder = new ViewHolder();
holder.poster = (ImageView) itemView.findViewById(R.id.poster);
itemView.setTag(holder);
} else {
holder = (Grid_View_Recommended.ViewHolder) itemView.getTag();
resultp = movieModelList.get(position);
Glide.with(context)
.asBitmap()
.load("https://image.tmdb.org/t/p/w342" + resultp.getPosterPath())
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
.into(holder.poster);
}
return itemView;
}
}
I have done many searches and tried loading as bitmap and using different cache strategy's, but cant get it to work.
The first time the listview loads the null condition is true.
If you see the null condition does not have the Glide code in it. Hence the picture loads only when the convertView is not null.
So you can keep the Glide code in both sections.
If that does not work.
Here are a few things you can try
Load it directly with
Glide.with(this)
.load("https://image.tmdb.org/t/p/w342" + resultp.getPosterPath())
..into(holder.poster);
```
If that does not solve it. Change the DiskCacheStrategy to NONE
Whenever I try to add an item to the object it gives an error
I tried to switch between android.support.v4.app and the other one but it is not working
Code of Place Object
package com.example.tourguide.ui.main;
public class Place {
/** String resource ID for the name of the place */
private int mDefaultTranslationId;
private int mImageResourceId;
public Place(int defaultTranslationId, int imageResourceId) {
mDefaultTranslationId = defaultTranslationId;
mImageResourceId = imageResourceId;
}
/**
* Get the string resource ID for the name of the place.
*/
public int getPlaceName() {
return mDefaultTranslationId;
}
/**
* Return the image resource ID of the place.
*/
public int getImageResourceId() {
return mImageResourceId;
}
}
Code of Place Adapter
package com.example.tourguide.ui.main;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.example.tourguide.R;
import java.util.ArrayList;
public class PlaceAdapter extends ArrayAdapter<Place> {
public PlaceAdapter(Context context, ArrayList<Place> places) {
super(context, 0, places);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Check if an existing view is being reused, otherwise inflate the
view
View listItemView = convertView;
if (listItemView == null) {
listItemView = LayoutInflater.from(getContext()).inflate(
R.layout.list_item, parent, false);
}
Place currentPlace = getItem(position);
TextView placeName = (TextView)
listItemView.findViewById(R.id.ImageViewText);
placeName.setText(currentPlace.getPlaceName ());
ImageView imageView = (ImageView)
listItemView.findViewById(R.id.ImageView);
imageView.setImageResource ( currentPlace.getImageResourceId () );
return listItemView;
}
}
This is where I am getting an error when I try to add an item in Place object
package com.example.tourguide.ui.main;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;
import androidx.fragment.app.Fragment;
import com.example.tourguide.R;
import java.util.ArrayList;
public class MonumentsFragment extends Fragment {
public MonumentsFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate( R.layout.places_list, container,
false);
final ArrayList<Place> places = new ArrayList<> ();
places.add(R.string.app_name, R.drawable.google_maps);
PlaceAdapter adapter = new PlaceAdapter (getActivity(), places);
ListView listView = (ListView) rootView.findViewById(R.id.list);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int
position, long l) {
Place place = places.get(position);
}
});
return rootView;
}
}
This is the error message that i am getting
Wrong 2nd argument type. Found: 'int', required: 'com.example.tourguide.ui.main.Place'
Inspection info:
add
(int, com.example.tourguide.ui.main.Place) in ArrayList cannot be applied
to (int, int)
You're trying to add name and the image to list directly. What you should do is first create a Place Object and then add this to the list.
ArrayList<Place> places = new ArrayList<> ();
// create a Place Object
Place place = new Place(R.string.app_name, R.drawable.google_maps)
// Then add the object to the list
places.add(place);
EDIT
You can add any type of data to a List, but it has to be the same type as you declare when initializing the array. For example, you are making a List of Places and therefore you can only add Place Object to the array.
The add method takes two parameters, first the index and then the object. If you just use list.add(E element) it will append the Object to the end of the list.
public void add(int index, E element)
Inserts the specified element at the specified position in this list. Shifts the element currently at that position (if any) and any subsequent elements to the right (adds one to their indices).
You can find more about ArrayList from this doc
I created a simple android application with a ListView in it. I have my custom list adapter below:
package com.example.android.efaas;
import java.util.List;
import com.example.android.R;
import com.example.android.efaas.bean.ListItem;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class CustomListAdapter extends BaseAdapter {
LayoutInflater inflater = null;
Context ctx;
List<ListItem> data;
public CustomListAdapter(Activity activity, List<ListItem> data){
ctx = activity;
this.data = data;
inflater = ( LayoutInflater )ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
return data.size();
}
#Override
public Object getItem(int pos) {
return pos;
}
#Override
public long getItemId(int pos) {
return pos;
}
#Override
public View getView(int pos, View view, ViewGroup vgroup) {
View rowView = inflater.inflate(R.layout.list_menu, null);
ImageView img = (ImageView) rowView.findViewById(R.id.list_menu_image);
TextView title = (TextView) rowView.findViewById(R.id.list_menu_title);
ListItem item = data.get(pos);
img.setImageResource(item.getId());
title.setText(item.getTitle());
return rowView;
}
public ListItem getListItem(int pos){
return data.get(pos);
}
}
I have a public method getListItem, I tried to call it during ListView onClick but somehow I cant seem to make it work. Here's the image of the problem:
How do I resolve this?
You should cast adapter.getAdapter() to CustomListAdapter class then
the compile time error is due of the fact that adapter is not your CustomListAdapter. What you can do is cast the return value of adapter.getAdapter() to CustomListAdapter. Conversely you should use what's already available to retrieve the object at position. Like getItemAtPosition(int)
I'm a newb so try to be patient with me
Here is some background information about the problem I am having:
I have a fragment, ViewFragment, that consists of two components: a Spinner and a ListView. The Spinner is a default Spinner with a regular ArrayAdapter that fills the Spinner with values taken from an ArrayList called "metaList" that is basically and ArrayList of objects that each themselves have an ArrayList called "mainList".
The ListView has the custom ArrayAdapter (called "CustomAdapter") I mentioned in the title. The CustomAdapter fills the ListView with values taken from the "mainList" of the selected item in the Spinner.
Now, the CustomAdapter for the ListView has two buttons. The only button that does anything yet (called "viewButton") creates a DialogFragment when pushed.
The problem I am having relates to this DialogFragment. Each item in the ListView should create a unique DialogFragment when its viewButton is pressed. However, regardless of which item in the ListView has its button pressed, the DialogFragment is always the same and displays the DialogFragment for the last item in the ListView.
I think that this problem is due to the fact that the ListView items themselves are not being pressed, only their buttons, so the position value in the getView() method for the CustomAdapter never really changes.
How do I fix this problem?
Here is the code for the ViewFragment that contains the Spinner and ListView components:
package com.statbot;
import java.util.ArrayList;
import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.AdapterView.OnItemSelectedListener;
public class ViewFragment extends Fragment{
static int spinPos;
private ArrayList<String> names;
private ArrayList<String> nums;
private Spinner spinner;
private View view;
private ViewGroup container;
private ListView listView;
private Fragment frag;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = inflater.inflate(R.layout.view_layout, container, false);
this.container = container;
frag = this;
names = new ArrayList<String>();
nums = new ArrayList<String>();
spinner = (Spinner)view.findViewById(R.id.view_frag_spinner1);
listView = (ListView) view.findViewById(R.id.view_frag_listView2);
for(int k = 0; k < MainActivity.metaList.size(); k++)
{
names.add(MainActivity.metaList.get(k).getName());
}
ArrayAdapter<String> adapter = new ArrayAdapter<String> (container.getContext(),android.R.layout.simple_spinner_item, names);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(new SpinListener());
return view;
}
private class SpinListener extends Activity implements OnItemSelectedListener{
#Override
public void onItemSelected(AdapterView<?> parent, View view, int pos,
long id) {
// TODO Auto-generated method stub
spinPos = pos;
nums.clear();
for(int k = 0; k < MainActivity.metaList.get(pos).mainList.size(); k++)
{
nums.add("" + MainActivity.metaList.get(pos).mainList.get(k).quantity);
}
CustomAdapter adapter2 = new CustomAdapter(container.getContext(), android.R.layout.simple_spinner_item, nums);
adapter2.setFrag(frag);
listView.setAdapter(adapter2);
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
// TODO Auto-generated method stub
spinPos = 0;
}
}
}
Here is the code for the CustomAdapter:
package com.statbot;
import java.util.List;
import android.app.Fragment;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
public class CustomAdapter extends ArrayAdapter{
private TextView quantity;
private ImageButton editButton;
private ImageButton deleteButton;
private ImageButton viewButton;
private Context mContext;
private int id;
private Fragment frag;
private int pos;
private List<String> objects;
#SuppressWarnings("unchecked")
public CustomAdapter (Context context, int textViewResourceId, List<String> objects){
super(context, textViewResourceId, objects);
mContext = context;
id = textViewResourceId;
this.objects = objects;
}
public View getView(int position, View v, ViewGroup parent){
this.pos = position;
final int POSITION = position;
View mView = v;
if (mView == null){
LayoutInflater vi = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mView = vi.inflate(R.layout.cust_list_view, parent, false);
}
editButton = (ImageButton) mView.findViewById(R.id.cust_list_view_button1);
viewButton = (ImageButton) mView.findViewById(R.id.cust_list_view_button2);
quantity = (TextView) mView.findViewById(R.id.cust_list_view_textView1);
if(quantity == null)
{
System.out.println("The poop is a lie");
}
quantity.setText("" + pos + "," + ViewFragment.spinPos);
viewButton.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
System.out.println("" + POSITION);
showContents(ViewFragment.spinPos, POSITION);
}
});
return mView;
}
private void showContents(int metaPos, int mainPos){
NumStatDialog contents = new NumStatDialog();
contents.setMainPosition(pos);
contents.setMetaPosition();
contents.show(frag.getFragmentManager(), "content_dialog");
}
public void setFrag(Fragment f){
frag = f;
}
}
Here is the code for the DialogFragment, called NumStatDialog:
package com.statbot;
import android.annotation.SuppressLint;
import android.app.DialogFragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
#SuppressLint("ValidFragment")
public class NumStatDialog extends DialogFragment{
private View view;
private int metaPosition;
private int mainPosition;
private TextView title;
private TextView info;
private boolean type;//if true, sets up a dialog to display information on a list. if false, displays information on an item of that list.
public NumStatDialog(int metaPos, int mainPos){
type = false;
metaPosition = metaPos;
mainPosition = mainPos;
}
public NumStatDialog(int metaPos){
type = true;
metaPosition = metaPos;
}
public NumStatDialog(){
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = inflater.inflate(R.layout.numstat_dialog_layout, container);
info = (TextView) view.findViewById(R.id.numstat_dialog_textView2);
title = (TextView) view.findViewById(R.id.numstat_dialog_textView1);
if(!type){
//info.setText((CharSequence) MainActivity.metaList.get(metaPosition).mainList.get(mainPosition).toString());
info.setText((CharSequence) ("" + mainPosition));
//title.setText((CharSequence) ("" + MainActivity.metaList.get(metaPosition).mainList.get(mainPosition).quantity));
title.setText((CharSequence) ("" + metaPosition));
}
if(type){
info.setText((CharSequence) MainActivity.metaList.get(metaPosition).toString());
title.setText((CharSequence) MainActivity.metaList.get(metaPosition).getName());
}
return view;
}
public void setMainPosition(int mp){
mainPosition = mp;
}
public void setMetaPosition(){
metaPosition = ViewFragment.spinPos;
}
}
and here are some screenshots to make things easier to understand. The spinner is at the top with the ListView directly below it. The first number of each ListView item is its position, and the second number is the position of the Spinner. The button of interest is the paper-shaped one. ignore the pencil, it doesn't do anything yet.
and here is a DialogFragment. The title is the Spinner position and the bottom number is the position of the item whose button was pushed, but notice that even though the 1st item on the ListView had its button pushed to display this dialog, the number at the bottom of the fragment displays a three for the 4th item.
Any help on this problem would be greatly appreciated, this is one of the last major hurdles I need to overcome for this project, everything else should be pretty basic.
Thanks!
In your showContents(), you completely ignore the two parameters passed in and instead use this.pos, which would indeed be the position of the last view inflated by the ListView. Just use your parameters (and set up your IDE to warn you about unused variables and parameters).
On a different note, passing data to a fragment like this would not survive configuration changes (unless you have setRetainInstance everywhere, but that's riddled with different sorts of problems). Consider using setArguments, serializing state in onSaveInstanceState and deserializing it in onCreate (using getArguments or savedInstanceState, if that's available).
I would like to load only selected fields from my ArrayList to ListView.
I couldn't find that example so I ask.
I have an ArrayList of a structure as follows:
ArrayList<LogInfo> logInfoArray
Where LogInfo class have fields as follows:
public ArrayList<Point[][]> strokes;
public LinkedList<byte[]> codes;
public int[] times; //contains fields of calendar class
I want to put in my ListView in each row selected fields from "times" and "codes"
How can I achieve that? I would like to use a cursor, if possible.
You can use a custom adapter extending from ArrayAdapter to which you can pass the ArrayList<LogInfo> .
You can then ovverride the getView(..) method of the Adapter to set the fields you want in the row of the Listview .
UPDATE
From this example at Android Custom Adapters
import java.util.List;
import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
public class InteractiveArrayAdapter extends ArrayAdapter<LogInfo> {
private final List<LogInfo> list;
private final Activity context;
public InteractiveArrayAdapter(Activity context, List<LogInfo> list) {
super(context, R.layout.rowbuttonlayout, list);
this.context = context;
this.list = list;
}
static class ViewHolder {
protected TextView text1, text2;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
if (convertView == null) {
LayoutInflater inflator = context.getLayoutInflater();
view = inflator.inflate(R.layout.rowbuttonlayout, null);
final ViewHolder viewHolder = new ViewHolder();
viewHolder.text1 = (TextView) view.findViewById(R.id.label1);
viewHolder.text2 = (TextView) view.findViewById(R.id.label2);
view.setTag(viewHolder);
} else {
view = convertView;
}
ViewHolder holder = (ViewHolder) view.getTag();
holder.text1.setText(list.get(position).getName1());
holder.text2.setText(list.get(position).getName2());
return view;
}
}