Strange NullPointerException in my own ArrayAdapter - java

I'm beginner in programming in Android Studio and I'm making now some kind of messenger via bluetooth. So I have my own ArrayAdapter class which extends ArrayAdapter class and it is for outgoing and incoming messages. I want incoming messages to be at the left side ang outgoing ones at the right, so I made two layouts for this Adapter. I know, that on stackoverflow there is a lot of solutions to make ArrayAdapter with few diffrent layouts for each row, but every one of them doesn't work - changing layouts cause change view of every row. So my solution is to make another ArrayList of booleans, and in getView() I check what I have in this List - true or false - and use right layout on that row in ArrayAdapter (I'm checking it by position field from getView()). And when I send a lot of messages to second device and try to response to first device there is NullPointerException in line with
(TextView)row.findViewById(R.id.singleIncomingMessage);
or
(TextView)row.findViewById(R.id.singleOutgoingMessage);
This exceptions seems to appear in random situation, but of course there must be some pattern. Here's the whole code. What it's wrong? And I'm sorry for my language if there is some misspells ;)
public class MyArrayAdapter extends ArrayAdapter<String> {
public MyArrayAdapter(Context context, ArrayList<String> list) {
super(context, 0, list);
this.list =list;
this.context=context;
}
ArrayList<String> list;
Context context;
#Override
public int getCount() {
return list.size();
}
#Override
public String getItem(int position) {
return list.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
if(Messenger.inOut){
return 1;
}
else{
return 0;
}
}
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
int type=getItemViewType(position);
View row=convertView;
if(row==null){
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if(Messenger.inOutList.get(position)==0){
row=inflater.inflate(R.layout.outgoing_message_layout, parent, false);
}
if(Messenger.inOutList.get(position)==1){
row=inflater.inflate(R.layout.incoming_message_layout, parent, false);
}
}
String message=getItem(position);
TextView label;
if(Messenger.inOutList.get(position)==0){
label=(TextView)row.findViewById(R.id.singleOutgoingMessage);
label.setText(message);
}
if(Messenger.inOutList.get(position)==1){
label=(TextView)row.findViewById(R.id.singleIncomingMessage);
label.setText(message);
}
return row;
}
}

I think the issue is from the case where row is non-null. It may have previously been inflated as an outgoing message layout, now you are recycling it and trying to treat it as an incoming message layout, so it can't find the TextView.
It's hard to say for sure this is the issue though since I don't really know how the Messenger.* calls behave in your code. Since you already get type in getView you should use that rather than Messenger.inOutList.get(position)==X to determine which view logic to use if you keep it this way. This question has some good answers on how to do this consistently.
Also keep in mind that for this to work getItemViewType must always return the same type for a given position, or else you have to detect the change and inflate a new layout in getView. If Messenger.inOut is constant, there's not reason to use this format (multi-layout format). If it's not a constant and it gets changed, then you need to detect this in getView

Okay, thank you very much, I finally did it. I was using two list (of booleans and messages) and after I read about ArrayAdapter I decided to make class with this two fields and make a list of its object. That error was probably because of that second list of booleans. Here's below my final code if anyone have similiar problem.
public class MyArrayAdapter extends ArrayAdapter<MessageWithType> {
public MyArrayAdapter(Context context, ArrayList<MessageWithType> list) {
super(context, 0, list);
this.list =list;
this.context=context;
}
public static final int TYPE_OUT = 0;
public static final int TYPE_IN = 1;
ArrayList<MessageWithType> list;
Context context;
#Override
public int getCount() {
return list.size();
}
#Override
public MessageWithType getItem(int position) {
return list.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
if(list.get(position).inOut){
return 1;
}
else{
return 0;
}
}
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
MessageWithType item=getItem(position);
int type=getItemViewType(position);
View row=convertView;
ViewHolder viewHolder=null;
if(row==null){
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if(type==TYPE_OUT){
row=inflater.inflate(R.layout.outgoing_message_layout, parent, false);
}
if(type==TYPE_IN){
row=inflater.inflate(R.layout.incoming_message_layout, parent, false);
}
TextView label=(TextView)row.findViewById(R.id.singleMessage);
viewHolder=new ViewHolder(label);
row.setTag(viewHolder);
}
else{
viewHolder=(ViewHolder)row.getTag();
}
viewHolder.textView.setText(item.message);
return row;
}
public class ViewHolder{
TextView textView;
public ViewHolder(TextView textView){
this.textView=textView;
}
}
}

Related

Android databinding in an expandable listview

I have a very specific Question. I am using the android databinding library:
https://developer.android.com/topic/libraries/data-binding/index.html
I have a datamodel like this:
Class Participant
Public String Name;
//and so on
Public ObservableArrayList<Drink> DrinkList;
End Class
Class Drink extends BaseObservable
#Bindable
Public String Name;
#Bindable
Public Float Price;
End Class
ViewModel:
Class ParticipantList
public ObservableArrayList<Participant> list = new ObservableArrayList<>();
//some Methods to add items and concstructor
I use also Getter and Setter for all #Bindable fields.
Now I have an activity with an expandablelistview where I bind an adapter to the expandablelistview via XML and an custom #BindingAdapter like this:
//in XML for the layout
<layout xmlns:bind="http://schemas.android.com/apk/res-auto"
<data>
<variable name="Participants" type="com.example.ParticipantList"/>
</data>
<ExpandableListview>
<bind:participants=Particpants.list>
In a binding helper Class this static method
#BindingAdapter("participants")
public static void bindList(ExpandableListView view, ObservableArrayList<Participant> list) {
ParticipantListAdapter adapter = new ParticipantListAdapter(list);
view.setAdapter(adapter);
}
and finally my adapter:
Class ParticpantListAdapter Extends ExpandableListAdapter {
public ObservableArrayList<Participant> list;
#Override
public View getGroupView(..)
ParticipantListItemBinding binding = DataBindingUtil.inflate(inflater,R.layout.participant_list_item, parent, false);
binding.setParticipant(list.get(groupposition));
return binding.getRoot();
}
#Override
public View getChildView(..) {
DrinkListItemBinding binding = DataBindingUtil.inflate(inflater, R.layout.drink_list_item, parent, false);
binding.setDrink(list.get(goupposition).DrinkList.get(childposition));
return binding.getRoot();
}
I have 2 more xml LayoutFiles with data binding layout.drink_list_item + layout.participant_list_item which are bound to the participant and to the drink class.
I skipped a lot of code which is not really important here.
All is working fine except that I have a button in my layout.participant_list_item which will open a new activity where I can add a new drink to my drinklist in my dataclasses. When I finish the activity a new child should be added to the group. But it will only show after I collapse and expands the group once. Usually databinding does this for me when I call NotifyPropertychanged for a #Bindable field or if I add a new item to a directly bound ObservableArrayList. But this is not the case. I add an item to an ObservableArrayList of an item in an ObservableArrayList. Is there a way to refresh only the children of that group? Or is my approach somehow wrong?
My dirty solution is at the moment that I tell my main binding of the activity in its onResume event that if a certain condition is met, that it should refresh all bindings which leads to a complete redraw of the ExpandableListView Activity and all items will be collapsed again (which is not a desired behavior).
I wrote all the code from my memory since I m not at my development place at moment. But this problem bothers me since days and I could not find a decent example of Databinding and ExpandableListView so far. If you need more information just ask.
I am glad for any hints which helps me to find a nice solution.
This is my first question here so please be kind if the post is not meeting all the requirements.
Thank you in advance
//Edit. Edited some Code. Is anybody here that has any helpful comment? Do you need more code or how can I get some tips..
Try this..
public class IExpandableListViewAdapter extends BaseExpandableListAdapter {
#Override
public int getGroupCount() {
return 0;
}
#Override
public int getChildrenCount(int i) {
return 0;
}
#Override
public Object getGroup(int i) {
return null;
}
#Override
public Object getChild(int i, int i1) {
return null;
}
#Override
public long getGroupId(int i) {
return 0;
}
#Override
public long getChildId(int i, int i1) {
return 0;
}
#Override
public boolean hasStableIds() {
return false;
}
#Override
public View getGroupView(int i, boolean b, View view, ViewGroup viewGroup) {
if(view == null) {
parentBinding = DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()), R.layout.basket_list_layout, viewGroup, false);
view = parentBinding.getRoot();
}
else {
parentBinding = (BasketListLayoutBinding)view.getTag();
}
parentBinding.setBasketParentModel(parent.get(i));
view.setTag(parentBinding);
return view;
}
#Override
public View getChildView(int i, int i1, boolean b, View view, ViewGroup viewGroup) {
final int index = i;
if(view == null) {
childBinding = DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()), R.layout.basket_detail_layout, viewGroup, false);
view = childBinding.getRoot();
}
else {
childBinding = (BasketDetailLayoutBinding) view.getTag();
}
childBinding.setBasketChildModel(parent.get(i).getBasketChildModel());
view.setTag(childBinding);
return view;
}
#Override
public boolean isChildSelectable(int i, int i1) {
return false;
}
}

Fitting Constructor for a Custom adapter

I am trying to create a custom adapter, I have an error saying there is no default constructor available
public class GuessAdapter extends ArrayAdapter <Game> {
Context context;
int resource;
Peg[] guess;
LayoutInflater inflater;
public void PegArrayAdapter(Peg[] array, Context ctxt){
guess= array;
context = ctxt;
inflater = (LayoutInflater) ctxt.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public int getCount() {
return guess.length;
}
#Override
public Game getItem(int arg0){
return guess[arg0];
}
public long getItemId(int arg0){
return arg0;
}
#Override
public View getView(int arg0, View arg1, ViewGroup arg2){
View view = arg1;
if (arg1==null){
arg1 = inflater.inflate(android.R.layout.simple_list_item_1, arg2, false);
}
ImageView imageView =(ImageView)arg1.findViewById(R.id.imageView);
ImageView imageView2=(ImageView)arg1.findViewById(R.id.imageView2);
ImageView imageView3=(ImageView)arg1.findViewById(R.id.imageView3);
ImageView imageView4=(ImageView)arg1.findViewById(R.id.imageView4);
return view;
}
}
what would the fitting constructor for this adapter be?
this
public void GuessAdapter(Peg[] array, Context ctxt){
super(ctxt, 0, array);
}
You haven't added any constructor to your custom adapter. You need to make a constructor which would call super.
Add this method in your custom adapter
public GuessAdapter(Peg[] array, Context ctxt) {
super(ctxt, 0, array);
guess= array;
context = ctxt;
inflater = (LayoutInflater) ctxt.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
Also, there is no use of method PegArrayAdapter, you can remove this afterwards.
The Best way of making custom Adapter is to use BASEADAPTER.
There will be no issue in that.
Just extend your CustomAdapter class to BaseAdapter.
I hope it works fine.

The method is being applied wrong elements in listview

I have a problem about listview. Each item of listview have an imageview and a textview. I extended BaseAdapter class for Listview adapter and overrided some methods that I must override. By the way I want to shrink the size of text in the textview if greater than 25. For this reason I created a method whose name is "shrinkText()". When I execute the application first time, this method works correctly.So the textviews whose size of text grater than 25 have been shrinked and other textviews keep their size. However, when I scrool down the listview, textviews that their text size less than 25 have been shrinked too. What should I do to fix this? Thanks
My listview adapter...
public class ListAdapter extends BaseAdapter{
private final ProgramInfo values;
private LayoutInflater mInflater;
public ListAdapter(Context context, ProgramInfo values) {
this.values = values;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount(){
return values.getSize();
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.brd_stream_list_item,parent,false);
viewHolder = new ViewHolder();
viewHolder.p_Name = (TextView)convertView.findViewById(R.id.prgName);
viewHolder.p_Image = (ImageView)convertView.findViewById(R.id.prgImage);
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.p_Name.setText(values.programNames.get(position));
viewHolder.p_Image.setImageResource(R.drawable.alarm_clock_ed);
CharSequence text = viewHolder.p_Name.getText();
shrinkText(text,viewHolder.p_Name); //Call for shrink
}
My shrinkText() method..
private void shrinkText(CharSequence text, TextView v){
if(text.length()>25){
v.setTextSize(TypedValue.COMPLEX_UNIT_SP, 11);
}
}
You need to add the else statement :
private void shrinkText(CharSequence text, TextView v){
if(text.length()>25){
v.setTextSize(TypedValue.COMPLEX_UNIT_SP, 11);
}
else {
v.setTextSize(TypedValue.COMPLEX_UNIT_SP, 25); //the default text size
}
}
A tips : to avoid bug, always use if-else statement in getView (not only if).

java.lang.IllegalArgumentException: Can't have a viewTypeCount < 1

I'm getting this error:
java.lang.IllegalArgumentException: Can't have a viewTypeCount < 1
I'm pretty sure I know exactly what's causing it, but I don't know how to fix it.
My app loads a users friends from the database. When the user has at least 1 friend to put in the list view, it's fine. When the user is brand new and has no friends yet, the app crashes because the listview has a count of 0.
Is this simply a case of error handling?
If I don't post all the necessary relevant code please let me know!
Here is my adapter:
public class MyAdapter extends ArrayAdapter<HashMap<String, String>> {
Context context;
int resourceId;
LayoutInflater inflater;
private Context mContext;
#Override
public int getViewTypeCount() {
return getCount();
}
#Override
public int getItemViewType(int position) {
return position;
}
ArrayList<HashMap<String, String>> items;
public MyAdapter (Context context, int resourceId, ArrayList<HashMap<String, String>> items)
{
super(context, resourceId, items);
mContext = context;
this.items =items;
inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public View getView(int position, View convertView, ViewGroup parent){
final ViewHolder holder;
if (convertView == null){
convertView = inflater.inflate(R.layout.list_item, null);
holder = new ViewHolder();
holder.fbphoto = (ImageView)convertView.findViewById(R.id.fbphoto);
holder.name = (TextView)convertView.findViewById(R.id.name);
convertView.setTag(holder);
} else {
holder = (ViewHolder)convertView.getTag();
}
final HashMap<String,String> item = (HashMap<String,String> ) items.get(position);
if (item != null)
{
String facebookProfilePicUrl = "https://graph.facebook.com/"+item.get(TAG_FACEBOOKID)+"/picture?width=150&height=150";
Picasso.with(mContext)
.load(facebookProfilePicUrl)
.placeholder(R.drawable.no_image)
.into(holder.fbphoto);
holder.name.setText(item.get(TAG_USERNAME));
}
return convertView;
}
public class ViewHolder
{
ImageView fbphoto;
TextView name;
}
}
I think you miss the point of ViewTypeCount. You should return the number of Different View Types in your list.
This is important for recycling of the Views inside the List.
Imaging you have 2 Types of Listitems, one with a white Background and one with black Background. When you return 2 as ViewTypeCount the Listview knows ok, there a 2 types of Listitems and will not mix them up in the getView view recycling.
so just use:
public int getViewTypeCount() {
return 1;
}
or dont override that method at all.
Use this
#Override
public int getViewTypeCount() {
if(getCount() > 0){
return getCount();
}else{
return super.getViewTypeCount();
}
}
getViewTypeCount returns the number of different types of views this adaptor can return. Since you're only returning one type of view, this should just return 1.
Change this function in your Adapter class:
#Override
public int getViewTypeCount() {
return getCount();
}
to:
public int getViewTypeCount() {
if(getCount()<1) return 1;
return getCount();
}
**note: avoid #Override

getView is never called for reasons beyond me

I have a custom list view adapter populated through an asynctask, I'm calling notifydatasetchanged in the onprogress function, and getCount() returns 10, yet my list never shows, Ive set a breakpoint and determined that getView() simply never is called. any ideas? Ive tried for hours and Im just stumped. Ive done the exactly same thing in another activity except that one used viewholders, this one only holds text based data so I didn't bother.
Adapter:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
if(row == null) {
row = inflater.inflate(R.layout.podcastepisode, null);
}
PodcastItem item = items.get(position);
TextView episodeTitle = (TextView)row.findViewById(R.id.episodeTitle);
TextView episodeDate = (TextView)row.findViewById(R.id.episodeDate);
episodeTitle.setText(item.title);
episodeDate.setText(API.FormatPodcastDate(item.date));
return row;
}
My task:
protected void onProgressUpdate(PodcastItem... progress) {
AddPodcastActivity.episodes.add(progress[0]);
AddPodcastActivity.adapter.notifyDataSetChanged();
}
I'd recommend moving your list adapter from inside the Activity file to it's own file, and using something like this:
import java.util.ArrayList;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
public class EpisodeArrayAdapter extends BaseAdapter {
public EpisodeArrayAdapter(Context context) {
mContext = context;
items = new ArrayList<PodcastItem>();
}
private Context mContext;
private ArrayList<PodcastItem> items;
public void add(PodcastItem item) {
items.add(item);
notifyDataSetChanged();
}
public void remove(int index) {
items.remove(index);
notifyDataSetChanged();
}
public void clear() {
items.clear();
notifyDataSetChanged();
}
#Override
public int getCount() { return items.size(); }
#Override
public Object getItem(int position) { return items.get(position); }
#Override
public long getItemId(int position) { return position; }
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
if(row == null) row = LayoutInflater.from(mContext).inflate(R.layout.podcastepisode, null);
PodcastItem item = items.get(position);
TextView episodeTitle = (TextView)row.findViewById(R.id.episodeTitle);
TextView episodeDate = (TextView)row.findViewById(R.id.episodeDate);
episodeTitle.setText(item.title);
episodeDate.setText(API.FormatPodcastDate(item.date));
return row;
}
}
This is the type of code we use for all the list adapters in Boid :) Also, notice that the add/remove/clear functions call notifyDataSetChanged(), which makes it so you don't have to call it yourself when adding items.
When you initialize it, you would just use:
EpisodeArrayAdapter adapter = new EpisodeArrayAdapter(this);
listView.setAdapter(adapter);
Adding items with the add() function will cause the list to update immediately. Make sure you call setAdapter for the list view that's using the adapter, otherwise there won't be any connection and nothing will show up in the list (didn't see a call to this in your code).

Categories