Im building an listview with sections. i was using this answer of a post and asked a question before, but am stuck again. I think it is a pretty weird error.
When i start my activity, i can see the list on the screen, just as i want it. But the moment i try to start scrolling the activity crashes. I thought i implemented everything the same way, but apparently im not.
My adapter:
public class DelftAdapter extends BaseAdapter {
private static final int TYPE_ITEM = 0;
private static final int TYPE_SECTION = 1;
private Activity activity;
private List<ListItem> listItems;
private static LayoutInflater inflater=null;
public ImageLoader imageLoader;
private final int[] bgColors = new int[] { R.color.list_odd, R.color.list_even };
public DelftAdapter(Activity a, ArrayList<ListItem> li) {
activity = a;
listItems = li;
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader=new ImageLoader(activity.getApplicationContext());
}
public int getCount() {
return listItems.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return listItems.get(position).isSection() ? TYPE_SECTION : TYPE_ITEM;
}
#Override
public int getViewTypeCount() {
return 2; // sectionheader and regular item
}
public View getView(int position, View convertView, ViewGroup parent) {
int type = getItemViewType(position);
View vi=convertView;
final ListItem li = listItems.get(position);
ItemViewHolder itemHolder;
SectionViewHolder sectionHolder;
switch (type) {
case TYPE_SECTION: // is sectionheader
if (vi == null) { //convertview==null
sectionHolder = new SectionViewHolder();
vi = inflater.inflate(R.layout.sectionedlistitem, null);
vi.setOnClickListener(null);
vi.setOnLongClickListener(null);
vi.setLongClickable(false);
sectionHolder.title = (TextView) vi.findViewById(R.id.list_header_title);
}else{//convertview is not null
sectionHolder = (SectionViewHolder)vi.getTag();
}
SectionItem si = (SectionItem)li;
sectionHolder.title.setText(si.getTitle());
break;
case TYPE_ITEM:// no sectionheader
if (vi == null) { //convertview==null
itemHolder = new ItemViewHolder();
vi = inflater.inflate(R.layout.singlelistitem, null);
itemHolder.name=(TextView)vi.findViewById(R.id.tvname);
itemHolder.tip=(TextView)vi.findViewById(R.id.tvtip);
itemHolder.image=(ImageView)vi.findViewById(R.id.image);
}else{ // convertview != null
itemHolder = (ItemViewHolder)vi.getTag();
}
ListData ld = (ListData)li;
itemHolder.name.setText(ld.name);
itemHolder.tip.setText(ld.tip);
if (ld.photoUrl != null ){
imageLoader.DisplayImage(ld.photoUrl, itemHolder.image);
}else{
itemHolder.image.setImageURI(Uri.fromFile(new File("//assets/nopic.png")));
}
// alternating colors
int colorPos = position % bgColors.length;
vi.setBackgroundResource(bgColors[colorPos]);
break;
}
return vi;
}
public static class SectionViewHolder {
public TextView title;
}
public static class ItemViewHolder {
public TextView name;
public TextView tip;
public ImageView image;
}
}
I build two ViewHolders for the two different kind of views. The error that occurs is NullPointerException on the itemHolder.name.setText(ld.name); line.
The thing i don't get is that the code works for the first few entrys but fails when i start scrolling. In the data i'm using, name and tip are never empty, only photoUrl might be but that is covered in the code.
Anyone knows why this piece of code is failing?
In the code paths where you create a new viewholder and inflate a new view, you never actually store the viewHolder in the Views tag, so when you scroll and get an exisitng view, view.gettag() returns null, and later when you try and use the ViewHolder you get the Null Pointer Exception. You need to add the calls to setTag().
Related
I am working on a project which require me to develop a autocomplete input suggestion box the problem is the suggestion items are categorized they each category is supposed to be highlighted and unclickable.
I have implemented custom ArrayAdapter for this purpose but could't figure out how to make categories un clickable
here is my custom array adapter code
public class CustomAutoCompleteAdapter extends ArrayAdapter<String> {
private static final int TYPE_ITEM = 0;
private static final int TYPE_CATEGORY = 1;
private TreeSet<Integer> sectionHeader = new TreeSet<Integer>();
private LayoutInflater mInflater;
public CustomAutoCompleteAdapter(#NonNull Context context, #LayoutRes int resource, #NonNull List<String> objects) {
super(context, resource, objects);
mInflater = LayoutInflater.from(context);
}
public int getItemViewType(int position) {
return sectionHeader.contains(position) ? TYPE_CATEGORY : TYPE_ITEM;
}
public void addHeader(String item) {
super.add(item);
sectionHeader.add(this.getCount() - 1);
}
#Override
public int getViewTypeCount() {
return 2;
}
#NonNull
#Override
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
ViewHolder holder = null;
if(convertView==null){
int type = getItemViewType(position);
holder = new ViewHolder();
if(type == TYPE_ITEM){
convertView = mInflater.inflate(R.layout.item_layout,null);
holder.textView = (TextView) convertView.findViewById(R.id.text);
}
else{
convertView = mInflater.inflate(R.layout.category_layout,null);
holder.textView = (TextView) convertView.findViewById(R.id.category);
convertView.setClickable(false);
}
convertView.setTag(holder);
}
else {
holder = (ViewHolder) convertView.getTag();
}
holder.textView.setText(this.getItem(position));
return convertView;
}
private static class ViewHolder {
public TextView textView;
}
}
In your CustomAutoCompleteAdapter, override isEnabled() method to disable view type TYPE_CATEGORY.
Add below code in your CustomAutoCompleteAdapter :
#Override
public boolean isEnabled(int position)
{
return ((getItemViewType(position) != TYPE_CATEGORY));
}
Hope this will help~
Try to remove focus from view by setting your convertview and textview both "unfocusable()". like this:
convertView.setFocusable(false);
holder.textview.setFocusable(false);
Hope it'll work!
I want to implement the two cells type in my app Image and text so i'm just testing this, my app works fine and load perfectly but tis crashes when i start scrolling with the following error
java.lang.ArrayIndexOutOfBoundsException: length=2; index=2130968624
this is my adapter
public class myadapter extends ArrayAdapter<myobject> {
Context context;
List<myobject> objectlist;
public myadapter(Context context, int resource, List<myobject> objects) {
super(context, resource, objects);
this.context = context;
this.objectlist = objects;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder2 tvholder;
ViewHolder ivholder;
LayoutInflater inflater =(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (getItemViewType(position) == R.layout.textcell){
if(convertView==null){
convertView= inflater.inflate(getItemViewType(position),parent,false);
tvholder= new ViewHolder2(convertView);
convertView.setTag(tvholder);
}else{
tvholder = (ViewHolder2)convertView.getTag();
}
tvholder.TV.setText(objectlist.get(position).getText());
}else{
if(convertView==null){
convertView= inflater.inflate(getItemViewType(position),parent,false);
ivholder = new ViewHolder(convertView);
convertView.setTag(ivholder);
}else{
ivholder=(ViewHolder) convertView.getTag();
}
ivholder.IV.setImageResource(objectlist.get(position).getImage());
}
return convertView;
}
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public int getItemViewType(int position) {
if(objectlist.get(position).getType()==1){
return R.layout.textcell;
}else{
return R.layout.imagecell;
}
}
}
class ViewHolder{
ImageView IV;
public ViewHolder(View view){
IV = (ImageView)view.findViewById(R.id.IV);
}
}
class ViewHolder2{
TextView TV;
public ViewHolder2(View view){
TV = (TextView)view.findViewById(R.id.TV);
}
}
my object :
public class myobject {
int type;
String Text;
int Image;
public myobject(){
this.type = 0;
this.Text = null;
this.Image = 0;
}
public myobject(int type,String Text,int Image){
this.type=type;
this.Image = Image;
this.Text = Text;
}
public int getType(){ return type;}
public String getText (){return Text;}
public int getImage (){return Image;}
}
Use in Main Activity :
public class MainActivity extends AppCompatActivity {
ListView LV;
List<myobject> myobjectList = new ArrayList<>();
myadapter myadapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LV = (ListView) findViewById(R.id.LV);
myobject myobject1 = new myobject(1,"teeeeeexxxxt",0);
myobject myobject2 = new myobject(2,null,R.mipmap.ic_launcher);
myobject myobject3 = new myobject(1,"teeeeeexxxxt",0);
myobject myobject4 = new myobject(1,"teeeeeexxxxt",0);
myobject myobject5 = new myobject(2,null,R.mipmap.ic_launcher);
myobject myobject6 = new myobject(2,null,R.mipmap.ic_launcher);
myobject myobject7 = new myobject(2,null,R.mipmap.ic_launcher);
myobject myobject8 = new myobject(1,"teeeeeexxxxt",0);
myobject myobject9 = new myobject(1,"teeeeeexxxxt",0);
myobjectList.add(myobject1);
myobjectList.add(myobject2);
myobjectList.add(myobject3);
myobjectList.add(myobject4);
myobjectList.add(myobject5);
myobjectList.add(myobject6);
myobjectList.add(myobject7);
myobjectList.add(myobject8);
myobjectList.add(myobject9);
myadapter = new myadapter(this,0,myobjectList);
LV.setAdapter(myadapter);
}
I really don't know what is causing this fatal error + is my implementation consider to be the most optimize approach for the 2 different cells type ?
any ideas would be much appreciated
Before solving your issue I want to clarify something first. While using the ArrayAdapter you don't have to store yourself the list of objects or the context, since it is done for you when the adapter is created. that being said, every call like objectlist.get(position) would be something like this getItem(position), and to obtain the context you simply call getContext.
Now let's explore the issue. According to Adapter class documentation, the getItemViewType(int position) method has to return a value between 0 and getViewTypeCount() - 1.
An integer representing the type of View. Two views should share the same type if one can be converted to the other in getView(int, View, ViewGroup). Note: Integers must be in the range 0 to getViewTypeCount() - 1. IGNORE_ITEM_VIEW_TYPE can also be returned.
The problem in you code is that you are returning the id of a layout file, which usually is a very big number, and thus IndexOutOfBounds is thrown.
A possible solution for this would be to return the getItem(position).getType() - 1 instead of what you are currently returning. After this, the getView method can be changed to match the different layouts and view holders.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder2 tvholder;
ViewHolder ivholder;
LayoutInflater inflater =(LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
switch (getItemViewType(position)) {
case 0:
convertView = inflater.inflate(R.layout.textcell, null);
tvholder = new ViewHolder2(convertView);
tvholder.TV.setText(getItem(position).getText());
convertView.setTag(tvholder);
break;
case 1:
convertView = inflater.inflate(R.layout.imagecell, null);
ivholder = new ViewHolder(convertView);
ivholder.IV.setImageResource(getItem(position).getImage());
convertView.setTag(ivholder);
break;
}
} else {
switch (getItemViewType(position)) {
case 0:
tvholder = (ViewHolder2) convertView.getTag();
tvholder.TV.setText(getItem(position).getText());
break;
case 1:
ivholder = (ViewHolder) convertView.getTag();
ivholder.IV.setImageResource(getItem(position).getImage());
break;
}
}
return convertView;
}
The app works ok, but computing each listview entry takes some time (I'm doing a ping on network devices) so I'm looking for creating entries on the fly. Although I google for a solution, I'm lost: I can't find where I need to modify my code.
Also I'd like to have a textview or a progressbar to indicate the progress but all the computing is taken by the listview and I only get that ino at the end. Explanations or a tutorial would be much appreciated on these problems.
listIPitem = new ArrayList<>();
iPadaptater = new IPadaptater(getActivity(), listIPitem);
ListView list_ip = (ListView) rootView.findViewById(id.listIP);
list_ip.setAdapter(iPadaptater);
DiscoverNetwork dn = new DiscoverNetwork();
dn.passContext(context, listIPitem, iPadaptater)
Here is my adaptater:
public class IPadaptater extends BaseAdapter {
private List<IPitem> listIPitem;
private LayoutInflater layoutInflater;
Context context;
public IPadaptater(Context c, List<IPitem> objects) {
context = c;
listIPitem = objects;
layoutInflater = LayoutInflater.from(context);
}
public int getCount() {
return listIPitem.size();
}
public Object getItem(int position) {
return listIPitem.get(position);
}
public long getItemId(int position) {
return position;
}
private class ViewIPHolder {
TextView ip_disc;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewIPHolder viewHolder;
if(convertView == null) {
viewHolder = new ViewIPHolder();
convertView = layoutInflater.inflate(R.layout.listview_item_discovery, null);
viewHolder.ip_disc = (TextView) convertView.findViewById(R.id.ip_disc);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewIPHolder) convertView.getTag();
}
viewHolder.ip_disc.setText(listIPitem.get(position).getIPaddress());
notifyDataSetChanged();
return convertView;
}
}
And here is where I process my data:
listIPitem.clear();
for(int i=1;i<50;i++) {
IPitem iPitem = new IPitem();
ip = network + i;
if(!ping(ip)) continue;
iPitem.setIPaddress(ip);
listIPitem.add(iPitem);
iPadaptater.notifyDataSetChanged();
}
I have a dilemma using
if (convertview==null){
(my code)
}
or not. Without this piece of code, my listview is not very fast, it locks for a few ms sometimes and you can easy notice this in use. It just doesn't work how its meant to work.
But when this piece of code, my listitems will start recounting after a while (10 or so) and i have a few returning listitems in my list (with the header i used). I used this tutorial for getting my listview with sections link. The length of the list is good.
Ofcourse my list is totally useless with a view repeating items (nicely sectioned by the way) but i also dont want it to be slow.
Does anyone know what to do?
Below is my adapter:
public class DelftAdapter extends BaseAdapter {
private Activity activity;
private List<ListItem> listItems;
private static LayoutInflater inflater=null;
public ImageLoader imageLoader;
private final int[] bgColors = new int[] { R.color.list_odd, R.color.list_even };
public DelftAdapter(Activity a, ArrayList<ListItem> li) {
activity = a;
listItems = li;
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader=new ImageLoader(activity.getApplicationContext());
}
public int getCount() {
return listItems.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
View vi=convertView;
final ListItem li = listItems.get(position);
if (li != null) {
if(li.isSection()){ // is sectionheader
SectionItem si = (SectionItem)li;
vi = inflater.inflate(R.layout.sectionedlistitem, null);
vi.setOnClickListener(null);
vi.setOnLongClickListener(null);
vi.setLongClickable(false);
final TextView sectionView = (TextView) vi.findViewById(R.id.list_header_title);
sectionView.setText(si.getTitle());
}else{ // no sectionheader
ListData ld = (ListData)li;
vi = inflater.inflate(R.layout.singlelistitem, null);
TextView tvNames=(TextView)vi.findViewById(R.id.tvname);
TextView tvTip=(TextView)vi.findViewById(R.id.tvtip);
ImageView image=(ImageView)vi.findViewById(R.id.image);
tvNames.setText(ld.name);
tvTip.setText(ld.tip);
if (listItems.get(position) != null ){
imageLoader.DisplayImage(ld.photoUrl, image);
}
else{
image.setImageURI(Uri.fromFile(new File("//assets/eten.png")));
}
// alternating colors
int colorPos = position % bgColors.length;
vi.setBackgroundResource(bgColors[colorPos]);
}
}
return vi;
}
}
Consider using getItemViewType() and getViewTypeCount() with recycling convertView. Those are used when you have list items with various layouts. You definitely should recycle convertView.
See also http://android.amberfog.com/?p=296
In your case:
private static final int TYPE_ITEM = 0;
private static final int TYPE_SECTION = 1;
#Override
public int getItemViewType(int position) {
return listItems.get(position).isSection() ? TYPE_SECTION : TYPE_ITEM
}
#Override
public int getViewTypeCount() {
return 2; // sectionheader and regular item
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
int type = getItemViewType(position);
if (convertView == null) {
switch (type) {
case TYPE_ITEM:
convertView = mInflater.inflate(R.layout.singlelistitem, null);
...
break;
case TYPE_SECTION:
convertView = mInflater.inflate(R.layout.sectionedlistitem, null);
...
break;
}
} else {
...
}
return convertView;
}
Also use ViewHolder pattern to achieve better performance.
I have two ListViews, the second view items change according to what the first user chose in the first ListView
When at first the user choose an item from the first list, the second ListView loads without a problem... but when going back to the first ListView, selecting another item, the second ListView gives the Illegal State Exception...
and I have no idea when to notify the ListView about DataSetChanges because it doesn't make sense to notify it before or after the setListAdapter!
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int mIndex = getIntent().getIntExtra("mIndex", 0);
adapter = new mListAdapter(this, getItems(mIndex));
setListAdapter(adapter);
}
Update:
I changed the adapter class and the problem disappeared... This is my old custom class that caused the error... Any idea what is wrong with it?
mListAdapter Class
public class mListAdapter extends BaseAdapter {
private static ArrayList<mItemsHolder> arrayList;
private LayoutInflater mInflater;
Context context;
int textSize;
public mListAdapter (Context m_context, ArrayList<mItemsHolder> results, int mTextSize) {
arrayList = results;
mInflater = LayoutInflater.from(m_context);
context = m_context;
textSize = mTextSize;
}
public int getCount() {
return arrayList.size();
}
public Object getItem(int position) {
return arrayList.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.custom_row, null);
holder = new ViewHolder();
holder.mainItem = (TextView) convertView.findViewById(R.id.row_txt_main);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
Typeface typeFace=Typeface.createFromAsset(context.getAssets(),"fonts/verdana.ttf");
holder.mainItem.setText(arrayList.get(position).getMainItem());
holder.mainItem.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize);
holder.mainItem.setTypeface(typeFace);
return convertView;
}
static class ViewHolder {
TextView mainItem;
}
}
I just removed "static" when declaring my ArrayList in
private static ArrayList<mItemsHolder> arrayList;