androidkotlinandroid-recyclerviewandroid-livedataandroid-filterable

RecycleView List with SearchView ist empty after back pressed


I am struggling with setting up my recycle view with a search view, well not really because my search view does work with my recycle view. The filter works and the items are displayed liek i want them to after i entered my search query. But when i click a list item to show the details and in the detail view press the back button, the list won't show me my items, the list is empty. I think it as someting to do with my filtering setup because when i don`t set the OnQueryTextListener it just works fine. Maybe someone has a hint for me what i am missing. I have a tabbed layout with 2 different fragments which both have a recycle view:

class WorkoutListFragment : Fragment() {

private var adapter: WorkoutListTabAdapter? = null

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    return inflater.inflate(R.layout.fragment_workout_list, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    adapter = WorkoutListTabAdapter(childFragmentManager)

    viewPager.adapter = adapter
    workoutListTabs.setupWithViewPager(viewPager);
}
}

I only show 1 List Fragment to keep it more clear

class WorkoutCustomListFragment : Fragment() {

private lateinit var mAdapter: WorkoutListAdapter
private lateinit var listViewModel: ListViewModel

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {

    listViewModel = ViewModelProviders.of(this.activity!!).get(ListViewModel::class.java)

    val dataBinding: FragmentCustomWorkoutListBinding =
        DataBindingUtil.inflate(
            inflater,
            R.layout.fragment_custom_workout_list,
            container,
            false
        )
    val view: View = dataBinding.root
    dataBinding.viewmodel = listViewModel
    return view
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    setupRecycleView()
    listViewModel.getWorkouts().observe(this, Observer { workouts ->
        mAdapter.setData(workouts)
    })

    listViewModel.openWorkoutDetail.observe(this, EventObserver { workoutId ->
        val action =
            WorkoutListFragmentDirections.actionWorkoutListFragmentToWorkoutDetailFragment(
                workoutId
            )
        findNavController().navigate(action)
    })

    listViewModel.openCreateWorkoutEvent.observe(this, EventObserver {
        val action =
            WorkoutListFragmentDirections.actionWorkoutListFragmentToWorkoutCreateFragment(0)
        findNavController().navigate(action)
    })

    setUpSwipeHandler(mAdapter)

    customListSearchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
        override fun onQueryTextSubmit(p0: String?): Boolean {
            mAdapter.filter.filter(p0)
            return false
        }

        override fun onQueryTextChange(p0: String?): Boolean {
            mAdapter.filter.filter(p0)
            return false
        }
    })
}

private fun setupRecycleView() {
    mAdapter = WorkoutListAdapter(listViewModel)
    workoutList.adapter = mAdapter
    workoutList.layoutManager = LinearLayoutManager(activity)
}

private fun setUpSwipeHandler(
    adapter: WorkoutListAdapter
) {
    ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(
        0,
        ItemTouchHelper.RIGHT
    ) {
        override fun onMove(
            recyclerView: RecyclerView,
            viewHolder: RecyclerView.ViewHolder,
            target: RecyclerView.ViewHolder
        ): Boolean {
            return false
        }

        override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
            val workoutToDelete = adapter.getWorkout(viewHolder.adapterPosition)
            listViewModel.delete(workoutToDelete.id)
            Toast.makeText(
                activity,
                "Workout ${workoutToDelete.title} wurde gelöscht",
                Toast.LENGTH_SHORT
            ).show()
            adapter.removeItemFromList(workoutToDelete.id)
            adapter.notifyItemRemoved(viewHolder.adapterPosition)
            adapter.notifyDataSetChanged()
        }
    }).attachToRecyclerView(workoutList)
}
}

And my Adapter

class WorkoutListAdapter(private val viewModel: ListViewModel) :
RecyclerView.Adapter<WorkoutListAdapter.ViewHolder>(), Filterable {

private var mWorkouts = ArrayList<Workout>()
private var mWorkoutsFull = ArrayList<Workout>()

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

fun setData(workouts: List<Workout>?) {
    mWorkouts = workouts as ArrayList<Workout>
    mWorkoutsFull.addAll(workouts)
    notifyDataSetChanged()
}

inner class ViewHolder(private val binding: WorkoutlistWorkoutItemBinding) :
    RecyclerView.ViewHolder(binding.root) {
    fun bind(workout: Workout) {
        binding.workout = workout
        binding.viewModel = viewModel
        binding.executePendingBindings()
    }
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    val layoutInflater: LayoutInflater = LayoutInflater.from(parent.context)
    val itemBinding: WorkoutlistWorkoutItemBinding =
        WorkoutlistWorkoutItemBinding.inflate(layoutInflater, parent, false)
    return ViewHolder(itemBinding)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    val item = mWorkouts[position]
    holder.bind(item)
}

fun getWorkout(position: Int): Workout {
    return mWorkouts[position]
}

override fun getFilter(): Filter {
    return workoutListFilter
}

fun removeItemFromList(id: Int) {
    val workout = mWorkouts.find { it.id == id }
    val index = mWorkouts.indexOf(workout)
    mWorkouts.removeAt(index)
}

private val workoutListFilter: Filter = object : Filter() {
    override fun performFiltering(constraint: CharSequence?): FilterResults {
        val filterList = ArrayList<Workout>()

        if (constraint!!.isEmpty() || constraint == null) {
            filterList.addAll(mWorkoutsFull)
        } else {
            val searchString = constraint.toString().toLowerCase().trim()

            for (workout in mWorkoutsFull) {
                if (workout.title.toLowerCase().contains(searchString)) {
                    filterList.add(workout)
                }
            }
        }

        val results = FilterResults()
        results.values = filterList
        return results
    }

    override fun publishResults(p0: CharSequence?, filterResult: FilterResults?) {
        mWorkouts.clear()
        mWorkouts.addAll(filterResult!!.values as ArrayList<Workout>)
        notifyDataSetChanged()
    }
}
}

And at last my viewmodel (maybe it helps) i am working with live data

class ListViewModel @Inject constructor(
    private var workoutRepository: WorkoutRepository,
    private var finishedWorkoutRepository: FinishedWorkoutRepository
) : ViewModel() {

    private val _openWorkoutDetailEvent = MutableLiveData<Event<Int>>()
    val openWorkoutDetail: LiveData<Event<Int>> = _openWorkoutDetailEvent

    private val _openCreateWorkoutEvent = MutableLiveData<Event<Int>>()
    val openCreateWorkoutEvent: LiveData<Event<Int>> = _openCreateWorkoutEvent

    fun getStandardWorkouts(): LiveData<List<Workout>> {
        return workoutRepository.getStandardWorkouts()
    }

    fun getWorkouts(): LiveData<List<Workout>> {
        return workoutRepository.getWorkouts()
    }

    fun openWorkoutDetail(workoutId: Int) {
        _openWorkoutDetailEvent.value = Event(workoutId)
    }

    fun openCreateWorkout() {
        _openCreateWorkoutEvent.value = Event(0)
    }

    fun delete(workoutId: Int) {
        workoutRepository.delete(workoutId)
    }
}

Best regards


Solution

  • You're clearing list mWorkouts list in publishResults without checking if there are some results found or not, add a check as :

    if (filterResults.values != null && filterResults.count > 0) {
        mWorkouts.clear()
        mWorkouts.addAll(filterResult!!.values as ArrayList<Workout>)
        notifyDataSetChanged()
        }