Search code examples
androidandroid-recyclerviewcardview

Edit RecyclerView item programatically in Android


So I have this app in which I have to make a RecyclerView which contains a list of items that can be deleted/edited e.t.c.

I followed a tutorial on youtube and I made a custom CardView item on another layout and a custom adapter for that item.

Thing is, depending on the state of the item, I have to change something(ex. background color or text color).

When I do that in the RecyclerView activity I get NullPointerException even if I have the id's.

How can I edit those TextViews inside the programmatically generated list of items in the moment I make the Retrofit call?

boolean isEnded;
                if(!endedAt.equals("null"))
                {
                    endedAt = endedAt.substring(endedAt.indexOf("T") + 1, endedAt.lastIndexOf(":"));
                    isEnded=true;
                }
                else
                {
                    endedAt="ongoing";
                    isEnded=false;
                }
                item.timeDifference=createdAt+" - "+endedAt;
                if(externalSystem.equals("null"))
                {
                    item.externalSystem="";
                }
                else
                {
                    item.externalSystem = externalSystem;
                }
                Log.i("attr",externalSystem);
                items.add(item);

                itemAdapter=new ItemAdapter(getApplicationContext(), items);
                recyclerView.setAdapter(itemAdapter);
                if(isEnded) {
                error->    externalSystemView.setTextColor(Color.BLACK);
                }

The app is rather big, but I think you can get the idea from this piece of code.

Here is the error: Attempt to invoke virtual method 'void android.widget.TextView.setTextColor(int)' on a null object reference

public class ItemAdapter extends     
RecyclerView.Adapter<ItemAdapter.ItemViewHolder>{

private Context context;
private  ArrayList<Item> itemList;

public ItemAdapter(Context context, ArrayList<Item> itemList)
{
    this.context=context;
    this.itemList=itemList;
}

@Override
public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    LayoutInflater layoutInflater=LayoutInflater.from(parent.getContext());
    View view=layoutInflater.inflate(R.layout.item_layout,parent,false);
    ItemViewHolder itemViewHolder=new ItemViewHolder(view);
    return itemViewHolder;
}

@Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
    Item item=itemList.get(position);
    holder.timeDifference.setText(item.timeDifference);
    holder.title.setText(item.title);
    holder.timeCounter.setText(item.timeCounter);
    holder.externalSystem.setText(item.externalSystem);
    holder.type.setText(item.type);
    holder.project.setText(item.project);
    MY IDEA
    "holder.timeDifference.setTextColor(Color.parseColor(item.color));"
}

@Override
public int getItemCount() {
    if(itemList!=null)
        return itemList.size();
    else
        return 0;
}


    public static class ItemViewHolder extends RecyclerView.ViewHolder
{
    public CardView cardViewItem;
    public TextView title;
    public TextView project;
    public TextView externalSystem;
    public TextView timeDifference;
    public TextView timeCounter;
    public TextView type;

    public ItemViewHolder(View itemView)
    {
        super(itemView);
        cardViewItem=(CardView)itemView.findViewById(R.id.card_view_item);
        title=(TextView)itemView.findViewById(R.id.title);
        project=(TextView)itemView.findViewById(R.id.project);
        externalSystem=
        (TextView)itemView.findViewById(R.id.external_system);
        timeDifference=   
        (TextView)itemView.findViewById(R.id.time_difference);
        timeCounter=(TextView)itemView.findViewById(R.id.time_counter);
        type=(TextView)itemView.findViewById(R.id.type);
    }

EDIT: I think I found a way, but I don't know if it's the best one


Solution

  • The solution would involve changing your Item class a little.

    Your problem is that you're not passing over the boolean trigger to your RecyclerView.Adapter from your Activity properly. E.g. isEndedBoolean's value to know what state the item is in. You have the right idea in the use of all three classes.

    What I would suggest do is create a constructor in your Item class passing the values from your Activity to be used in your adapter. I feel it's easier to use getters and setters rather than assigning the variables straight from code like you have.

    So let's begin,

     boolean isEnded;
      if(!endedAt.equals("null")) {
         endedAt = endedAt.substring(endedAt.indexOf("T") + 1, endedAt.lastIndexOf(":"));
         isEnded=true;
      } else {
         endedAt="ongoing";
         isEnded=false;
      }
        String timeDifference = createdAt+" - "+endedAt;
      if(externalSystem.equals("null")) {
        externalSystem="";
      } else {
        externalSystem = externalSystem;
      }
        Log.i("attr",externalSystem);
        items.add(new ItemModel(isEnded, timeDifference, externalSystem);
    
        itemAdapter=new ItemAdapter(this, items);
        recyclerView.setAdapter(itemAdapter);
        itemAdapter.notifyDataSetChanged();
    

    You'll notice how I'm adding a new ItemModel for each row of variables inside the RecyclerView to the array and then passing it that array to the Adapter. This is so that it's easier to know what variables are being passed to the Model and thus the corresponding row at the position inside the Adapter.

    An example of the ItemModel class would look something like:

    public class ItemModel {
        // Getter and Setter model for recycler view items
        private boolean isEnded;
        private String timeDifference;
        private String externalSystem;
        //other variables, title etc etc 
    
        public ItemModel(boolean isEnded, String timeDifference, String externalSystem) {
    
            this.isEnded = isEnded;
            this.timeDifference = timeDifference;
            this.externalSystem = externalSystem;
            //only pass to the model if you can access it from code above  otherwise to assign the variables statically like you have.
        }
    
        public boolean getIsEnded() {return isEnded;}
    
        public String getTimeDifference() {return timeDifference;}
    
        public String getExternalSystem() { return externalSystem; }
    
    }
    

    The information above is just a guideline for you to create a more efficient model framework to pass the data rather than using static variables.

    Now to solve your problem you need to check if (item.getIsEnded()) and then change the text color corresponding to that if condition.

    RecyclerView.Adapter onBindViewHolder would look like:

    @Override
    public void onBindViewHolder(ItemViewHolder holder, int position) {
        ItemModel item =itemList.get(position);
        holder.timeDifference.setText(item.getTimeDifference());
        holder.title.setText(item.title);
        holder.timeCounter.setText(item.timeCounter);
        holder.externalSystem.setText(item.getExternalSystem());
        holder.type.setText(item.type);
        holder.project.setText(item.project);
        if (item.getIsEnded() {
        holder.timeDifference.setTextColor(item.color);
        } else {
        holder.timeDifference.setTextColor(item.color);
        }
    }
    

    The purpose of the Adapter is to inflate a layout, bind components to that layout and perform functionality to the items corresponding to the layout at the dedicated position. You need to know which item in your list is in which state, you won't be able to do that from your Activity alone. Be mindful of how useful the Adapter is in keeping the code from your Activity separate from the actual activity of your RecyclerView.