Search code examples
javaandroidandroid-listviewandroid-package-managers

Perform/Add search functionality to a custom ListView


i created a custom listView of all installed apps but i don't know how to add a search functionality because it's a little complicated(..my app) can anyone help me with that? (picture of the app )

App.java - app constructor

public class App {
    private int number;
    private String name;
    private String version;
    private Drawable drawable;

    public App(int number, String name, String version, Drawable drawable){
        this.number = number;
        this.name = name;
        this.version = version;
        this.drawable = drawable;
    }
    //Getters & Setters...
}

AppAdapter.java - listView Adapter

public class AppAdapter extends ArrayAdapter<App> {
    Context context;
    List<App> objects;

    public AppAdapter(Context context, int resources, int textViewResources, List<App> objects){
        super(context, resources, textViewResources, objects);

        this.context = context;
        this.objects = objects;
    }

    public View getView(int position, View convertView, ViewGroup parent){
        LayoutInflater layoutInflater = ((Activity)context).getLayoutInflater();
        View view = layoutInflater.inflate(R.layout.custom_card,parent,false);

        TextView tvName =  (TextView)view.findViewById(R.id.tvName);
        TextView tvVersion =  (TextView)view.findViewById(R.id.tvVersion);
        TextView tvNumber =  (TextView)view.findViewById(R.id.tvNumber);
        ImageView ivImage = (ImageView)view.findViewById(R.id.ivImage);

        App current = objects.get(position);
        tvName.setText(String.valueOf(current.getName()));
        tvVersion.setText(String.valueOf(current.getVersion()));
        tvNumber.setText(String.valueOf(current.getNumber()));
        ivImage.setImageDrawable(current.getDrawable());

        return view;
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {   
    ArrayList<App> appList;
    ListView lv;
    AppAdapter appAdapter;
    App lastSelected;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        EditText etSearch = findViewById(R.id.etSearch);
        
        PackageManager packageManager = getPackageManager();
        List<PackageInfo> mApps = packageManager.getInstalledPackages(0);
        //array strings to all packages, names and version
        final String[] arrPackages = new String[mApps.size()];
        final String[] arrVersion = new String[mApps.size()];
        String[] arrName = new String[mApps.size()];
        //array of Drawables for icons...
        Drawable[] arrIcons = new Drawable[mApps.size()];
        App[] arrApps = new App[mApps.size()];
        appList = new ArrayList<>();
        //reading all app's packages and version to the arrays
        for (int i = 0; i < mApps.size(); i++){
            arrVersion[i] = mApps.get(i).versionName;
            arrPackages[i] = mApps.get(i).packageName;
        }

        for (int i = 0; i < mApps.size(); i++){
            try {//getting app's names from theres packages
                arrName[i] = (String) packageManager.getApplicationLabel(packageManager.getApplicationInfo(arrPackages[i], PackageManager.GET_META_DATA));
            } catch (PackageManager.NameNotFoundException e) {
                arrName[i] = "Unknown";
            }

            try {//same as names for icons
                arrIcons[i] = packageManager.getApplicationIcon(arrPackages[i]);
            } catch (PackageManager.NameNotFoundException e) {
                arrIcons[i] = getDrawable(R.drawable.placeholder);
            }
            arrApps[i] = new App(i + 1, "Name: "+arrName[i], "Version: "+arrVersion[i], arrIcons[i]);
            appList.add(arrApps[i]);
        }
        //on item click open app
        appAdapter = new AppAdapter(this,0,0,appList);
        lv = findViewById(R.id.lv);
        lv.setAdapter(appAdapter);

        lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                lastSelected = appAdapter.getItem(position);
                Intent launchIntent = getPackageManager().getLaunchIntentForPackage(arrPackages[position]);
                if (launchIntent != null) {
                    startActivity(launchIntent);//null pointer check in case package name was not found
                }
            }
        });

        //(trying to..) Add Text Change Listener to EditText
        etSearch.addTextChangedListener(new TextWatcher() {
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                // Call back the Adapter with current character to Filter
                MainActivity.this.appAdapter.getFilter().filter(s.toString());
            }
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count,int after) {
            }
            @Override
            public void afterTextChanged(Editable s) {
            }
        });
    }
}

(when i try to search something it's gives nothing...)


Solution

  • exchange this line

    App current = objects.get(position);
    

    to this

    App current = (App) getItem(position);
    

    you don't need to keep List<App> objects; inside your adapter, it is passed in constructors super call (last param) and kept under the hood for you. check out ArrayAdapter source - passed ArrayList is kept as mObjects and further used in some methods, e.g. getPosition, getCount and getItem

    ArrayAdapter already implements Filterable, so there is an overriden method getFilter, which returns ArrayFilter - this inner class is declared on the bottom. when you call getFilter().filter( then performFiltering gets called (in a separated thread) and iterates through local copy of your data (line 588). It is using values.get(i).toString().toLowerCase() to compare objects from array with passed String (CharSequence in fact). so in your custom App class override toString method and return in there some searchable value, e.g.

    @Override
    public String toString(){
        return name;
    }
    

    this is not best approach, because toString may be used in a lot of mechanisms (its base method of Object) and with above toString implementation two Apps with same name, but different version or number are threated as same object, which isn't true... maybe better way would be to return name+version+number;, but still you have also Drawable in there. thats why I've suggested (in this answer before edit) to make own class extends BaseAdapter and implement own filtering or at least use ArrayAdapter, but override getFilter method and return your own Filter implementation comparing variables instead of using toString. Then won't be needed to override this class, leave it as it is. By default it returns kind-of memory address, so it is unique for every new App instance, even when created with exacly same variables

    also in THIS answer you can find nice example how to implement that Filter