How to notify observers after filtering LiveData<List<Note>> using SearchView in ViewModel?

41 Views Asked by At

I have an entity named Note, along with its data access object (NoteDAO) that contains SQL-specific logic. There's also a NoteDatabase class that extends RoomDatabase and a NoteRepository that utilizes both NoteDAO and NoteDatabase. The NoteViewModel interacts with the database through NoteRepository.

In a fragment, I use NoteViewModel to retrieve all notes in the onCreateView. After observing these notes, I set the RecyclerView items using adapter.submitList.

However, when incorporating a SearchView, I face an issue. While I can retrieve the query text from SearchView, my attempt to filter the underlying allNotes (a member variable in NoteViewModel of type LiveData<List<Note>>) didn't notify the observers as expected.

On learning about the distinction between MutableLiveData and LiveData, I considered changing the allNotes member in NoteViewModel to MutableLiveData. This should theoretically allow me to use the setValue or postValue methods inside the filterNotes(String searchTerm) function, hence notifying the observers. But this didn't work – I couldn't retrieve even the unfiltered notes list with MutableLiveData.

FirstFragment

@Override
public View onCreateView(
        .......
) {
    ........

    RecyclerView recyclerView = binding.recyclerView;
    final NoteListAdapter adapter = new NoteListAdapter(new NoteListAdapter.WordDiff());
    recyclerView.setAdapter(adapter);
    recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));

    mNoteViewModel = new ViewModelProvider(requireActivity()).get(NoteViewModel.class);

    mNoteViewModel.getAllNotes().observe(getViewLifecycleOwner(), new Observer<List<Note>>() {
                @Override
                public void onChanged(List<Note> notes) {
                    Log.d("MainPage", "onChanged: " + notes);
                    adapter.submitList(notes);
                }
            });
   
    ........
}

NoteViewModel

public class NoteViewModel extends AndroidViewModel {

    private final NoteRepository mRepository;

    private final LiveData<List<Note>> mAllNotes;

    public NoteViewModel(@NonNull Application application) {
        super(application);
        mRepository = new NoteRepository(application);
        mAllNotes = mRepository.getAllNotes();
    }

    LiveData<List<Note>> getAllNotes() {
        return mAllNotes;
    }

    public void insert (Note note) { mRepository.insert(note); }
}

NoteRepository

public class NoteRepository {

    private final NoteDAO mNoteDAO;

    @NotNull
    private final LiveData<List<Note>> mAllNotes;

    NoteRepository(Application application) {
        NoteRoomDatabase db = NoteRoomDatabase.getDatabase(application);
        mNoteDAO = db.noteDAO();
        mAllNotes = mNoteDAO.getAllNotes();
    }

    public LiveData<List<Note>> getAllNotes() {
        return mAllNotes;
    }

    public void insert(Note note) {
        NoteRoomDatabase.databaseWriteExecutor.execute(() -> {
            mNoteDAO.insert(note);
        });
    }

}

NoteDAO

@Dao
public interface NoteDAO {

    @Insert
    void insert(Note note);

    @Query("SELECT * FROM notes ORDER BY id DESC")
    LiveData<List<Note>> getAllNotes();

    @Query("SELECT * FROM notes where title = :title ORDER BY id DESC")
    LiveData<List<Note>> getNotesByTitle(String title);

    @Query("SELECT * FROM notes where id = :id ORDER BY id DESC")
    LiveData<Note> getNoteById(long id);

    @Query("SELECT * FROM notes where color = :color ORDER BY id DESC")
    LiveData<List<Note>> getNotesByColor(String color);

}
1

There are 1 best solutions below

0
Shreyas Sparrow On

I have two approach

  1. Please make use of Kotlin flow instead of livedata. You can use powerful flow extension function which will fulfill your needs. Here is an example
class NoteViewModel():AndroidViewModel{
 private val noteItemsFlow = combine(mAllNotes, searchQueryFlow){ items, query ->
        items.filter{
            it.noteName.contains(query) // Here you can write a logic to filter according to search query
        }
    }
}

// In Fragment
noteItemsFlow.collectLatest{
   adapter.submitList(it)
}
  1. You can implement or extend filterable class in Recycler adapter and override getFilter method here is the code
class NoteListAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>(), Filterable {
    val notes = ArrayList<Note>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return NoteVieHolder()
    }

    override fun getItemCount(): Int {
        return notes.size
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        // Replace your onBindViewHolder code here
    }

    inner class NoteVieHolder: RecyclerView.ViewHolder(){
        // Replace your view holder code here
    }


    fun submitList(latestNotes:List<Note>){
        notes.clear()
        notes.addAll(latestNotes)
        notifyDataSetChanged()
    }

    override fun getFilter(): Filter {
        return  object : Filter() {
            override fun performFiltering(constraint: CharSequence?): FilterResults {
                val results = FilterResults()
                if (!constraint.isNullOrEmpty()){
                    val filterNotes = notes.filter { 
                        it.title.contains(constraint)
                    }
                    results.count = filterNotes.size
                    results.values = filterNotes
                }else{
                    results.count = notes.size
                    results.values = notes
                }
                
                return  results
            }

            override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
                if (results?.values != null) {
                    submitList(results.values as List<Note>)
                }
            }
        }
    }
}

Hope this helps