0

I want to loop through all CardViews and change the text and color of a TextView within a single CardView item using a button click. The following code seems to produce the desired results but I'm not certain that it's the most effective code or even accurate (index).

// CustomAdapter

import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;

public class CustomAdapter extends RecyclerView.Adapter<CustomViewHolder> {

    private Context context;
    private List<MyModel> list;

    public CustomAdapter(Context context, List<MyModel> list) {
        this.context = context;
        this.list = list;
    }

    @NonNull
    @Override
    public CustomViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new CustomViewHolder(LayoutInflater.from(context).inflate(R.layout.single_items, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull CustomViewHolder holder, int position) {
        holder.textName.setText(list.get(position).getName());
        holder.textAge.setText(String.valueOf(list.get(position).getAge()));
    }

    @Override
    public int getItemCount() {
        return list.size();
    }

}

//CustomViewHolder

import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

public class CustomViewHolder extends RecyclerView.ViewHolder {

    public TextView textName, textAge;

    public CustomViewHolder(@NonNull View itemView) {
        super(itemView);

        textName = itemView.findViewById(R.id.textName);
        textAge = itemView.findViewById(R.id.textAge);
    }

}
MainActivity

import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    RecyclerView recyclerView;
    List<MyModel> myModelList;
    CustomAdapter customAdapter;

    private Button button1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        loadData();
    }

    private void loadData() {
        recyclerView = findViewById(R.id.recycler_main);
        recyclerView.setHasFixedSize(true);
        recyclerView.setLayoutManager(new GridLayoutManager(this, 1));

        myModelList = new ArrayList<>();

        myModelList.add(new MyModel("Joe", 21));
        myModelList.add(new MyModel("Jane", 26));
        myModelList.add(new MyModel("Kyle", 19));
        myModelList.add(new MyModel("Scott", 30));

        customAdapter = new CustomAdapter(this, myModelList);
        recyclerView.setAdapter(customAdapter);
    }

    public void onClickBtn(View v)
    {
        String searchString = "Kyle";

        for (int x = recyclerView.getChildCount(), i = 0; i < x; ++i) {
            RecyclerView.ViewHolder holder = recyclerView.getChildViewHolder(recyclerView.getChildAt(i));
            TextView txtName = holder.itemView.findViewById(R.id.textName);

            if (txtName.getText().toString().equals(searchString.toString())) {
                txtName.setText("Found " + txtName.getText().toString());
                txtName.setTextColor(Color.GREEN);
                customAdapter.notifyItemChanged(x);
            }
        }
    }

}

//MyModel

public class MyModel {

    String name = "";
    int age = 0;

    public MyModel(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

}

It's important that I iterate through the list in button click event. Functionality to be changed later. Really appreciate any advice and feedback. Update. Must be an index or other related problem. When my ArrayList contains many, many more items and button is clicked, a lot of non found rows text and color are changed.

JMoli
  • 33
  • 4
  • I apologize for duplicate question... couldn't seem to delete initial question. – JMoli Apr 24 '22 at 23:32
  • https://stackoverflow.com/questions/67257217/android-how-to-fix-spannablestrings-from-search-in-recyclerview/67464940#67464940 – i_A_mok Apr 25 '22 at 14:51
  • Thank you. Unfortunately I can't see how this solution maintains my list and order etc and only changes the textview etc for item matching search criteria. – JMoli Apr 25 '22 at 16:26
  • Your current approach is wrong. RecyclerView do not keep item view for each data item. If you have 1000 data and only 10 is visiable on the screen, recyclerview only keeps a little bit more than 10 item views. When scrolled, old data items go outside the screen and their views are **recyclered** for new data items. This is the cause of your problem when item list is long. – i_A_mok Apr 26 '22 at 02:27
  • Correct approach is adapter have correct and completed data set (Note: Here **set** includes data list and search string), then notify the adapter (adapter will calls its `onBindViewHolder()` to populate each visiable item view). With `onBindViewHolder()` to set different views for different conditions. Similar to the above mentioned post. – i_A_mok Apr 26 '22 at 02:44
  • Since multiple items can be filtered/found, position index should be stored in an array when used. Similar post is here: https://stackoverflow.com/questions/65583085/how-can-i-get-data-in-item-stored-when-entering-data-from-filtering-listview/65626810#65626810 – i_A_mok Apr 26 '22 at 02:50
  • Thank you very much for the replies. Would something other than recyclerview work better for what I'm trying to do? I just can't seem to get this working. – JMoli Apr 26 '22 at 11:27

2 Answers2

1

Try this adapter:

public class CustomAdapter extends RecyclerView.Adapter<CustomViewHolder> {

private Context context;
private List<MyModel> list;
private String searchString = "";

public CustomAdapter(Context context, List<MyModel> list) {
    this.context = context;
    this.list = list;
}

@NonNull
@Override
public CustomViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    return new CustomViewHolder(LayoutInflater.from(context).inflate(R.layout.single_items, parent, false));
}

@Override
public void onBindViewHolder(@NonNull CustomViewHolder holder, int position) {
    holder.textAge.setText(String.valueOf(list.get(position).getAge()));
    if(list.get(position).getName().equals(searchString)){
        holder.textName.setText("Found " + list.get(position).getName());
        holder.textName.setTextColor(Color.GREEN);
    } else {
        holder.textName.setText(list.get(position).getName());
        holder.textName.setTextColor(Color.BLACK);
    }
}

@Override
public int getItemCount() {
    return list.size();
}

public void setNewSearchString(String searchString) {
    this.searchString = searchString;
    notifyDataSetChanged();
}
}

and button click:

public void onClickBtn(View v)
{
    customAdapter.setNewSearchString("Kyle");
}

For Multiple search, the adapter:

public class CustomAdapter extends RecyclerView.Adapter<CustomViewHolder> {

private Context context;
private List<MyModel> list;
//private String searchString = "";
private ArrayList<String> arraySearchStrings = new ArrayList<>();

public CustomAdapter(Context context, List<MyModel> list) {
    this.context = context;
    this.list = list;
}

@NonNull
@Override
public CustomViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    return new CustomViewHolder(LayoutInflater.from(context).inflate(R.layout.single_items, parent, false));
}

@Override
public void onBindViewHolder(@NonNull CustomViewHolder holder, int position) {
    holder.textAge.setText(String.valueOf(list.get(position).getAge()));
    boolean found = false;
    for (String searchString : arraySearchStrings) {
        if (list.get(position).getName().equals(searchString)) {
            found = true;
            break;
        }
    }
    if (found) {
        holder.textName.setText("Found " + list.get(position).getName());
        holder.textName.setTextColor(Color.GREEN);
    } else {
        holder.textName.setText(list.get(position).getName());
        holder.textName.setTextColor(Color.BLACK);
    }
}

@Override
public int getItemCount() {
    return list.size();
}

public void setNewSearchString(String searchString) {
    //this.searchString = searchString;
    arraySearchStrings.add(searchString);
    notifyDataSetChanged();
}

public void resetSearchString() {
    arraySearchStrings.clear();
    notifyDataSetChanged();
}
}

Button click:

public void onClickBtn(View v)
{
    customAdapter.setNewSearchString("Kyle");
    customAdapter.setNewSearchString("Steve");
    customAdapter.setNewSearchString("Joe");
}

Alternative answser:

public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.CustomViewHolder> {

private Context context;
private List<MyModel> list;
private ArrayList<String> arraySearchStrings = new ArrayList<>();
private ArrayList<Boolean> arrayFound = new ArrayList<>();
private int[] arrayFoundCount;
private int foundTotalCount = 0;

public CustomAdapter(Context context, List<MyModel> list) {
    this.context = context;
    this.list = list;
    arrayFoundCount = new int[list.size()];
    for (int i = 0; i < list.size(); i++) {
        arrayFound.add(false);
        arrayFoundCount[i] = 0;
    }
}

@NonNull
@Override
public CustomViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    return new CustomViewHolder(LayoutInflater.from(context).inflate(R.layout.single_items, parent, false));
}

@Override
public void onBindViewHolder(@NonNull CustomViewHolder holder, int position) {
    holder.textAge.setText(String.valueOf(list.get(position).getAge()));
    holder.textCount.setText(String.valueOf(arrayFoundCount[position]));
    if (arrayFound.get(position)) {
        holder.textName.setText("Found " + list.get(position).getName());
        holder.textName.setTextColor(Color.GREEN);
    } else {
        holder.textName.setText(list.get(position).getName());
        holder.textName.setTextColor(Color.BLACK);
    }
}

@Override
public int getItemCount() { return list.size(); }

public class CustomViewHolder extends RecyclerView.ViewHolder {

    public TextView textName, textAge, textCount;

    public CustomViewHolder(@NonNull View itemView) {
        super(itemView);
        textName = itemView.findViewById(R.id.textName);
        textAge = itemView.findViewById(R.id.textAge);
        textCount = itemView.findViewById(R.id.textCount);
    }
}

private int countFoundNameInList() {

    int count = 0;
    boolean found;
    MyModel model;

    arrayFound.clear();
    for (int i = 0; i < list.size(); i++) {
        model = list.get(i);
        found = false;
        for (String searchString : arraySearchStrings) {
            if (model.getName().equals(searchString)) {
                found = true;
                arrayFoundCount[i] = arrayFoundCount[i]++;
                count++;
                break;
            }
        }
        arrayFound.add(found);
    }
    return count;
}

public void setNewSearchString(String searchString) {
    arraySearchStrings.add(searchString);
    int newCount = countFoundNameInList();
    if (newCount > foundTotalCount) {
        Toast.makeText(context, searchString + " found.", Toast.LENGTH_SHORT).show();
    } else {
        Toast.makeText(context, "Error: Nothing found!!", Toast.LENGTH_LONG).show();
    }
    foundTotalCount = newCount;
    notifyDataSetChanged();
}
}
i_A_mok
  • 2,744
  • 2
  • 11
  • 15
  • Thank you very much. This works very well for a single item. What is I wanted to find multiple items? eg ustomAdapter.setNewSearchString("Kyle"); ustomAdapter.setNewSearchString("Steve"); ustomAdapter.setNewSearchString("Joe"); and have those that are found in the list changed to found/green font? – JMoli Jul 06 '22 at 20:01
  • Updated the answer. Try it! – i_A_mok Jul 07 '22 at 03:59
  • You are the best! Thank you all of your support as I learn and work through this. Much appreciated. Thank you. – JMoli Jul 07 '22 at 13:15
  • One more issue I'm experiencing and hoping you can provide some guidance. I'm looking to return the count of the number of times an item was found. I've tried several variables within the code however I believe the issue is the lifecycle of the adapter. Following a search I just want to know increment a variable that is return to let me know how many times a certain name was found in the arraylist. – JMoli Jul 12 '22 at 11:10
  • Ideally I'd have a textview within the item with a value of 0. Each time the record is found that textview value would increment by 1 and displayed within the item. – JMoli Jul 12 '22 at 11:20
  • You need another approach, try the alternative answer. I have to make sure it works before going on. – i_A_mok Jul 12 '22 at 11:45
  • Thank you so much. Unfortunately error nothing found following first find. Can I maintain the count in a textview for each item? – JMoli Jul 12 '22 at 12:56
  • The more I think of it I'd prefer to display the count of number of times found in a textview within the item. I can confirm that names will be unique... just want to increment and display a count each time found. – JMoli Jul 12 '22 at 16:18
  • Try the updated alternative answer. – i_A_mok Jul 14 '22 at 03:00
0

I don't think this code will give you accurate result. you have to come up with different logic for this In your logic you are searching static name i am sure this is for demo purpose only. later you will implement with user input string. So As per my opinion you can create variable in your adapter that search mode is on or off after this when ever user start searching make searchMode On and same as when they done with searching set searchMode Off in your viewHolder Class you can update View on searchMode on off Status. You can Create 2 list 1 is for main List seconds is for searched list when user starts searching you have to filter main list with search string and then set it to searchedList and also make searchMode On and then update it to your adapter. rest will handle your adapter. no need to change it one by one from your list.

I am adding here required changes as per my opinion

Your Custom Adapter

public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.CustomViewHolder> {

   private Context context;
   private List<MyModel> list;
   private Boolean isSearchModeOn = false;

   public CustomAdapter(@NonNull Context context, @NonNull List<MyModel> list) {
      this.context = context;
      this.list = list;
   }

   @NonNull
   @Override
   public CustomViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
      return new CustomViewHolder(LayoutInflater.from(context).inflate(R.layout.single_items, parent, false));
   }

   @Override
   public void onBindViewHolder(@NonNull CustomViewHolder holder, int position) {
      if (isSearchModeOn){
         holder.textName.setText("Found " + list.get(position).getName());
         holder.textName.setTextColor(Color.GREEN);
      }else {
         holder.textName.setText(list.get(position).getName());
         //Also set Here normal text color
      }
      holder.textAge.setText(String.valueOf(list.get(position).getAge()));
   }

   @SuppressLint("NotifyDataSetChanged")
   public void updateList(@NonNull List<MyModel> searchedList){
      list = searchedList;
      notifyDataSetChanged();
   }

   @SuppressLint("NotifyDataSetChanged")
   public void setSearchMode(@NonNull Boolean isOn){
      isSearchModeOn = isOn;
      notifyDataSetChanged();
   }

   @Override
   public int getItemCount() {
      return list.size();
   }

   public class CustomViewHolder extends RecyclerView.ViewHolder {

      @NonNull
      public TextView textName, textAge;

      public CustomViewHolder(@NonNull View itemView) {
         super(itemView);

         textName = itemView.findViewById(R.id.textName);
         textAge = itemView.findViewById(R.id.textAge);
      }

   }

}

MainActivity

public class MainActivity extends AppCompatActivity {

    RecyclerView recyclerView;
    List<MyModel> myModelList;
    List<MyModel> searchedList;
    CustomAdapter customAdapter;

    @Override
    protected void onCreate(@NonNull Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        loadData();
    }

    private void loadData() {
        recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setHasFixedSize(true);
        recyclerView.setLayoutManager(new GridLayoutManager(this, 1));

        myModelList = new ArrayList<>();

        myModelList.add(new MyModel("Joe", 21));
        myModelList.add(new MyModel("Jane", 26));
        myModelList.add(new MyModel("Kyle", 19));
        myModelList.add(new MyModel("Scott", 30));

        customAdapter = new CustomAdapter(this, myModelList);
        recyclerView.setAdapter(customAdapter);
    }

    public void onClickBtn(@NonNull View v)
    {
        String searchString = "Kyle";
        searchedList = new ArrayList<>();

        for (int x = myModelList.size(), i = 0; i < x; ++i) {
            if (myModelList.get(i).getName().equals(searchString)){
                searchedList.add(myModelList.get(i));
            }
        }
        customAdapter.updateList(searchedList);
        customAdapter.setSearchMode(true);
    }
}

I am mostly develop in kotlin so maybe some text error can happen in this code. you can check the logic for this requirement

  • Thank you for the reply. Really appreciate it. This seems to work well, however, instead of filtering the items and only returning those that are found, I'd like to still maintain and display the entire list and only set color green for those found. – JMoli Apr 25 '22 at 13:14