How to use LiveData for searching?

1.1k Views Asked by At

I have a ViewModel that has a MutableLiveData of an arraylist of class Course

private var coursesList: MutableLiveData<ArrayList<Course>> = MutableLiveData()

This coursesList is filled with data got from an API (by Retrofit): coursesList.postValue(response.body())

Now, a user can search for a course by its name. The function that I have for searching is that I iterate through the elements of the coursesList and check if its name is equal to what a user typed. It returns an arrayList with the courses that start with the name typed (this list is later sent to a fragment which passes it to an adapter to be shown in a recyclerview):

fun getCoursesList(): MutableLiveData<ArrayList<Course>> {
        return coursesList
}

fun searchCourses(searchString: String): ArrayList<Course> {

    val resultsList: ArrayList<Course> = ArrayList()

    if (getCoursesList().value == null) return resultsList

    if (getCoursesList().value!!.size > 0) {
       
        for (course in getCoursesList().value!!.iterator()) {
            if (course.name.toLowerCase(Locale.ROOT).startsWith(searchString)) {
                resultsList.add(course)
            }
        }
    }
    resultsList.sortBy { it.price }
    return resultsList
}

This function works and all but my instructor asked me to use LiveData for searching without giving any additional hints on how to do that.

So my question is how to use LiveData for searching? I tried to search for answers, I saw that some used LiveDataTransformations.switchMap but they were all using RoomDAOs and I couldn't adapt it to the code that I have.

Any help would be appreciated very much. Thanks in advance.

2

There are 2 best solutions below

2
On BEST ANSWER

Maybe that can help you a little bit,

class YourViewModel(
   private val courcesRepository: CourcesRepository
) : ViewModel() {

// Private access - mutableLiveData!
private val _coursesList = MutableLiveData<ArrayList<Course>>()

// Public access - immutableLiveData
val coursesList: LiveData<ArrayList<Course>>
    get() = _coursesList

init {
    // mutableLiveData initialize, automatic is immutable also initialize
    _coursesList.postValue(getCourses())
}

// Here you get your data from repository
private fun getCourses(): ArrayList<Course> {
    return courcesRepository.getCources()
}

// Search function
fun searchCourses(searchString: String) {

    // you hold your data into this methode
    val list: ArrayList<Course> = getCources()

    if (searchString.isEmpty()) {

        // here you reset the data if search string is empty
        _coursesList.postValue(list)
    } else {

        // here you can search the list and post the new one to your LiveData
        val filterList = list.filter {
            it.name.toLowerCase(Locale.ROOT).startsWith(searchString)
        }
        filterList.sortedBy { it.price }
        _coursesList.postValue(filterList)
    }
}

}

The first tip is you should use LiveData like below, that is also recommended from google's jet pack team. The reason is so you can encapsulate the LivaData.

The second tip is you should use kotlin's idiomatic way to filter a list. Your code is readable and faster.

At least is a good idea to make a repository class to separate the concerns in your app.

And some useful links for you:

https://developer.android.com/jetpack/guide

https://developer.android.com/topic/libraries/architecture/livedata

I hope that's helpful for you

0
On

Ii is hard to guess the desired outcome, but a possible solution is to use live data for searched string also. And then combine them with coursesList live data into live data for searched courses, like this for example.

    val searchStringLiveData: MutableLiveData<String> = MutableLiveData()
    val coursesListLiveData: MutableLiveData<ArrayList<Course>> = MutableLiveData()
    val searchedCourses: MediatorLiveData<ArrayList<Course>> = MediatorLiveData()

    init {
        searchedCourses.addSource(searchStringLiveData) {
            searchedCourses.value = combineLiveData(searchStringLiveData, coursesListLiveData)
        }
        searchedCourses.addSource(coursesListLiveData) {
            searchedCourses.value = combineLiveData(searchStringLiveData, coursesListLiveData)
        }
    }


    fun combineLiveData(searchStringLiveData: LiveData<String>, coursesListLiveData: LiveData<ArrayList<Course>> ): ArrayList<Course> {
        // your logic here to filter courses
        return ArrayList()
    }

I haven't run the code so I am not 100% sure that it works, but the idea is that every time either of the two live data changes value, searched string or courses, the combine function is executed and the result is set as value of the searchedCourses mediator live data. Also I omitted the logic of the filtering for simplicity.