when i scroll the custom list in my android app then the favorite icon which is part of the custom adapter changes on its own

130 views Asked by At

I have created a custom list, the adapter of which contains three components song title, song number and favorite icon. The favorite icon is meant to mark or unmark the favorite item in the list. Please have a look to the attached video to understand what is the problem.

Video

When I click on the star, the icon gets selected/unselected and fires the setOnFavoriteChangeListener event. In the event I check the isFavorite status and update the database accordingly. Here is the full code of adapter:

public class song_index_adapter extends ArrayAdapter<song_index_model>{ //implements View.OnClickListener {
private ArrayList<song_index_model> dataSet;
Context mContext;
private int lastPosition = -1;

public song_index_adapter(ArrayList<song_index_model> data, Context context) {
    super(context, R.layout.song_index_row, data);
    this.dataSet = data;
    this.mContext=context;
}
// View lookup cache
private static class ViewHolder {
    TextView txt_sno;
    TextView txt_title;
    MaterialFavoriteButton favorite;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
    // Get the data item for this position
    final song_index_model dataModel = getItem(position);

    // Check if an existing view is being reused, otherwise inflate the view
    final ViewHolder viewHolder; // view lookup cache stored in tag

    final View result;

    if (convertView == null) {
        viewHolder = new ViewHolder();
        LayoutInflater inflater = LayoutInflater.from(getContext());
        convertView = inflater.inflate(R.layout.song_index_row, parent, false);

        viewHolder.txt_sno = (TextView) convertView.findViewById(R.id.sno);
        viewHolder.txt_title = (TextView) convertView.findViewById(R.id.songTitle);
        viewHolder.favorite = (MaterialFavoriteButton) convertView.findViewById(R.id.indexfav);

        result=convertView;

        convertView.setTag(viewHolder);
    } else {
        viewHolder = (ViewHolder) convertView.getTag();
        result=convertView;
    }

    Animation animation = AnimationUtils.loadAnimation(mContext, (position > lastPosition) ? R.anim.up_from_bottom : R.anim.down_from_top);
    result.startAnimation(animation);
    lastPosition = position;

    viewHolder.txt_sno.setText(dataModel.getSno());
    viewHolder.txt_title.setText(dataModel.getTitle());

    //--- following conditional statements take care to
    //--- not to show a star with the index letter
    if(viewHolder.txt_sno.getText().toString().equals(""))
        viewHolder.favorite.setVisibility(View.GONE);
    else
        viewHolder.favorite.setVisibility(View.VISIBLE);

    viewHolder.favorite.setFavorite(dataModel.getFav());

    int fsize = (gvar.fontsize * gvar.fontstep) + gvar.fontmin;
    viewHolder.txt_title.setTextSize(fsize);
    viewHolder.txt_sno.setTextSize(fsize);

    viewHolder.favorite.setOnFavoriteChangeListener(new MaterialFavoriteButton.OnFavoriteChangeListener() {
        @Override
        public void onFavoriteChanged(MaterialFavoriteButton buttonView, boolean isfavorite) {
            DBHelper db = new DBHelper(mContext);
            SQLiteDatabase sdb = db.getWritableDatabase();

            boolean isUpdate = db.updateData(gvar.table,dataModel.getSno(),dataModel.getTitle(),dataModel.getSong(),dataModel.getCategory(),isfavorite);
            if(!isUpdate)
                Toast.makeText(mContext, "Song Selection could not be saved", Toast.LENGTH_SHORT).show();
            else {
                Toast.makeText(mContext, "Updated " + dataModel.getSno(), Toast.LENGTH_SHORT).show();
                Log.e("UPDATED", dataModel.getSno() + " " + isfavorite);
            }
        }
    });
    return convertView;
}

}

This event is inside the adapter file which is set on the listview and it basically checks the status of the favorite star and update the status of the song in the database. You can see the Toast messages prompting about the update.

My problem is that even if I'm simply scrolling up and down without pressing the star icon, then also the setOnFavoriteChangeListener event keeps firing. This can be seen in the Toast messages and in the Log records. I'm attaching snapshot of the Log records also for you to view. Log record

I personally changed the favorite icon of only song no 9 and 42 in the beginning and 35 in the end. In between I only kept scrolling up and down and you can see how the UPDATE is happening by itself.

My aim is to mark the list of favorite items.

Why is the setOnFavoriteChangeListener getting fired without me touching it.

Is there any other method to mark favorite items from a list and save them in the database.

Thanks in advance

1

There are 1 answers

0
cincy_anddeveloper On BEST ANSWER

I think I figured out your problem. It has to do with the fact your are recycling your ListView's items. During the initial load everything works fine and nothing is selected and the call dataModel.getFav() returns false so when your call viewHolder.favorite.setFavorite() the MaterialFavoriteButton doesn't fire it's set OnFavoriteChangeListener. The reason for this is, because internally it checks to see if the new favorite state is different from the last as an optimization to prevent unnecessary work (I checked the source code). But, once you make a selection your onFavoriteChanged will be fired because the new state is different and you then store the values inside of your Database. Your problem arises when you start to scroll, since your view are recycled, the MaterialFavoriteButton's favorite state will set but when you call dataModel.getFav() it will return false to and change the MaterialFavoriteButton's favorite state back to false. Thus causing your the MaterialFavoriteButton's old OnFavoriteChangeListener to fire once again (thus why the button isn't favorited). It is also updating your Database with it's previous dataModel (NOTE: this is a closure issue) that is why you see it use the text from a view that was scrolled of screen. The reason the old OnFavoriteChangeListener is being called because the view is being recycled and still has the instance you passed to it during the initial load. So once all view are populated to fill the entire screen, if you scroll the first one to go off the top screen will be passed in to getView() as convertView.You should move the call viewHolder.favorite.setOnFavoriteChangeListener to before the setFavorite() call. If you place a debug statement in the code and step through the call to viewHolder.favorite.setFavorite() you should see what I am talking about. I hope this all makes since to you. If not, comment and I try and give you more assistance. My recommended solution would be to hold off on the database writes until a save button is pressed or the Activity/Fragment is paused or stopped and just store the favorite state inside of an ArrayList that gets accessed using the getView(...) position argument. This is much more efficient because you won't have to constantly access the Database anytime the ListView is scrolled.