Potential memory leaks when using searchView along with ROOM database and live data

68 views Asked by At

I'm looking to implement a SearchView feature in an app that utilizes the ROOM library. During my online research, I noticed that many examples follow a pattern similar to the one below:

override fun onQueryTextChange(newText: String?): Boolean {
        if (newText != null) {
            mViewModel.searchAllDatabase(newText).observe(viewLifecycleOwner){
                ...
            }
        }
        return true
    }

Now, I'm curious to know if I'm getting the whole thing right. Could it be the case that with this approach, a new observer is created each time a user enters a new letter and the onQueryTextChange method is triggered? If that's the case, does this potentially lead to memory leaks? Or, on the other hand, does the viewLifecycleOwner automatically handle these observers, ensuring they are disposed of when they're no longer needed?

Edit:what if we follow this approach:

override fun onQueryTextChange(newText: String?): Boolean {
        if (newText != null) viewModel.searchQuery.value = newText
        return true
    }

Inside onViewCreated:

viewModel.allData.observe(viewLifecycleOwner) {
        adapter.setData(it)
    }
viewModel.searchQuery.observe(viewLifecycleOwner) { query ->
        if (viewModel.allData.value != null) {
            adapter.setData(viewModel.allData.value!!.filter { it.title.contains(query) })
        }
    }
1

There are 1 answers

0
Tenfour04 On BEST ANSWER

From what you’ve shown, this does leak observers. There will be obsolete observers sitting in memory for almost every character typed or deleted until the Fragment’s view is destroyed. Even worse, if these observers come from Room, they will all be running queries every time the database is modified until they are released.

One way to avoid this would be to use a switch map operation based on the search query. The switch map operation does exactly what we want…when there is new data, it cancels observing the previous LiveData and starts a new one. This is done under its own hood, so to an outside observer, it’s just a single LiveData stream.

In the ViewModel, something like this:

private val searchQuery = MutableLiveData("")

val queryText: String
    get() = searchQuery.value
    set(value) { searchQuery.value = value }

val searchedData = searchQuery.switchMap { query ->
    if (query.isEmpty()) repo.allData else repo.searchAll(query)
}

Now in your UI classes you only observe viewModel.searchedData a single time in onViewCreated() and do nothing but set the new value of ViewModel.queryText in onQueryTextChange().


Regarding your proposed solution, there are a couple of drawbacks.

First, it is really hacky to be trying read the value of a LiveData directly instead of observing it. In this case it leaves open the possibility of the user typing a letter before the allData gets its first value set, leaving the screen blank until the user types again.

Second, you are manually sifting through the data to perform the search yourself, which is likely less efficient than letting the repository do it. Databases are optimized for that kind of thing.