I'm trying to filter a recyclerList that contains around 3000 items.
My filter kinda works but for some reason it doesn't update the list until I scroll far enough.
For example: the top 2 elements start with the letter A --> if my filter starts with B, the top 2 elements still get shown until I scroll far enough so that they are no longer visible. When I scroll back up, they disappeared from the view.
Adapter
class LocationAdapter(
private var locations: ArrayList<Costcenter>,
private val onLocationClick: (location: Costcenter) -> Unit
) : RecyclerView.Adapter<LocationAdapter.LocationViewHolder>(), Filterable {
var locationsFiltered = locations
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LocationViewHolder {
val view = LayoutInflater
.from(parent.context)
.inflate(R.layout.location_row, parent, false)
return LocationViewHolder(view)
}
override fun getItemCount(): Int = locationsFiltered.size
override fun onBindViewHolder(holder: LocationViewHolder, position: Int) {
holder.bind(locationsFiltered[position])
}
inner class LocationViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val title: TextView = itemView.findViewById(R.id.txtCostCenter)
private val desc: TextView = itemView.findViewById(R.id.txtCostCenterDescription)
fun bind(loc: Costcenter) {
title.text = loc.goederenontvanger
desc.text = loc.goederenontvanger_omschrijving
itemView.setOnClickListener {
onLocationClick.invoke(loc)
}
}
}
override fun getFilter(): Filter {
return object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
val charSearch = constraint.toString()
locationsFiltered = if (charSearch.isEmpty()) {
locations
} else {
val resultList = ArrayList<Costcenter>()
for (row in locations) {
if (row.goederenontvanger.toLowerCase()
.contains(charSearch.toLowerCase()) || row.goederenontvanger_omschrijving.toLowerCase()
.contains(charSearch.toLowerCase())
) {
resultList.add(row)
}
}
resultList
}
val filterResults = FilterResults()
filterResults.values = locationsFiltered
return filterResults
}
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
}
}
}
}
Fragment where the recyclerview gets created
class LocationsFragment : Fragment() {
private lateinit var locationAdapter: LocationAdapter
private val storageViewModel by activityViewModels<StorageViewModel>()
private lateinit var currentView : View
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
var view = inflater.inflate(R.layout.fragment_locations, container, false)
var recycler = view.findViewById<RecyclerView>(R.id.recyclerLocations)
var filter = view.findViewById<SearchView>(R.id.editFilter)
locationAdapter = LocationAdapter(Utilities.costcenters) { loc -> setSelectedLocation(loc) }
val recycleMngr: RecyclerView.LayoutManager =
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
filter.setOnQueryTextListener(object: SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
locationAdapter.filter.filter(newText)
return false
}
})
recycler.layoutManager = recycleMngr
recycler.adapter = locationAdapter
currentView = view
return view
}
You need to update the original list locationsFiltered with the filtered results when the filter is published with publishResults() and then notifyDataSetChanged() to apply the changes on the RecyclerView
So add the below in publishResults() method:
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
locationsFiltered = filterResults.values as ArrayList<Costcenter>
notifyDataSetChanged()
}
Related
I am trying to filter my recyclerview using filterable, it works fine on static data, but on dynamic data from firestore
First, I got the data from Firestore in arrayList
Then, I filter those arrayList to new arrayList
But when any change happended in Firestore, the original arrayList will be updated and my recyclerview will display this data instead the data I currently filltered on searchview
What I wanted to do is my recyclerview to aslway display filtered data when I type any word on searchview whether they are any updated data or not
added screenshot
here is my adapter
class SearchAdapter() : RecyclerView.Adapter<SearchAdapter.ListViewHolder>(), Filterable {
private var listSearch = ArrayList<Dosen>()
private var listSearchFull = ArrayList<Dosen>()
fun setData(list: ArrayList<Dosen>){
this.listSearch = list
listSearchFull = ArrayList(listSearch)
}
inner class ListViewHolder(itemView: UserListBinding) : RecyclerView.ViewHolder(itemView.root) {
private val binding = itemView
fun bind(dosen: Dosen) {
with(binding){
val db = Firebase.firestore
val collection = db.collection("alat")
.whereEqualTo("id", dosen.alat_id)
collection.get()
.addOnSuccessListener { document ->
try {
val location = document.toObjects(Alat::class.java)[0].lokasi
tvLocation.text = location
} catch (e: Exception){
Log.d("rv", "system error $e")
}
}
.addOnFailureListener { exception ->
Log.d("rv", "get failed with ", exception)
}
tvUsername.text = dosen.nama
val simpleDateFormat = SimpleDateFormat("EEEE, dd LLLL yyyy")
val date = simpleDateFormat.format(dosen.datetime!!.toDate())
Log.d("rvTime", date)
tvDate.text = date
val simpleTimeFormat = SimpleDateFormat("KK:mm:ss aaa")
val time = simpleTimeFormat.format(dosen.datetime.toDate())
Log.d("rvTime", time)
tvTime.text = time
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder {
return ListViewHolder(UserListBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
holder.bind(listSearch[position])
}
override fun getItemCount(): Int = listSearch.size
override fun getFilter(): Filter {
return object : Filter(){
override fun performFiltering(query: CharSequence?): FilterResults {
val filteredList: ArrayList<Dosen> = ArrayList()
if (query == null || query.length == 0){
filteredList.addAll(listSearchFull)
} else{
val searchQuery = query.toString().toLowerCase().trim()
for (item in listSearchFull) {
if (item.nama!!.lowercase(Locale.ROOT).contains(searchQuery)){
filteredList.add(item)
}
}
}
val filterResults = FilterResults()
filterResults.values = filteredList
return filterResults
}
override fun publishResults(query: CharSequence?, filteredResult: FilterResults?) {
//error
listSearch.clear()
listSearch.addAll(filteredResult!!.values as ArrayList<Dosen>)
notifyDataSetChanged()
}
}
}
}
and here is my fragment
class SearchFragment : Fragment() {
private var _binding: FragmentSearchBinding? = null
private val binding get() = _binding!!
private lateinit var searchAdapter: SearchAdapter
private lateinit var searchArrayList : ArrayList<Dosen>
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
_binding = FragmentSearchBinding.inflate(inflater, container, false)
val root: View = binding.root
searchArrayList = arrayListOf()
#Suppress("DEPRECATION")
setHasOptionsMenu(true)
eventChangeListener()
return root
}
private fun eventChangeListener() {
val db = Firebase.firestore
db.collection("presensi")
.orderBy("datetime", Query.Direction.DESCENDING)
.addSnapshotListener(object : EventListener<QuerySnapshot>{
override fun onEvent(value: QuerySnapshot?, error: FirebaseFirestoreException?) {
searchArrayList.clear()
for (document in value!!){
searchArrayList.add(document.toObject(Dosen::class.java))
searchAdapter.setData(searchArrayList)
Log.d("arraySearch", document.toObject<Dosen>().toString())
Log.d("arraySearchList", searchArrayList.toString())
}
searchAdapter.notifyDataSetChanged()
}
})
searchAdapter = SearchAdapter()
with(binding){
tvNoData.visibility = View.GONE
rvSearch.layoutManager = LinearLayoutManager(activity)
rvSearch.setHasFixedSize(true)
rvSearch.adapter = searchAdapter
}
}
/// prob need fix
#Deprecated("Deprecated in Java")
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
#Suppress("DEPRECATION")
super.onCreateOptionsMenu(menu, inflater)
menu.clear()
inflater.inflate(R.menu.main_menu, menu)
val searchView = context?.let { SearchView(it) }
menu.findItem(R.id.menu_search).apply {
setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW or MenuItem.SHOW_AS_ACTION_IF_ROOM)
actionView = searchView
}
searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
return false
}
override fun onQueryTextChange(newText: String): Boolean {
searchAdapter.filter.filter(newText)
return false
}
})
}
override fun onStart() {
super.onStart()
Log.d("firebaseFirestoreListener", "onStart")
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
Log.d("firebaseFirestoreListener", "onDestroyView")
}
}
Is it actually possible to do what I want?
or should I use 3rd party library like algolia, but it's not free.
I hope you guys understand my question
First of all, I am Spanish so my english is not very good.
I have a list of items on a Recyclerview, and I also have a SearchView to filter those items.
Every item has a favourite button, so when you click, the item adds to favorite table.
The problem is that, when I filter something and I start clicking those buttons, odd things happens: some items dissapear from the filtered list. It doesn't happen always, only sometimes. How can I fix this?
My class:
class CoasterFragment : Fragment() {
private val myAdapter by lazy { CoasterRecyclerViewAdapter(CoasterListenerImpl(requireContext(), viewModel),requireContext()) }
private lateinit var searchView: SearchView
private var _binding: FragmentCoasterBinding? = null
private val binding get() = _binding!!
private val viewModel: CoastersViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentCoasterBinding.inflate(inflater, container, false)
val root: View = binding.root
val recyclerView = binding.recyclerCoaster
recyclerView.adapter = myAdapter
recyclerView.layoutManager = LinearLayoutManager(requireContext())
viewModel.coasters().observe(viewLifecycleOwner){myAdapter.setData(it)}
searchView = binding.search
searchView.clearFocus()
searchView.setOnQueryTextListener(object: SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
if(query != null){
searchDatabase(query)
searchView.clearFocus()
}
return true
}
override fun onQueryTextChange(query: String?): Boolean {
if(query != null){
searchDatabase(query)
}
return true
}
})
return root
}
fun searchDatabase(query: String) {
val searchQuery = "%$query%"
viewModel.searchDatabase(searchQuery).observe(viewLifecycleOwner) { myAdapter.setData(it)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
My adapter:
class CoasterRecyclerViewAdapter( val listener: CoasterListener,
val context: Context ) : RecyclerView.Adapter<CoasterRecyclerViewAdapter.ViewHolder>(){
private var coasterList = emptyList<CoasterFavorito>()
class ViewHolder private constructor(val binding: CoasterItemBinding, private val listener: CoasterListener,
private val context: Context): RecyclerView.ViewHolder(binding.root){
companion object{
fun crearViewHolder(parent: ViewGroup, listener: CoasterListener, context: Context):ViewHolder{
val layoutInflater = LayoutInflater.from(parent.context)
val binding = CoasterItemBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding, listener, context )
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder.crearViewHolder(parent, listener, context)
override fun onBindViewHolder(holder: ViewHolder, position: Int){
holder.binding.nombre.text = coasterList[position].coaster.nombre
holder.binding.parque.text = coasterList[position].coaster.parque
holder.binding.ciudad.text = coasterList[position].coaster.ciudad
holder.binding.provincia.text = coasterList[position].coaster.provincia
holder.binding.comunidad.text = coasterList[position].coaster.comunidadAutonoma
Glide
.with(context)
.load(coasterList[position].coaster.imagen)
.centerCrop()
.into(holder.binding.imagen)
holder.binding.check.isChecked = coasterList[position].favorito
holder.binding.check.setOnClickListener{
if (coasterList[position].favorito) {
listener.delFavorito(coasterList[position].coaster.id)
holder.binding.check.isChecked = false
} else {
listener.addFavorito(coasterList[position].coaster.id)
holder.binding.check.isChecked = true
}
}
}
override fun getItemCount(): Int{
return coasterList.size
}
fun setData(coaster: List<CoasterFavorito>){
coasterList = coaster
notifyDataSetChanged()
}
}
interface CoasterListener {
fun addFavorito(id: Long)
fun delFavorito(id: Long)
}
I tried changing the focus, changing the notifydatasetchanged with notifyitemchanged, and nothing happens...
I'm trying to refactor my app to use ViewBinding. I've gone through all the fragments and activities; however, I have an ArrayAdapter that I'm unsure of the proper convention to use view binding to prevent memory leaks.
What is the proper way to use a viewbinding in an ArrayAdapter?
I have been using this method for fragments:
private var _binding: BINDING_FILE_NAME? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = BINDING_FILE_NAME.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
I call my adapter like so:
var myadapter : MyCustomAdapter = MyCustomAdapter(requireContext(), R.layout.row_autocomplete_item, myListOfStrings())
MyCustomAdapter class
class MyCustomAdapter(ctx: Context, private val layout: Int, private val allItems: List<String>) : ArrayAdapter<String>(ctx, layout, allItems) {
var filteredItems: List<String> = listOf()
override fun getCount(): Int = filteredItems.size
override fun getItem(position: Int): String = filteredItems[position]
#SuppressLint("SetTextI18n")
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = convertView ?: LayoutInflater.from(parent.context).inflate(layout, parent, false)
val item = filteredItems[position]
view.apply {
// HERE IS WHERE I AM NEEDING TO BIND THE VIEW
tvName?.text = item
}
return view
}
override fun getFilter(): Filter {
return object : Filter() {
override fun publishResults(charSequence: CharSequence?, filterResults: FilterResults) {
#Suppress("UNCHECKED_CAST")
filteredItems = filterResults.values as List<String>
notifyDataSetChanged()
}
override fun performFiltering(charSequence: CharSequence?): FilterResults {
val queryString = charSequence?.toString()?.lowercase(Locale.ROOT)
val results = FilterResults()
results.values = if (queryString == null || queryString.isEmpty())
allItems
else
allItems.filter {
it.lowercase(Locale.ROOT).contains(queryString)
}
return results
}
}
}
}
I did like this, its working. But Im not sure, whether it is correct way or not
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val binding: LayoutCustomSpinnerBinding
var row = convertView
if (row == null) {
val inflater =
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
binding = LayoutCustomSpinnerBinding.inflate(inflater, parent, false)
row = binding.root
} else {
binding = LayoutCustomSpinnerBinding.bind(row)
}
binding.txtContent.text = spinnerList[position].ValueData
return row
}
Based on this answer, got this:
If convertView is not null, then bind to it. Inflate otherwise.
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val binding: MyLayoutBinding =
if (convertView != null) MyLayoutBinding.bind(convertView)
else MyLayoutBinding.inflate(LayoutInflater.from(context), parent, false)
// use binding
val item = getItem(position)
binding.text = item.name
return binding.root
}
class HoursAdapter(private val hoursList: List<HoursItem>)
:RecyclerView.Adapter<HoursAdapter.HoursViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
HoursViewHolder {
val binding = HoursListItemsBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return HoursViewHolder(binding)
}
override fun getItemCount() = hoursList.size
override fun onBindViewHolder(holder: HoursViewHolder, position: Int) {
with(holder){
with(hoursList[position]) {
binding.topLearnerName.text = name
val hours = "$hours learning hours, $country"
binding.topLearnerTime.text = hours
GlideApp.with(holder.itemView.context)
.load(badgeUrl)
.into(binding.topLearnerImage)
holder.itemView.setOnClickListener {
Toast.makeText(holder.itemView.context, hours,
Toast.LENGTH_SHORT).show()
}
}
}
}
inner class HoursViewHolder(val binding: HoursListItemsBinding)
:RecyclerView.ViewHolder(binding.root)
}
I would use a SearchView to filter my RecyclerView, on stackoverflow and other sites i've found just examples of using Filterable with Java and with RecyclerView.Adapter while i'm using ListAdapter..
So i was trying to make the custom filter by my self but when i try to filter the adapter i just get a null on my MutableList in publishResults.
My Adapter code looks like this:
class ArticoliListAdapter : ListAdapter<Articolo, ArticoliListAdapter.ArticoliViewHolder>(ArticoliComparator()), Filterable {
private val list = mutableListOf<Articolo>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticoliViewHolder {
return ArticoliViewHolder.create(parent)
}
override fun onBindViewHolder(holder: ArticoliViewHolder, position: Int) {
val current = getItem(position)
holder.bind(current)
}
override fun getItemId(position: Int): Long {
val articolo = currentList[position]
return articolo.barcode.hashCode().toLong()
}
class ArticoliViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val barcode: TextView = itemView.findViewById(R.id.barcode)
private val qta: TextView = itemView.findViewById(R.id.qta)
private val desc: TextView = itemView.findViewById(R.id.desc)
private val um: TextView = itemView.findViewById(R.id.um)
fun bind(articolo: Articolo?) {
barcode.text = articolo?.barcode
qta.text = articolo?.qta?.formatForQta()
um.text = articolo?.um?.toLowerCase(Locale.ITALIAN)
desc.text = if(articolo?.desc.isNullOrEmpty()) "-" else articolo?.desc
}
private fun Float.formatForQta(): String {
val floatString = this.toString()
val decimalString: String = floatString.substring(floatString.indexOf('.') + 1, floatString.length)
return when (decimalString.toInt() == 0) {
true -> this.toInt().toString()
false -> "%.3f".format(this)
}
}
companion object {
fun create(parent: ViewGroup): ArticoliViewHolder {
val view: View = LayoutInflater.from(parent.context)
.inflate(R.layout.item_layout, parent, false)
return ArticoliViewHolder(view)
}
}
}
class ArticoliComparator : DiffUtil.ItemCallback<Articolo>() {
override fun areItemsTheSame(oldItem: Articolo, newItem: Articolo): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: Articolo, newItem: Articolo): Boolean {
return oldItem.qta == newItem.qta
}
}
override fun getFilter(): Filter {
return customFilter
}
private val customFilter = object: Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
val filteredList = mutableListOf<Articolo>()
if (constraint == null || constraint.isEmpty()){
filteredList.addAll(currentList)
}else {
val filterPattern = constraint.toString().toLowerCase(Locale.ITALIAN).trim { it <= ' ' }
for (item in currentList) {
if (item.barcode.toLowerCase(Locale.ITALIAN).contains(filterPattern) || item.desc?.toLowerCase(
Locale.ITALIAN
)!!.contains(filterPattern)) {
filteredList.add(item)
}
}
}
val results = FilterResults()
results.values = filteredList
return results
}
override fun publishResults(constraint: CharSequence?, filterResults: FilterResults?) {
list.clear()
list.addAll(filterResults?.values as MutableList<Articolo>)
notifyDataSetChanged()
}
}
}
So i was wondering which would be the right way to built a custom filter to filter my data in the recyclerView by using ListAdapter in Kotlin.
I'm calling the filter in my fragment like this:
override fun onQueryTextChange(query: String?): Boolean {
adapter.filter.filter(query)
return false
}
But when i try to filter nothing happend and still all items are shown...
Data to the RecyclerView adapter is set from my ViewHolder and the data is get from the DataBase (LiveData<List<Articolo>>)
Here is the code from my Fragment:
articoliViewModel.articoli.observe(viewLifecycleOwner) { articoli ->
articoli.let { adapter.submitList(it) }
}
Few flaws in your code which i am listing down below.
currentList is holding the current items which r on list not the complete list of items . i.e if you have 10 items and after filter u get 3 items then currentList will be holding 3 items not 10 . So you can not use currentList for filtering the list . instead u hold on to the CompleteList and apply filter on this one .
you should not be calling notifyDataSetChanged() this just defeats the whole purpose of having DiffUtils, instead you call #submitList
Al thought you have a reference to complete list as global variable but you have never assigned value to it its always empty.
I have made a working sample to illustrate. pls try same with your code adding the essential code below. I have use type as String just to make sample easy to understand you can use your custom object. You can also modify the code to make it look better but i think its enough to get the idea how ListAdapter works.
class ArticoliListAdapter : ListAdapter<String, ArticoliListAdapter.ArticoliViewHolder>(ArticoliComparator()), Filterable {
private var list = mutableListOf<String>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticoliViewHolder {
return ArticoliViewHolder.create(parent)
}
override fun onBindViewHolder(holder: ArticoliViewHolder, position: Int) {
val current = getItem(position)
holder.bind(current)
}
fun setData(list: MutableList<String>?){
this.list = list!!
submitList(list)
}
class ArticoliViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val desc: TextView = itemView.findViewById(R.id.txtName)
fun bind(name: String) {
desc.text = name.toUpperCase()
}
companion object {
fun create(parent: ViewGroup): ArticoliViewHolder {
val view: View = LayoutInflater.from(parent.context)
.inflate(R.layout.item_list, parent, false)
return ArticoliViewHolder(view)
}
}
}
class ArticoliComparator : DiffUtil.ItemCallback<String>() {
override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
return oldItem == newItem
}
}
override fun getFilter(): Filter {
return customFilter
}
private val customFilter = object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
val filteredList = mutableListOf<String>()
if (constraint == null || constraint.isEmpty()) {
filteredList.addAll(list)
} else {
for (item in list) {
if (item.toLowerCase().startsWith(constraint.toString().toLowerCase())) {
filteredList.add(item)
}
}
}
val results = FilterResults()
results.values = filteredList
return results
}
override fun publishResults(constraint: CharSequence?, filterResults: FilterResults?) {
submitList(filterResults?.values as MutableList<String>)
}
}
}
When you set data to adapter you call setData not submitList.
articoliViewModel.articoli.observe(viewLifecycleOwner) { articoli ->
articoli.let { adapter.setData(it) }
}
Correct my if I'm wrong, but I would say that there's a mistake here:
override fun publishResults(constraint: CharSequence?, filterResults: FilterResults?) {
list.clear()
list.addAll(filterResults?.values as MutableList<Articolo>)
notifyDataSetChanged()
}
I would do the following:
override fun publishResults(constraint: CharSequence?, filterResults: FilterResults?) {
list.clear()
list.addAll(filterResults?.values as MutableList<Articolo>)
submitList(list)
}
I'm trying to implement search option in recyclerview.
What I have implemented so far is:
created a search bar as below:
I've applied onCreateOptions as per below in MainActivity:
class RecyclerListActivity: AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var viewModel: PostListViewModel
private var errorSnackbar: Snackbar? = null
private var searchView: SearchView? = null
override fun onCreate(savedInstanceState: Bundle?){
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.postList.layoutManager = GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, false)
viewModel = ViewModelProviders.of(this).get(PostListViewModel::class.java)
viewModel.errorMessage.observe(this, Observer {
errorMessage -> if(errorMessage != null) showError(errorMessage) else hideError()
})
binding.viewModel = viewModel
}
private fun showError(#StringRes errorMessage:Int){
errorSnackbar = Snackbar.make(binding.root, errorMessage, Snackbar.LENGTH_INDEFINITE)
errorSnackbar?.setAction(R.string.retry, viewModel.errorClickListener)
errorSnackbar?.show()
}
private fun hideError(){
errorSnackbar?.dismiss()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
searchView = menu.findItem(R.id.action_search).actionView as SearchView
searchView!!.maxWidth = Int.MAX_VALUE
searchView!!.imeOptions = EditorInfo.IME_ACTION_DONE
// listening to search query text change
searchView!!.setOnQueryTextListener(object :
SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
return false
}
override fun onQueryTextChange(query: String): Boolean {
return false
}
})
return true
}
}
My Adapter class as below:
class PostListAdapter: RecyclerView.Adapter<PostListAdapter.ViewHolder>(), Filterable {
private lateinit var postList:List<Data>
private lateinit var postListFull:List<Data>
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ListItemBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.context), R.layout.list_item, parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(postList[position])
}
override fun getItemCount(): Int {
return if(::postList.isInitialized) postList.size else 0
}
fun updatePostList(postList:List<Data>){
this.postList = postList
notifyDataSetChanged()
}
class ViewHolder(private val binding: ListItemBinding): RecyclerView.ViewHolder(binding.root){
private val viewModel = MoviesViewModel()
fun bind(post:Data){
viewModel.bind(post)
binding.viewModel = viewModel
}
init {
binding.root.setOnClickListener {
//Toast.makeText(binding.root.context, binding.postTitle.text, Toast.LENGTH_SHORT).show()
val intent = Intent(binding.root.context, DetailsActivity::class.java)
//intent.putExtra(REPO_NAME, binding.postTitle.text)
binding.root.context.startActivity(intent)
}
}
}
override fun getFilter(): Filter? {
return searchFilter
}
private val searchFilter: Filter = object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults? {
val filteredList: MutableList<Data> = ArrayList()
if (constraint == null || constraint.isEmpty()) {
filteredList.addAll(postListFull)
} else {
val filterPattern =
constraint.toString().toLowerCase().trim()
for (item in postListFull) {
if (item.title.toLowerCase().contains(filterPattern) || item.genre.toLowerCase().contains(filterPattern)) {
filteredList.add(item)
}
}
}
val results = FilterResults()
results.values = filteredList
return results
}
override fun publishResults(
constraint: CharSequence?,
results: FilterResults
) {
//postList.clear()
//postList.addAll(results.values as List<*>)
notifyDataSetChanged()
}
}
I have two issues here to resolve it. first one is: getting unresolved reference in these lines (Adapter class). How could I resolve it?
postList.clear()
postList.addAll(results.values as List<*>)
second one is: how to apply the filter results in adapter as I've used dagger & databinding? I've used following tutorial to create recyclerview: https://proandroiddev.com/mvvm-with-kotlin-android-architecture-components-dagger-2-retrofit-and-rxandroid-1a4ebb38c699
private lateinit var postListFull: ArrayList<Data>= ArrayList()
in the publishResults() method, to store the result in list :
postListFull= results!!.values as ArrayList<Data>
notifyDataSetChanged()
for your second issue, where do you want to apply the filter results and why?