Search code examples
androidautocompletetextview

Android AutoComplete TextView with Custom ArrayAdapter and with Firestore not working


What I want to do is to populate AutoComplete TextView with the data from Firestore. I tried to retrieve data from Firestore and use it to create a Songs Object and store it in an ArrayList named songs. Those data are all Strings. You can see below. auto refers to the AutoComplete TextView.

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        seekbar = findViewById(R.id.seekBar);
        auto = findViewById(R.id.auto);
        current = findViewById(R.id.start);
        end = findViewById(R.id.end);
        lol.add("chey");
        lol.add("oi");
        db.collection("Songs_names").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
            @Override
            public void onComplete(@NonNull Task<QuerySnapshot> task) {
                if(task.isSuccessful()){
                    if (task.getResult() != null) {
                        for (QueryDocumentSnapshot document : task.getResult()) {
                            Log.d("tag","msg " + document.getString("name"));
                            songs.add(new Songs(document.getString("name")));
                            Log.d("show objects","showed" + songs);
                        }

                    }
                }
            }
        });
        songs_auto_adapter auto_adapter = new songs_auto_adapter(this,songs,this);
        Log.d("show songs","show" + songs);
        //ArrayAdapter<String> adapter = new ArrayAdapter<>(this,android.R.layout.simple_list_item_1,lol);
        auto.setAdapter(auto_adapter);

Then, I setAdapter to auto_adapter. You can see it below.

public class songs_auto_adapter extends ArrayAdapter<Songs> {
    private List<Songs> songsFull;
    private Touch touch;
    public songs_auto_adapter(@NonNull Context context, @NonNull List<Songs> objects,Touch touch) {
        super(context,0, objects);
        songsFull = new ArrayList<>(objects);
        this.touch = touch;
    }

    @NonNull
    @Override
    public Filter getFilter() {
        return filter;
    }

    @NonNull
    @Override
    public View getView(final int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        Log.d("View", "Creating Views");
        if (convertView == null){
            convertView = LayoutInflater.from(getContext()).inflate(R.layout.songs_for_auto,parent,false);
        }
        final TextView textView = convertView.findViewById(R.id.auto_text);
        Songs songs = getItem(position);
        if (songs != null) {
            textView.setText(songs.getName());
        }

        convertView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("clicked","It works" + position);
                touch.Touched(textView.getText().toString().trim());
            }
        });
        return convertView;
    }

    private Filter filter = new Filter() {
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            FilterResults results = new FilterResults();
            List<Songs> suggestions = new ArrayList<>();
            if ( constraint == null || constraint.length() == 0){
                suggestions.addAll(songsFull);
            }
            else{
                String filtertxt = constraint.toString().toLowerCase().trim();
                for (Songs s : songsFull){
                    if (s.getName().toLowerCase().contains(filtertxt)){
                        suggestions.add(s);
                        Log.d("filtering","msg" + suggestions);
                    }
                }
            }
            results.values = suggestions;
            results.count = suggestions.size();
            return results;
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            clear();
            addAll((List) results.values);
            notifyDataSetChanged();
            Log.d("published","mmm" + results.values + songsFull);

        }

    };

The problem is when I typed in the AutoComplete TextView, nothing showed up. Then I used Log.d and I found out that publishResult is the one that gets called first and performFiltering is never called. See the message below.

D/published: mmm[][]
I/zygote: Do partial code cache collection, code=55KB, data=53KB
    After code cache collection, code=55KB, data=53KB
    Increasing code cache capacity to 256KB

However, when I typed in the data manually, like this, songs.add(new Songs("sth")); ,the whole thing works. I cannot think of any solutions to solve this so I am here to ask. Thank you.


Solution

  • You are dealing with network request and it is not sequential. What it means - by the time adapter is created and set to the auto object the network request that returns a list of songs is not yet finished. Or maybe it has finished successfully, or maybe failed - no one knows.

    Thus, from the OnCompleteListener you must make sure you update the list of songs and adapter. When the list is updated from onComplete method - your adapter knows nothing about that update. It does not watch changes in the array by itself.

    You have to do a few things:

    1. When network request is finished - make sure activity is not finished. Otherwise, you can end up with a crashing application (that will be hard to debug);
    2. Make sure you notify the adapter about changes in the list of songs.
    db.collection("Songs_names").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                @Override
                public void onComplete(@NonNull Task<QuerySnapshot> task) {
                    // Step 1: if activity is no longer valid to use just skip this "task" response.
                    if (isFinishing() || isDestroyed()) return;
    
                    if(task.isSuccessful()){
                        if (task.getResult() != null) {
                            for (QueryDocumentSnapshot document : task.getResult()) {
                                Log.d("tag","msg " + document.getString("name"));
                                songs.add(new Songs(document.getString("name")));
                                Log.d("show objects","showed" + songs);
                                
                                // Step 2: songs are updated
                                adapter.updateList(songs);
                            }
    
                        }
                    }
                }
            });
    

    Now you have to modify your adapter a little bit. A new method is added that accepts a list of songs and notifies adapter about the changes.

    public class songs_auto_adapter extends ArrayAdapter<Songs> {
        private List<Songs> songsFull;
        private Touch touch;
        public songs_auto_adapter(@NonNull Context context, @NonNull List<Songs> objects,Touch touch) {
            super(context,0, objects);
            songsFull = new ArrayList<>(objects);
            this.touch = touch;
        }
    
        public void updateList(@NonNull List<Songs> newList) {
            songsFull = new ArrayList<>(newList);
            clear();
            addAll(songsFull); // Adapter is now aware of the updated list
        }
        
        ...
    
    }