• After 15+ years, we've made a big change: Android Forums is now Early Bird Club. Learn more here.

Apps How can I use my variables from my custom adapter?

cdubs

Newbie
Feb 25, 2016
13
1
I am using a list view and creating a custom adapter. I am trying to get information every time the button is clicked. The button being declared is connected with each item in the list view. How can I use that button variable so that I get the actual button being used and not just the first button of the list.

Here is the adapter method:

Code:
class PostsAdapter extends ArrayAdapter<Posts>{

    //used to create views from xml
    private LayoutInflater layoutInflater;

    public PostsAdapter(Context context, int textViewResourceId, List<Posts> posts) {
        super(context, textViewResourceId, posts);
        layoutInflater = LayoutInflater.from(context);
    }

    //add to xml from dataset
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view = layoutInflater.inflate(R.layout.activity_posts, null);
        Posts posts = getItem(position);

        TextView content = (TextView) view.findViewById(R.id.content);
        TextView user = (TextView) view.findViewById(R.id.user);
        TextView topic = (TextView) view.findViewById(R.id.topic);
        TextView date = (TextView) view.findViewById(R.id.date);
        TextView likes = (TextView) view.findViewById(R.id.likeCount);
        TextView id = (TextView) view.findViewById(R.id.hiddenID);
        Button like = (Button) view.findViewById(R.id.btnLike);

        content.setText(posts.getContent());
        user.setText(posts.getUser());
        topic.setText(posts.getTopic());
        date.setText(posts.getDate());
        likes.setText(posts.getLikes());
        id.setText(posts.getId());

        like.setOnClickListener(DisplayPosts.this);

        return view;
    }


}

In my other method I have to redeclare my button variable and this just gets the first button in the list, not the one being clicked on:

Code:
private void createLike(){
    Button like = (Button) findViewById(R.id.btnLike);
    TextView id = (TextView) findViewById(R.id.hiddenID);
    TextView user = (TextView) findViewById(R.id.user);

    final String hid = id.getText().toString().trim();
    final String puser = user.getText().toString().trim();

    if(like.getText().toString().equalsIgnoreCase("like")){
        like.setText("UnLike");
        ...
 
Upvote 0
You're setting up the button onClick listener in the wrong place.
So your basic problem as it stands is that your code is finding only the first Button that matches the id, within the Activity's View. This attaches your onClick listener to the first Button in the list only.

If you want to have a ListView where each row contains a Button (with listener), then the onClick must be attached in the Adapter class. So here, the 'view' variable refers to the View of the row in your ListView

Code:
...
//add to xml from dataset
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
      ...
      Button like = (Button) view.findViewById(R.id.btnLike);
      ...

This code finds the instance of the 'like' Button which corresponds to an individual row. You would attach the onClick listener to this Button.


As an aside: Another major problem with your ListView adapter is that you're not handling View reuse. This whole article is interesting, but in particular you should read this section

http://www.vogella.com/tutorials/AndroidListView/article.html#adapterperformance
 
  • Like
Reactions: cdubs
Upvote 0
You're setting up the button onClick listener in the wrong place.
So your basic problem as it stands is that your code is finding only the first Button that matches the id, within the Activity's View. This attaches your onClick listener to the first Button in the list only.

If you want to have a ListView where each row contains a Button (with listener), then the onClick must be attached in the Adapter class. So here, the 'view' variable refers to the View of the row in your ListView

Code:
...
//add to xml from dataset
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
      ...
      Button like = (Button) view.findViewById(R.id.btnLike);
      ...

This code finds the instance of the 'like' Button which corresponds to an individual row. You would attach the onClick listener to this Button.


As an aside: Another major problem with your ListView adapter is that you're not handling View reuse. This whole article is interesting, but in particular you should read this section

http://www.vogella.com/tutorials/AndroidListView/article.html#adapterperformance

Awesome, I knew why it was doing it but didn't know how to fix it. I have tried to put the onclick in the view class but I am getting errors. Is there a special way to do the onclick in this class?
 
Last edited:
  • Like
Reactions: Deleted User
Upvote 0
So in getView() use

Code:
like.setOnClickListener(this)

and your PostsAdapter class must implement onClickListener
Awesome! So I got the 'like' button changing text on each one that I click.

Code:
class PostsAdapter extends ArrayAdapter<Posts> implements View.OnClickListener {

        //used to create views from xml
        private LayoutInflater layoutInflater;
        Button like;
        TextView likes;
        TextView hiddenId;

        public PostsAdapter(Context context, int textViewResourceId, List<Posts> posts) {
            super(context, textViewResourceId, posts);
            layoutInflater = LayoutInflater.from(context);
        }

        //add to xml from dataset
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view = layoutInflater.inflate(R.layout.activity_posts, null);
            Posts posts = getItem(position);

            TextView content = (TextView) view.findViewById(R.id.content);
            TextView user = (TextView) view.findViewById(R.id.user);
            TextView topic = (TextView) view.findViewById(R.id.topic);
            TextView date = (TextView) view.findViewById(R.id.date);
            likes = (TextView) view.findViewById(R.id.likeCount);
            hiddenId = (TextView) view.findViewById(R.id.hiddenID);

            like = (Button) view.findViewById(R.id.btnLike);

            content.setText(posts.getContent());
            user.setText(posts.getUser());
            topic.setText(posts.getTopic());
            date.setText(posts.getDate());
            likes.setText(posts.getLikes());
            hiddenId.setText(posts.getId());

            like.setOnClickListener(this);

            return view;
        }


        @Override
        public void onClick(View v) {
            like = (Button) v;

            createLike(like, hiddenId, likes);
        }
    }

Now I am trying to pass in the hiddenID and likes so I can access those in the createLike method. However it is not getting the correct hiddenid from the post. It seems to be getting a random one, sometimes the same id when I click like.
 
Upvote 0
That's not going to work. You can't use class variables like this to store the list row Views

Code:
        Button like;
        TextView likes;
        TextView hiddenId;

Let me try to explain why. You don't control when getView() is called. It's called by the system, when it needs to render a row of your ListView. This method just inflates and returns the correct View, for the given position.
(And I really would look at how ListView recycling works, if your ListView contains a lot of items.)

Anyway, each time getView() is called, your code is assigning the above class variables. Each time it will overwrite these variables with what is in the current row to be rendered.

In parallel with all that, your onClick() method is being called asynchronously, whenever you click a button on a row. So when this code accesses class variables 'hiddenId' and 'likes', these variables will almost certainly hold values which are for a different row. Hence you get the wrong values for the row you just clicked.

What you need to use is a technique called 'view tagging'. This allows you to associate some supplementary data structure with the View. You could tag the Button view with a 'Holder' class. The Holder class contains your hiddenId. In the Button's onClick method, you simply retrieve the tag data structure from the Button view using getTag();

Hope I explained this reasonably clearly. As you can see ListViews are quite complex things to deal with!

Here's an outline of what you will need for your Button View tagging. In the following code 'holder' is an instance of the ViewHolder class:

Code:
static class ViewHolder {
   protected TextView hiddenId;
   protected TextView likes;
}

like.setTag(holder);
 
Last edited by a moderator:
  • Like
Reactions: cdubs
Upvote 0
That's not going to work. You can't use class variables like this to store the list row Views

Code:
        Button like;
        TextView likes;
        TextView hiddenId;

Let me try to explain why. You don't control when getView() is called. It's called by the system, when it needs to render a row of your ListView. This method just inflates and returns the correct View, for the given position.
(And I really would look at how ListView recycling works, if your ListView contains a lot of items.)

Anyway, each time getView() is called, your code is assigning the above class variables. Each time it will overwrite these variables with what is in the current row to be rendered.

In parallel with all that, your onClick() method is being called asynchronously, whenever you click a button on a row. So when this code accesses class variables 'hiddenId' and 'likes', these variables will almost certainly hold values which are for a different row. Hence you get the wrong values for the row you just clicked.

What you need to use is a technique called 'view tagging'. This allows you to associate some supplementary data structure with the View. You could tag the Button view with a 'Holder' class. The Holder class contains your hiddenId. In the Button's onClick method, you simply retrieve the tag data structure from the Button view using getTag();

Hope I explained this reasonably clearly. As you can see ListViews are quite complex things to deal with!

Here's an outline of what you will need for your Button View tagging. In the following code 'holder' is an instance of the ViewHolder class:

Code:
static class ViewHolder {
   protected TextView hiddenId;
   protected TextView likes;
}

like.setTag(holder);

Okay I think I am on the right track. I have been working hard on this and I really appreciate all your responses. Here is what I have come up with:

Code:
class PostsAdapter extends ArrayAdapter<Posts> implements View.OnClickListener {
    //used to create views from xml
    private LayoutInflater layoutInflater;

    public PostsAdapter(Context context, int textViewResourceId, List<Posts> posts) {
        super(context, textViewResourceId, posts);
        layoutInflater = LayoutInflater.from(context);
    }
    //add to xml from dataset
    @Override
    // view convertview = recycled view
    public View getView(int position, View convertView, ViewGroup parent) {
        View view = convertView;
        Holder holder = null;
        Posts posts = getItem(position);

        //checks if recycled view is null, thewn creates new view, if not null, use same view
        if(view == null){
            view = layoutInflater.inflate(R.layout.activity_posts, null);

            Button like = (Button) view.findViewById(R.id.btnLike);
            TextView content = (TextView) view.findViewById(R.id.content);
            TextView user = (TextView) view.findViewById(R.id.user);
            TextView topic = (TextView) view.findViewById(R.id.topic);
            TextView date = (TextView) view.findViewById(R.id.date);
            TextView likes = (TextView) view.findViewById(R.id.likeCount);
            TextView hiddenId = (TextView) view.findViewById(R.id.hiddenID);

            holder = new Holder(content, user, topic, date, likes, hiddenId, like);

            view.setTag(holder);
        }
        else{
            holder = (Holder) view.getTag();
        }
        holder.content.setText(posts.getContent());
        holder.user.setText(posts.getUser());
        holder.topic.setText(posts.getTopic());
        holder.date.setText(posts.getDate());
        holder.likes.setText(posts.getLikes());
        holder.hiddenId.setText(posts.getId());

        holder.like.setOnClickListener(this);

        return view;
    }

    -----@Override
  ----- public void onClick(View v) {
        -----like = (Button) v;
        -----Toast.makeText(DisplayPosts.this, "Button: " + like.getText(), 
        -----Toast.LENGTH_LONG).show();
        -----createLike(like);
    -----}
-----}

static class Holder{
    public TextView content;
    public TextView user;
    public TextView topic;
    public TextView date;
    public TextView likes;
    public TextView hiddenId;
    public Button like;

    public Holder(TextView content, TextView user, TextView topic, TextView date, TextView likes, TextView hiddenId, Button like) {
        this.content = content;
        this.user = user;
        this.topic = topic;
        this.date = date;
        this.likes = likes;
        this.hiddenId = hiddenId;
        this.like = like;
    }
}

I put dashes next to the part that I am having trouble with. How do I use the 'like' button variable in the onclick without declaring the class variable 'Button like;' . I hope I am on the right track. Thanks again for your help.
 
Upvote 0
You've called setTag on the wrong View object. You should set it on the like Button.

Then in your onClick method, you don't need to access a class 'like' variable. You use the parameter given to this method 'v' - which is actually the same object which you used in your getView method

So if you use something like the following, it should work

Code:
@Override
public void onClick(View v) {
  Button like = (Button) v;
  Holder holder = (Holder) v.getTag();
  // holder.hiddenId now accessible
  ...
}
 
Upvote 0
You've called setTag on the wrong View object. You should set it on the like Button.

Then in your onClick method, you don't need to access a class 'like' variable. You use the parameter given to this method 'v' - which is actually the same object which you used in your getView method

So if you use something like the following, it should work

Code:
@Override
public void onClick(View v) {
  Button like = (Button) v;
  Holder holder = (Holder) v.getTag();
  // holder.hiddenId now accessible
  ...
}
Again, thank you for your help. I am learning a lot going through all these steps. I know you have done more than what I have asked but I really appreciate the help. Here are the problems I am still encountering.
1) There still seems to be problems with liking one button, and it changing the text on another.
2) When I hit like button, it updates the like, when I scroll down out of view, and scroll back up, the like number is reset to the original number.
3) I needed to keep the view.setTag in order for the app to run. I added like.settag under it.

Here is updated code:
Code:
...
if(view == null){
            view = layoutInflater.inflate(R.layout.activity_posts, null);

            Button like = (Button) view.findViewById(R.id.btnLike);
            TextView content = (TextView) view.findViewById(R.id.content);
            TextView user = (TextView) view.findViewById(R.id.user);
            TextView topic = (TextView) view.findViewById(R.id.topic);
            TextView date = (TextView) view.findViewById(R.id.date);
            TextView likes = (TextView) view.findViewById(R.id.likeCount);
            TextView hiddenId = (TextView) view.findViewById(R.id.hiddenID);

            holder = new Holder(content, user, topic, date, likes, hiddenId, like);

            view.setTag(holder);
            like.setTag(holder);
        }
        else{
            holder = (Holder) view.getTag();
        }
        holder.content.setText(posts.getContent());
        holder.user.setText(posts.getUser());
        holder.topic.setText(posts.getTopic());
        holder.date.setText(posts.getDate());
        holder.likes.setText(posts.getLikes());
        holder.hiddenId.setText(posts.getId());

        holder.like.setOnClickListener(this);

        return view;
    }

    @Override
    public void onClick(View v) {
        Button like = (Button) v;
        Holder holder = (Holder) v.getTag();
        Toast.makeText(DisplayPosts.this, "Button: " + like.getText(), Toast.LENGTH_LONG).show();

        createLike(like, holder.hiddenId, holder.likes);
    }
}

static class Holder{
    public TextView content;
    public TextView user;
    public TextView topic;
    public TextView date;
    public TextView likes;
    public TextView hiddenId;
    public Button like;

    public Holder(TextView content, TextView user, TextView topic, TextView date, TextView likes, TextView hiddenId, Button like) {
        this.content = content;
        this.user = user;
        this.topic = topic;
        this.date = date;
        this.likes = likes;
        this.hiddenId = hiddenId;
        this.like = like;
    }
}

Here is createLike():

Code:
private void createLike(final Button like, final TextView hiddenid, final TextView likes){
    final String hid = hiddenid.getText().toString().trim();

    if(like.getText().toString().equalsIgnoreCase("like")){
        like.setText("UnLike");
        StringRequest stringRequest = new StringRequest(Request.Method.POST, Config.SERVER_ADDRESS + "LikePost.php",
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {

                        JSONObject jsonObject = null;
                        try {
                            //json string to jsonobject
                            jsonObject = new JSONObject(response);
                            //get json sstring created in php and store to JSON Array
                            result2 = jsonObject.getJSONArray(Config.json_array_likes);
                            //get username from json array
                            likes.setText(getLikeCount(result2));
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                    }
                }){
            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                Map<String,String> hashMap = new HashMap<>();
                //maps specified string key, to specified string value
                hashMap.put(Config.hid, hid);
                return hashMap;
            }
        };
        //add string request to queue
        RequestQueue requestQueue = Volley.newRequestQueue(this);
        requestQueue.add(stringRequest);
    }
    else{
        like.setText("Like");
    }
}
private String getLikeCount(JSONArray jsonArray){
    String lc = null;
    for(int i = 0; i < jsonArray.length(); i++) {
        try {
            JSONObject json = jsonArray.getJSONObject(i);
            likeCount.add(json.getString(Config.getLike));
            lc = likeCount.get(0);
        } catch (JSONException e) {
        }
    }
    return lc;
}
 
Upvote 0
Hard to say what's going wrong just through static code analysis. I'd have to get in there and debug, trying to figure out what exactly is getting saved into that Holder class.
But you do seem like a smart guy, I'm sure you will get to the bottom of it.
 
Last edited by a moderator:
  • Like
Reactions: cdubs
Upvote 0
Hard to say what's going wrong just through static code analysis. I'd have to get in there and debug, trying to figure out what exactly is getting saved into that Holder class.
But you do seem like a smart guy, I'm sure you will get to the bottom of it.

I really appreciate all your help so far. I have figured out problem 2 and problem 3 that I had. However problem 1 still exists. There still seems to be problems with liking one button, and it changing the text on another. Do you know what is causing this?

Here is the updated code:

Code:
public class PostsAdapter extends ArrayAdapter<Posts>  {
    //used to create views from xml
    Context context;
    int textViewResourceId;
    ArrayList<Posts> mPosts = new ArrayList<Posts>();

    public PostsAdapter(Context context, int textViewResourceId, ArrayList<Posts> posts) {
        super(context, textViewResourceId, posts);
        this.textViewResourceId = textViewResourceId;
        this.context = context;
        this.mPosts = posts;
    }

    // view convertview = recycled view
    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        View view = convertView;
        final Holder holder;
        //checks if recycled view is null, thewn creates new view, if not null, use same view
        if(view == null){
            LayoutInflater inflater = ((Activity) context).getLayoutInflater();
            view = inflater.inflate(textViewResourceId, parent, false);

            Button like = (Button) view.findViewById(R.id.btnLike);
            TextView content = (TextView) view.findViewById(R.id.content);
            TextView user = (TextView) view.findViewById(R.id.user);
            TextView topic = (TextView) view.findViewById(R.id.topic);
            TextView date = (TextView) view.findViewById(R.id.date);
            TextView likes = (TextView) view.findViewById(R.id.likeCount);
            TextView hiddenId = (TextView) view.findViewById(R.id.hiddenID);
            holder = new Holder(content, user, topic, date, likes, hiddenId, like);

            view.setTag(holder);
        }
        else{
            holder = (Holder) view.getTag();
        }

        Posts posts = mPosts.get(position);
        holder.content.setText(posts.getContent());
        holder.user.setText(posts.getUser());
        holder.topic.setText(posts.getTopic());
        holder.date.setText(posts.getDate());
        holder.likes.setText(posts.getLikes());
        holder.hiddenId.setText(posts.getId());

        holder.like.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                createLike(position, holder.like, holder.hiddenId, holder.likes);//Added one more parameter "position"
            }
        });

        return view;
    }

    private void createLike(final int position, final Button like, final TextView hiddenid, final TextView likes){
        final String hid = hiddenid.getText().toString().trim();

        if(like.getText().toString().equalsIgnoreCase("like")){

            like.setText("UnLike");
            StringRequest stringRequest = new StringRequest(Request.Method.POST, Config.SERVER_ADDRESS + "LikePost.php",
                    new Response.Listener<String>() {
                        @Override
                        public void onResponse(String response) {

                            JSONObject jsonObject = null;
                            try {
                                //counter
                                n+=1;

                                //json string to jsonobject
                                jsonObject = new JSONObject(response);
                                //get json sstring created in php and store to JSON Array
                                result2 = jsonObject.getJSONArray(Config.json_array_likes);
                                //get username from json array
                                String likestring = getLikeCount(result2);
                                String likenum = mPosts.get(position).getLikes(likestring);
                                likes.setText(likenum);
                                mPosts.get(position).setLikes(likenum);

                            } catch (JSONException e) {
                                e.printStackTrace();
                            }
                        }
                    },
                    new Response.ErrorListener() {
                        @Override
                        public void onErrorResponse(VolleyError error) {
                        }
                    }){
                @Override
                protected Map<String, String> getParams() throws AuthFailureError {

                    // corresponding values.
                    Map<String,String> hashMap = new HashMap<>();
                    //maps specified string key, to specified string value
                    hashMap.put(Config.hid, hid);
                    return hashMap;
                }
            };

            //add string request to queue
            RequestQueue requestQueue = Volley.newRequestQueue(DisplayPosts.this);
            requestQueue.add(stringRequest);
        }
        else{
            like.setText("Like");
            //mPosts.get(position);

        }
    }
    private String getLikeCount(JSONArray jsonArray){
        System.out.println("JSON: " + jsonArray);
        String lc = null;
        for(int i = 0; i < jsonArray.length(); i++) {
            try {
                JSONObject json = jsonArray.getJSONObject(i);
                likeCount.add(json.getString(Config.getLike));
                System.out.println("i: " + i);
                lc = likeCount.get(n - 1);
            } catch (JSONException e) {
            }
        }
        System.out.println("LC: " + lc);
        return lc;
    }

     class Holder{
        public TextView content;
        public TextView user;
        public TextView topic;
        public TextView date;
        public TextView likes;
        public TextView hiddenId;
        public Button like;

        public Holder(TextView content, TextView user, TextView topic, TextView date, TextView likes, TextView hiddenId, Button like) {
            this.content = content;
            this.user = user;
            this.topic = topic;
            this.date = date;
            this.likes = likes;
            this.hiddenId = hiddenId;
            this.like = like;
        }
    }
}

Thank you!
 
Upvote 0
Does this happen when you scroll the list up and down, or when you haven't scrolled at all?
Well, I think it's when I dont scroll at all. But I cant be sure because I have to scroll down to see it. I just know when I click 'like' it changes to unlike, and when I scroll down there will be another one with the text unlike. It seems to be the 5th one under it is also changed. Which is interesting because it can fit a max of 5 on the screen at once.
 
Upvote 0
It's because you don't update the like Button which is contained in your holder class.

When you scroll down one item, the system calls getView() with parameter 'convertView' set to what was the first item in your list (it's no longer visible on the screen, so can be recycled)

Your code then detects that view !=null, so goes into the block

Code:
else{
            holder = (Holder) view.getTag();
        }

But the holder still contains the like Button from the previous item 1, which has the text 'unlike'

The following code should update the like Button contained in the holder class

Code:
       Posts posts = mPosts.get(position);
        holder.content.setText(posts.getContent());
        holder.user.setText(posts.getUser());
        holder.topic.setText(posts.getTopic());
        holder.date.setText(posts.getDate());
        holder.likes.setText(posts.getLikes());
        holder.hiddenId.setText(posts.getId());
 
Upvote 0
It's because you don't update the like Button which is contained in your holder class.

When you scroll down one item, the system calls getView() with parameter 'convertView' set to what was the first item in your list (it's no longer visible on the screen, so can be recycled)

Your code then detects that view !=null, so goes into the block

Code:
else{
            holder = (Holder) view.getTag();
        }

But the holder still contains the like Button from the previous item 1, which has the text 'unlike'

The following code should update the like Button contained in the holder class

Code:
       Posts posts = mPosts.get(position);
        holder.content.setText(posts.getContent());
        holder.user.setText(posts.getUser());
        holder.topic.setText(posts.getTopic());
        holder.date.setText(posts.getDate());
        holder.likes.setText(posts.getLikes());
        holder.hiddenId.setText(posts.getId());

So do I just hard code the text in right there for the like button? What do I do to update there? All the other holders just enter text from the posts class. Do I add button text to the post class?
 
Upvote 0

BEST TECH IN 2023

We've been tracking upcoming products and ranking the best tech since 2007. Thanks for trusting our opinion: we get rewarded through affiliate links that earn us a commission and we invite you to learn more about us.

Smartphones