Search code examples
androidimageandroid-asynctaskandroid-recyclerviewandroid-cardview

AsyncTask loading image RecyclerView


I'm trying to create an app similar to that one: An app with some images and description (cardview) in a recyclerview

First at all i load all the information for my CardView from a database Title of the image, Description and the URL from the image. When i put all that information in the RecyclerView, (title and description) shows correctly, but for the image I create a AsyncTask class to load the images from the URL and let the user don't wait to much time waiting for the app to response.

If the user scroll slowly the images are loaded correctly and all is fine, but if i scroll to fast i have some problems, for example the image that is shown in the item 3 for example is shown to in the last item until the last item's image is loaded and refresh....

Here some code for my adapter where i load the image:

@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
    if (holder instanceof EventosViewHolder) {
        ......
        //Load the image (getFoto() get drawable)
        if (eventoInfoAux.getFoto()==null){
            CargarImagen cargarImagen = new CargarImagen(((EventosViewHolder) holder).vRelativeLayout,eventoInfo,position);
            cargarImagen.execute();
        }else{
            ((EventosViewHolder) holder).vRelativeLayout.setBackground(eventoInfoAux.getFoto());
        }   
    }
}

Here the code for the class CargarImagen:

//Clase para cargar imagenes con una tarea asíncrona desde un url de la imagen
public class CargarImagen extends AsyncTask<String, String, Boolean>{

//RelativeLayout donde se introduce la imagen de fondo
RelativeLayout relativeLayout=null;
//EventoInfo para obtener la url de la imagen
List<EventoInfo> eventoInfo=null;
//Posición de la imagen clicada
int i;

//Se cargan todos los valores de las variables necesarias desde los datos introducidos
public CargarImagen(RelativeLayout relativeLayout,List<EventoInfo> eventoInfo,int i) {
    this.relativeLayout = relativeLayout;
    this.eventoInfo = eventoInfo;
    this.i = i;
}

//Pintamos el fondo de gris mientras se está cargando la imagen
@Override 
protected void onPreExecute() { 
    super.onPreExecute(); 
    relativeLayout.setBackgroundResource(R.color.fondo_card_view);
}

//Se realiza la carga de la imagen en segundo plano
@SuppressWarnings("deprecation")
@Override
protected Boolean doInBackground(String... params) {
    //Necesario para hacer la llamada a la red
    StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();      
    StrictMode.setThreadPolicy(policy);

    //obtenemos la imagen con el metodo getBitmapFromURL
    Bitmap myImage = getBitmapFromURL(eventoInfo.get(i).url);

    //Si se tiene imagen se pinta, si no no se hace nada
    if (myImage !=null){
        Drawable dr = new BitmapDrawable(myImage);
        eventoInfo.get(i).setFoto(dr);
        return true;
    }
    return false;
}

//Al finalizar la carga de la imagen se pinta el fondo del relative layout
protected void onPostExecute(Boolean result) {
    if(result){
        relativeLayout.setBackground(eventoInfo.get(i).foto);
    }   
}

//Metodo para obtener un bitmap desde una url
public Bitmap getBitmapFromURL(String imageUrl) {
    try {

        URL url = new URL(imageUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setDoInput(true);
        connection.connect();
        InputStream input = connection.getInputStream();
        Bitmap myBitmap = BitmapFactory.decodeStream(input);
        return myBitmap;

    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
}

}

I could give you the .apk file and you could see the problem.

Thank you in advance. :)

Here the full code of my adapter

 import java.util.List;

import com.abdevelopers.navarraongoing.R;
import com.abdevelopers.navarraongoing.detalle.DetalleActivity;

import android.content.Intent;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class EventosAdapter extends RecyclerView.Adapter<ViewHolder>{

    private static final int TYPE_EVENTO = 0;
    private static final int TYPE_FOOTER = 1;

//lista de eventos
private List<EventoInfo> eventoInfo;


private String usuario;
private EventoInfo  eventoInfoAux;
private PaginaInicioActivity paginaInicio;

//Se pasan los valores necesarios para obtener información de los eventos
public EventosAdapter(List<EventoInfo> eventoInfo, String usuario, PaginaInicioActivity paginaInicio ) {
    this.eventoInfo = eventoInfo;
    this.usuario = usuario;
    this.paginaInicio = paginaInicio;
}


//Metodo para obtener el numero de items en la lista que introducimos
@Override
public int getItemCount() {
    return eventoInfo.size();
}

@Override
public int getItemViewType(int position) {
    if (position + 1  == getItemCount()) {
        return TYPE_FOOTER;
    } else {
        return TYPE_EVENTO;
    }
}

//Se asignan los datos a cada uno de los elementos de la cardview
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
    if (holder instanceof EventosViewHolder) {
        //Obtenemos cada uno de los eventos
        eventoInfoAux = eventoInfo.get(position);
        //eventosViewHolder = holder;

        //Introducimos el título del evento
        ((EventosViewHolder) holder).vTitle.setText(eventoInfoAux.titulo);
        //Introdicumos la fecha y el lugar del evento
        ((EventosViewHolder) holder).vFechaLugar.setText(eventoInfoAux.fecha+", "+eventoInfoAux.lugar);
        //obtenemos el numero de asistentes al evento
        String asistentes = Integer.toString(eventoInfoAux.asistentes);
        ((EventosViewHolder) holder).vLikeButton.setText(asistentes);

        //Se pinta el boton de like dependiendo de si está o no like 
        if(eventoInfoAux.like){
            ((EventosViewHolder) holder).vLikeButton.setBackgroundResource(R.drawable.button_like_liked);
        }else{
            ((EventosViewHolder) holder).vLikeButton.setBackgroundResource(R.drawable.button_like);
        }

        //Se pinta la imagen del evento dependiendo de si está en la base de datos o no
        if (eventoInfoAux.foto==null){
            CargarImagen cargarImagen = new CargarImagen(((EventosViewHolder) holder).vRelativeLayout,eventoInfo,position);
            cargarImagen.execute();
        }else{
            ((EventosViewHolder) holder).vRelativeLayout.setBackground(eventoInfoAux.foto);
        }   
    }
}



@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (viewType == TYPE_EVENTO) {
        View view = LayoutInflater.
                from(parent.getContext()).
                inflate(R.layout.evento_card_view, parent, false);
        return new EventosViewHolder(view,eventoInfo,usuario,paginaInicio);
    } else if (viewType == TYPE_FOOTER) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.pie_carga_pagina_inicio, parent, false);
        view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
        return new FooterViewHolder(view);
    }
    return null;
}

//Clase para crear el footerview donde se cargan más eventos
class FooterViewHolder extends ViewHolder {

    public FooterViewHolder(View view) {
        super(view);
    }

}

//Clase para crear los eventos con su cardview
class EventosViewHolder extends ViewHolder implements View.OnClickListener{

    private TextView vTitle;
    private TextView vFechaLugar;
    private Button vLikeButton;
    private RelativeLayout vRelativeLayout;
    private List<EventoInfo> eventoInfo;
    private String usuario;
    private LikesDDBB likesManager;
    private PaginaInicioActivity paginaInicio;
    private CardView vCardView;

    public EventosViewHolder(View itemView,List<EventoInfo> eventoInfo,String usuario, 
            PaginaInicioActivity paginaInicio) {
        super(itemView);

        this.paginaInicio = paginaInicio;
        this.usuario = usuario;
        vTitle = (TextView)itemView.findViewById(R.id.titulo);
        vFechaLugar =  (TextView) itemView.findViewById(R.id.fecha_lugar);
        vLikeButton = (Button)  itemView.findViewById(R.id.like_button);
        vRelativeLayout = (RelativeLayout) itemView.findViewById(R.id.layout_cardview);
        vCardView = (CardView)itemView.findViewById(R.id.card_view);

        //Valores por defecto de los botones y del fondo en caso de no haber like ni foto
        vLikeButton.setBackgroundResource(R.drawable.button_like);
        vRelativeLayout.setBackgroundResource(R.color.fondo_card_view);
        vLikeButton.setOnClickListener(this);
        vCardView.setOnClickListener(this);
        vLikeButton.setTag("boton");
        vCardView.setTag("evento");

        this.eventoInfo = eventoInfo;
    }


    @SuppressWarnings("deprecation")
    @Override
    public void onClick(View v) {
        if(v.getTag().equals("boton")){
            likesManager = new LikesDDBB(this.eventoInfo.get(getPosition()).like, usuario,
                    vLikeButton, getPosition(), eventoInfo, paginaInicio);
            likesManager.execute();
        }else if(v.getTag().equals("evento")){
            Intent inicion = new Intent(paginaInicio,DetalleActivity.class);

            paginaInicio.startActivity(inicion);
        }

    }

}

//Para obtener la lista de eventos desde la clase PaginaInicioActivity
public List<EventoInfo> getEventoInfo() {
    return eventoInfo;
}

public void setEventoInfo(List<EventoInfo> eventoInfo) {
    this.eventoInfo = eventoInfo;
}

}

Solution

  • As the name "RecyclerView" indicates, it recycles/reuses the views created to display your items/cards.

    So, lets assume your RecyclerView has 3 CardViews which it recycles as you scroll, and we have 4 items to display the contents of.

    Initially content of item 1 gets displayed in CardView 1, item 2 gets displayed in CardView 2 and item 3 gets displayed in CardView 3.

    Now, as you scroll, CardView 1 disappears, gets recycled and contents of item 4 are shown in CardView 1.

    As long as you don't reset the before inserted contents, CardView 1 will display them - in your case - as long as the AsyncTask needs to complete the contents of the before set item will be displayed.

    To solve your problem you may want to use an imageloading (and caching) library like:

    Your solution is also prone to race conditions (when later tasks complete before earlier ones)