Search code examples
androidandroid-listviewsearchview

ListView not updated on publish results using searchview


I have been writing an android app that uses SearchView to search through my db, I am able to search the notes, when I do a Log.e() on the results arraylist then i see proper results, but the listview which should change to display the search results doesn't get affected. I am using a custom Adapter. I just didn't get the concept behind how exactly is the results in the publishResults method going to modify my listview in real time

MainActivity.java

public class MainActivity extends RoboActionBarActivity {

    private static final int NEW_NOTE_RESULT_CODE = 4;
    private static final int EDIT_NOTE_RESULT_CODE = 5;

    @InjectView(android.R.id.empty)   private TextView emptyListTextView;
    @InjectView(android.R.id.list)    private ListView listView;
    @InjectView(R.id.add_note_button) private FloatingActionButton addNoteButton;

    @Inject private NoteDAO noteDAO;

    private ArrayList<Integer> selectedPositions;
    private ArrayList<NotesAdapter.NoteViewWrapper> notesData;
    private NotesAdapter listAdapter;
    private ActionMode.Callback actionModeCallback;
    private ActionMode actionMode;

    /** {@inheritDoc} */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Inicializa los componentes //////////////////////////////////////////////////////////////
        listView.setOnTouchListener(new ShowHideOnScroll(addNoteButton, getSupportActionBar())); // Esconde o muesta el FAB y la Action Bar
        addNoteButton.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View view) {
                // Crear una nota nueva
                startActivityForResult(EditNoteActivity.buildIntent(MainActivity.this), NEW_NOTE_RESULT_CODE);
            }
        });
        selectedPositions = new ArrayList<>();
        listView.setTextFilterEnabled(true);
        setupNotesAdapter();
        setupActionModeCallback();
        setListOnItemClickListenersWhenNoActionMode();
        updateView();
    }

    /** {@inheritDoc} */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    @Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.main, menu);

    // Get the SearchView and set the searchable configuration
    SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
    SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
    // Assumes current activity is the searchable activity
    searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
    searchView.setIconifiedByDefault(true); // Do not iconify the widget; expand it by default

    SearchView.OnQueryTextListener textChangeListener = new SearchView.OnQueryTextListener()
    {
        @Override
        public boolean onQueryTextChange(String query)
        {

            listAdapter.getFilter().filter(query);
            return true;
        }
        @Override
        public boolean onQueryTextSubmit(String query)
        {
            // this is your adapter that will be filtered

            return false;
        }
    };
    searchView.setOnQueryTextListener(textChangeListener);
    return true;
}



    /** {@inheritDoc} */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_about_info:
                new AboutNoticeDialog()
                        .show(getSupportFragmentManager(), "dialog_about_notice");
                return true;
            case R.id.action_import:
                importNotes();
                return true;

            case R.id.action_export:
                exportNotes();
                return true;
            default: return super.onOptionsItemSelected(item);
        }
    }

    private void exportNotes() {
        for (Note note : noteDAO.fetchAll()) {
            StringBuilder exportNotes = new StringBuilder();

        }
        }

    private void importNotes() {

    }

    /** {@inheritDoc} */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == NEW_NOTE_RESULT_CODE) {
            if (resultCode == RESULT_OK) addNote(data);
        }
        if (requestCode == EDIT_NOTE_RESULT_CODE) {
            if (resultCode == RESULT_OK) updateNote(data);
        }

        super.onActivityResult(requestCode, resultCode, data);
    }

    /** Crea la llamada al modo contextual. */
    private void setupActionModeCallback() {
        actionModeCallback = new ActionMode.Callback() {

            /** {@inheritDoc} */
            @Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                setListOnItemClickListenersWhenActionMode();
                // inflar menu contextual
                mode.getMenuInflater().inflate(R.menu.context_note, menu);
                return true;
            }

            /** {@inheritDoc} */
            @Override
            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                // Nada
                return false;
            }

            /** {@inheritDoc} */
            @Override
            public boolean onActionItemClicked(final ActionMode mode, MenuItem item) {
                switch (item.getItemId()) {
                    // borrar notas solo si hay notas a borrar; sino se acaba el modo contextual.
                    case R.id.action_delete:
                        if (!selectedPositions.isEmpty()) {
                            new AlertDialog.Builder(MainActivity.this)
                                    .setMessage(getString(R.string.delete_notes_alert, selectedPositions.size()))
                                    .setNegativeButton(android.R.string.no, null)
                                    .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {

                                        @Override
                                        public void onClick(DialogInterface dialog, int which) {
                                            deleteNotes(selectedPositions);
                                            mode.finish();
                                        }
                                    })
                                    .show();
                        } else mode.finish();
                        return true;
                    case R.id.action_share:
                        if (!selectedPositions.isEmpty()){
                            shareNotes(selectedPositions);
                        }
                        return true;
                    case R.id.action_select_all:
                        selectAll();
                    default:
                        return false;
                }
            }

            /** {@inheritDoc} */
            @Override
            public void onDestroyActionMode(ActionMode mode) {
                // Regresar al modo normal
                setListOnItemClickListenersWhenNoActionMode();
                resetSelectedListItems();
            }
        };
    }

    private void selectAll() {
        for ( int i=0; i < listAdapter.getCount(); i++) {
            selectedPositions.add(i);
            notesData.get(i).setSelected(true);
            listAdapter.notifyDataSetChanged();
        }
    }

    /** Inicializa el adaptador de notas. */
    private void setupNotesAdapter() {
        notesData = new ArrayList<>();
        for (Note note : noteDAO.fetchAll()) { // Convertir a wrapper
            NotesAdapter.NoteViewWrapper noteViewWrapper = new NotesAdapter.NoteViewWrapper(note);
            notesData.add(noteViewWrapper);
        }
        listAdapter = new NotesAdapter(notesData);
        listView.setAdapter(listAdapter);
    }

    /** Actualiza la vista de esta actividad cuando hay notas o no hay notas. */
    private void updateView() {
        if (notesData.isEmpty()) { // Mostrar mensaje
            listView.setVisibility(View.GONE);
            emptyListTextView.setVisibility(View.VISIBLE);
        } else { // Mostrar lista
            listView.setVisibility(View.VISIBLE);
            emptyListTextView.setVisibility(View.GONE);
        }
    }

    /**
     * Agrega una nota a lista y la fuente de datos.
     *
     * @param data los datos de la actividad de edición de notas.
     */
    private void addNote(Intent data) {
        Note note = EditNoteActivity.getExtraNote(data);
        noteDAO.insert(note);
        NotesAdapter.NoteViewWrapper noteViewWrapper = new NotesAdapter.NoteViewWrapper(note);
        notesData.add(0,noteViewWrapper);
        updateView();
        listAdapter.notifyDataSetChanged();
    }
/*
this method is called when the contextual action button of share is pressed
 */
    private void shareNotes(ArrayList<Integer> selectedPositions) {
        StringBuilder shareBody = new StringBuilder();
        // Primero borra de la base de datos
        for (int position : selectedPositions) {
            NotesAdapter.NoteViewWrapper noteViewWrapper = notesData.get(position);
            shareBody.append(noteViewWrapper.getNote().getContent());
        }

        Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
        sharingIntent.setType("text/plain");
        sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody.toString());
        startActivity(Intent.createChooser(sharingIntent, getResources().getString(R.string.share_using)));
    }

    /**
     * Borra notas de la lista y de la fuente de datos.
     *
     * @param selectedPositions las posiciones de las notas en la lista.
     */

    private void deleteNotes(ArrayList<Integer> selectedPositions) {
        ArrayList<NotesAdapter.NoteViewWrapper> toRemoveList = new ArrayList<>(selectedPositions.size());
        // Primero borra de la base de datos
        for (int position : selectedPositions) {
            NotesAdapter.NoteViewWrapper noteViewWrapper = notesData.get(position);
            toRemoveList.add(noteViewWrapper);
            noteDAO.delete(noteViewWrapper.getNote());
        }
        // Y luego de la vista (no al mismo tiempo porque pierdo las posiciones que hay que borrar)
        for (NotesAdapter.NoteViewWrapper noteToRemove : toRemoveList) notesData.remove(noteToRemove);
        updateView();
        listAdapter.notifyDataSetChanged();
    }

    /**
     * Actualiza una nota en la lista y la fuente de datos.
     *
     * @param data los datos de la actividad de edición de notas.
     */
    private void updateNote(Intent data) {
        Note updatedNote = EditNoteActivity.getExtraUpdatedNote(data);
        noteDAO.update(updatedNote);
        for (NotesAdapter.NoteViewWrapper noteViewWrapper : notesData) {
            // Buscar la nota vieja para actulizarla en la vista
            if (noteViewWrapper.getNote().getId().equals(updatedNote.getId())) {
                noteViewWrapper.getNote().setContent(updatedNote.getContent());
                noteViewWrapper.getNote().setUpdatedAt(updatedNote.getUpdatedAt());
            }
        }
        listAdapter.notifyDataSetChanged();
    }

    /** Reinicia las notas seleccionadas a no seleccionadas y limpia la lista de seleccionados. */
    private void resetSelectedListItems() {
        for (NotesAdapter.NoteViewWrapper noteViewWrapper : notesData) noteViewWrapper.setSelected(false);
        selectedPositions.clear();
        listAdapter.notifyDataSetChanged();
    }

    /**
     * Inicializa las acciones de la lista al hacer click en sus items cuando NO esta activo el
     * modo contextual.
     */
    private void setListOnItemClickListenersWhenNoActionMode() {
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                // Ver la nota al hacer click
                startActivityForResult(ViewNoteActivity.buildIntent(MainActivity.this, notesData.get(position).getNote()), EDIT_NOTE_RESULT_CODE);

            }
        });
        listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {

            @Override
            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
                // Iniciar modo contextual para selección de items
                notesData.get(position).setSelected(true);
                listAdapter.notifyDataSetChanged();
                selectedPositions.add(position);
                actionMode = startSupportActionMode(actionModeCallback);
                return true;
            }
        });
    }

    /**
     * Inicializa las acciones de la lista al hacer click en sus items cuando esta activo el menu
     * contextual.
     */
    private void setListOnItemClickListenersWhenActionMode() {
        listView.setOnItemLongClickListener(null);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                // Agregar items a la lista de seleccionados y cambiarles el fondo.
                // Si se deseleccionan todos los items, se acaba el modo contextual
                if (selectedPositions.contains(position)) {
                    selectedPositions.remove((Object)position); // no quiero el índice sino el objeto
                    if (selectedPositions.isEmpty()) actionMode.finish();
                    else {
                        notesData.get(position).setSelected(false);
                        listAdapter.notifyDataSetChanged();
                    }
                } else {
                    notesData.get(position).setSelected(true);
                    listAdapter.notifyDataSetChanged();
                    selectedPositions.add(position);
                }
            }
        });
    }
}

NotesAdapter.java

public class NotesAdapter extends BaseAdapter implements Filterable{

    private static final DateFormat DATETIME_FORMAT = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);

    private final List<NoteViewWrapper> data;
    private List<NoteViewWrapper> filtered_data;


     /** Wrapper para notas. Util para cambiar el fondo de los item seleccionados. */
    public static class NoteViewWrapper {

        private final Note note;
        private boolean isSelected;

        /**
         * Contruye un nuevo NoteWrapper con la nota dada.
         *
         * @param note una nota.
         */
        public NoteViewWrapper(Note note) {
            this.note = note;
        }

        public Note getNote() {
            return note;
        }

        public void setSelected(boolean isSelected) {
            this.isSelected = isSelected;
        }
    }



    /**
     * Constructor.
     *
     * @param data la lista de notas a usar como fuente de datos para este adaptador.
     */
    public NotesAdapter(List<NoteViewWrapper> data) {
        this.data = data;
        this.filtered_data = data;
    }

    /** @return cuantos datos hay en la lista de notas. */
    @Override
    public int getCount() {
        return data.size();
    }

    /**
     * @param position la posición de la nota que se quiere
     * @return la nota en la posición dada.
     */
    @Override
    public NoteViewWrapper getItem(int position) {
        return data.get(position);
    }

    /**
     * @param position una posición
     * @return la misma posición dada
     */
    @Override
    public long getItemId(int position) {
        return position;
    }

    /**
     * Muestra los datos de la nota en la posición dada en una instancia del componente visual
     * {@link com.rants.R.layout#notes_row}.
     *
     * @see <a href="http://bit.ly/MJqzXb">Hold View Objects in a View Holder</a>
     * @param position la posición de la nota en curso.
     * @param convertView el componente visual a usar.
     * @param parent el componente visual padre del componente visual a usar.
     * @return la vista con los datos.
     */
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if (convertView == null) { // inflar componente visual
            convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.notes_row, parent, false);
            holder = new ViewHolder(convertView);
            convertView.setTag(holder);
        } else holder = (ViewHolder) convertView.getTag(); // ya existe, solo es reciclarlo
        // Inicializa la vista con los datos de la nota
        NoteViewWrapper noteViewWrapper = data.get(position);
        holder.noteIdText.setText(String.valueOf(noteViewWrapper.note.getId()));
        // Corta la cadena a 80 caracteres y le agrega "..."

        SpannableString hashText = new SpannableString(noteViewWrapper.note.getContent());
        Matcher matcher = Pattern.compile("#([A-Za-z0-9_-]+)").matcher(hashText);
        while (matcher.find())
        {
            hashText.setSpan(new ForegroundColorSpan(Color.BLUE), matcher.start(), matcher.end(), 0);
        }


        holder.noteContentText.setText(hashText);

        holder.noteDateText.setText(DATETIME_FORMAT.format(noteViewWrapper.note.getUpdatedAt()));
        // Cambia el color del fondo si es seleccionado
        if (noteViewWrapper.isSelected) holder.parent.setBackgroundColor(parent.getContext().getResources().getColor(R.color.selected_note));
        // Sino lo regresa a transparente
        else holder.parent.setBackgroundColor(parent.getContext().getResources().getColor(android.R.color.transparent));
        return convertView;
    }

    /** Almacena componentes visuales para acceso rápido sin necesidad de buscarlos muy seguido.*/
    private static class ViewHolder {

        private TextView noteIdText;
        private TextView noteContentText;
        private TextView noteDateText;

        private View parent;

        /**
         * Constructor. Encuentra todas los componentes visuales en el componente padre dado.
         *
         * @param parent un componente visual.
         */
        private ViewHolder(View parent) {
            this.parent = parent;
            noteIdText = (TextView) parent.findViewById(R.id.note_id);
            noteContentText = (TextView) parent.findViewById(R.id.note_content);
            noteDateText = (TextView) parent.findViewById(R.id.note_date);
        }
    }

@Override
public Filter getFilter() {
Filter filter = new Filter(){

    @Override
    protected FilterResults performFiltering(CharSequence query) {
        FilterResults results = new FilterResults();
        ArrayList<NoteViewWrapper> filtered = new ArrayList<NoteViewWrapper>();
        query = query.toString().toLowerCase();

        for(int i=0;i<data.size();i++){
            String dataName = data.get(i).getNote().getContent().toLowerCase();
            if (dataName.contains(query)){
                filtered.add(data.get(i));
            }

        }
        results.count = filtered.size();
        results.values = filtered;
        Log.e("values", results.values.toString());
        return results;
    }

    @Override
    protected void publishResults(CharSequence constraint, FilterResults results) {
        if (results.count == 0){
            notifyDataSetInvalidated();
        } else {
            filtered_data = (ArrayList<NoteViewWrapper>) results.values;
            NotesAdapter.this.notifyDataSetChanged();
            Log.e("we are in publish results",filtered_data.toString());
        }
    }
};

return filter;
}
}

Now my question is:

  1. why isn't my listview not getting updated at all? I know for sure that my performFiltering method works properly because of the proper search results getting logged in the logcat output?
  2. how does the publishResult method affect the entire process
  3. how does the listview know which arraylist's data will be updated to the listview?
    1. What does the notifyDataSet() in the publish results accomplish? how can it refresh the dataset of a listview that isn't even in the same class?

pardon me if this is a silly question, I am not an expert on android and I haven't programmed in Java for quite some time now.


Solution

  • Your filtered data is

     filtered_data = (ArrayList<NoteViewWrapper>) results.values;
                NotesAdapter.this.notifyDataSetChanged();
                Log.e("we are in publish results",filtered_data.toString());
    

    You are using

    NoteViewWrapper noteViewWrapper = data.get(position);
    

    Use this code instead

    NoteViewWrapper noteViewWrapper = filtered_data.get(position);
    

    in your method

    @Override
        public View getView(int position, View convertView, ViewGroup parent) {}
    

    The following method is used to tell the adapter that dataset(ArraList) has changed and adapter should update the results in ListView,GridView etc

    notifyDataSetChanged();