Search code examples
androidlistviewscrollrecycle

Android ListView show incorrect content when Scrolling Down, then correct content when Scrolling Up


As a total newbie to Android Studio, I'm presently baffled by different behavior upon the same code. If the code is the same, how may the results be different when scrolling up or down? Could this be some Android View Recycling issue?

After parsing data from a JSON file on the web, a custom adapter reads and sets the corresponding data for each row inside a ListView. There are four pieces of information for each row: 1) a Title, 2) a Description, 3) an Image URL (shown through Picasso) and 4) an external URL which is assigned to an setOnClickListener of a Button, to be opened through a browser intent.

While the title, description and image work perfectly fine for each row, for the URL assigned to the setOnClickListener of the Button there happens to be a very weird, strange and so far (to me) inexplicable behavior:

1) When first presenting the ListView, when clicking the Button to open the URL on a browser, the system would open not the correct URL of that one item, but instead the URL corresponding to the NEXT item in the Array.

2) If one scrolls down a bit, and then scrolls up again, when clicking on that very same button (which before led to an incorrect URL), this time it would indeed open the correct URL.

Over the same code, on the same run, the Button would show incorrect URL when scrolling down, and then the correct URL when scrolling up.

So far, my hint has been to consider this might have something to do with the Recycling Views functions with Android, but I still have no clue about how to correct this.

I have indeed included the following lines of code within the CustomAdapter.java, hoping to prevent Recycling:

    @Override
public int getViewTypeCount() {
    return getCount();
}
@Override
public int getItemViewType(int position) {
    return position;
}

Nevertheless, including or excluding the above code makes not actual difference.

The complete code of my CustomAdapter.java is the following:

   package ca.costari.apps.ge3000;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import java.util.ArrayList;
import java.util.Objects;

public class CustomAdapter extends BaseAdapter {

    private Context context;
    private ArrayList<PlayersModel> playersModelArrayList;
    private String clickurl;

    public CustomAdapter(Context context, ArrayList<PlayersModel> playersModelArrayList) {

        this.context = context;
        this.playersModelArrayList = playersModelArrayList;
    }

    @Override
    public int getViewTypeCount() {
        return getCount();
    }
    @Override
    public int getItemViewType(int position) {
        return position;
    }

    @Override
    public int getCount() {
        return playersModelArrayList.size();
    }

    @Override
    public Object getItem(int position) {
        return playersModelArrayList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @SuppressLint("InflateParams")
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;

        if (convertView == null) {
            holder = new ViewHolder();
            LayoutInflater inflater = (LayoutInflater) context

                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                convertView = Objects.requireNonNull(inflater).inflate(R.layout.lv_item, null, true);
            }

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                holder.procesa_titulo = Objects.requireNonNull(convertView).findViewById(R.id.id_titulo);
            }
            holder.procesa_mensajepromo = convertView.findViewById(R.id.id_mensajepromo);
            holder.procesa_imagenurl = convertView.findViewById(R.id.imageViewPromo);

            convertView.setTag(holder);
        }else {
            // the getTag returns the viewHolder object set as a tag to the view
            holder = (ViewHolder)convertView.getTag();
        }

        clickurl = playersModelArrayList.get(position).getClickURL();
        holder.procesa_clickurl = convertView.findViewById(R.id.id_clickurl);
        holder.procesa_clickurl.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                AsignaBotonUrl(v, clickurl);
            }
        });
        holder.procesa_titulo.setText(playersModelArrayList.get(position).getTitulo());
        holder.procesa_mensajepromo.setText(playersModelArrayList.get(position).getMensajePromo());

        // Picasso to display image from URL
        Picasso.with(holder.procesa_imagenurl.getContext())
                .load(playersModelArrayList.get(position).getImageURL())
                .placeholder(R.drawable.echale_un_ojo_te_interesa)
                .into(holder.procesa_imagenurl);

        return convertView;
    }

    private void AsignaBotonUrl(View view, String url) {
        Intent browserIntent = new Intent(
                Intent.ACTION_VIEW,
                Uri.parse(url));
        view.getContext().startActivity(browserIntent);
    }

    private class ViewHolder {
        protected TextView procesa_titulo, procesa_mensajepromo;
        protected ImageView  procesa_imagenurl;
        protected Button procesa_clickurl;
    }

}

The above code assigns values to the items included in the following ListView xml:

    <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#fdfdfd"
        android:orientation="vertical"
        android:layout_marginBottom="30dp"
        tools:ignore="UselessParent">

        <TextView
            android:id="@+id/id_titulo"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColor="#2f2f2f"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="20dp"
            android:gravity="center_vertical"
            android:textSize="20sp"
            android:paddingLeft="10dp" />

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/imageViewPromo"
            android:layout_marginBottom="20dp"
            android:contentDescription="@string/echale_un_ojo" />

        <TextView
            android:id="@+id/id_mensajepromo"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColor="#2f2f2f"
            android:gravity="center_vertical"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:paddingLeft="10dp"
            android:layout_marginBottom="10dp" />

        <Button
            android:id="@+id/id_clickurl"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:textSize="24sp"
            android:text="@string/conocer_mas"
            android:drawableRight="@drawable/open_web"
            android:layout_marginBottom="30dp" />

    </LinearLayout>

</LinearLayout>

This is my very first post in StackOverflow, thus I beg you to please be patient in the sense that I might not have presented my issue correctly, please let me know if I may need to include more code herein, or provide more information.

Thank you immensely!!!


Solution

  • Change clickurl to a final local variable, so remove

    private String clickurl
    

    and just use this in getView so each listener has a unique URL.

    final String clickurl = playersModelArrayList.get(position).getClickURL();
    

    As-is, your onClickListener uses a single instance of clickurl (the one saved in the Adapter class) so it's always going to use whatever was set in the last call to getView.