Search code examples
androidkotlinandroid-recyclerviewsearchview

Android SearchView - No errors however unable to filter recyclerView


I am trying to filter a RecyclerView using a SearchView in the toolbar. I'm not hitting any errors and the table and search bar seem to display properly. However, the table does not filter when I type in the search bar.

Initially I thought I'm probably not notifying that the dataset has changed but this method call is present in publishResults.

Is it perhaps due to the dataset not being a global variable? I thought it best to avoid global variables here...

//Models
package com.example.imperialvisualisationsandroid

class DataModel(val Visualisations: List<Visualisation>)

class Visualisation(val id: Int, val name: String, val info: String, val url_name: String, val tags: String, val imageURL: String, val gifURL: String)



//MainActivity

package com.example.imperialvisualisationsandroid

import android.app.SearchManager
import android.content.Context
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.provider.ContactsContract
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.SearchView
import android.view.Menu
import android.view.MenuItem
import com.google.gson.GsonBuilder
import com.klinker.android.peekview.PeekViewActivity
import kotlinx.android.synthetic.main.activity_main.*
import okhttp3.*
import java.io.IOException

public class MainActivity : AppCompatActivity() {

    // data variable - changed only once during data decode
    var data: DataModel = DataModel(Visualisations = emptyList())

    private var searchView: SearchView? = null


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)

        recyclerView_main.layoutManager = LinearLayoutManager(this)

        decodeJSON()

    }

    //TABLE SETUP



    fun decodeJSON() {

        //var data: DataModel?

        val jsonURL = "__insert url here__"

        val request = Request.Builder().url(jsonURL).build()

        val client = OkHttpClient()

        client.newCall(request).enqueue(object: Callback {

            override fun onResponse(call: Call, response: Response) {
                val jsonBody = response?.body?.string()
                //println(jsonBody)

                val gson = GsonBuilder().create()

                //class.java??
                data = gson.fromJson(jsonBody, DataModel::class.java)
                println("JSON FETCH SUCCESSFUL")

                //cannot return data here as return needs to be unit (cannot specify model to DataModel)
                //Have to update UI on UI thread
                runOnUiThread {
                    recyclerView_main.adapter = MainAdapter(data)
                }

            }

            override fun onFailure(call: Call, e: IOException) {
                println("JSON FETCH FAILED")
                println(e)
            }
        })


    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.options_menu, menu)


        val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
        // ???
        searchView = menu?.findItem(R.id.search)?.actionView as SearchView

        searchView!!.setSearchableInfo(searchManager.getSearchableInfo(componentName))

        searchView!!.maxWidth = Integer.MAX_VALUE

        searchView!!.setOnQueryTextListener(object: SearchView.OnQueryTextListener {

            override fun onQueryTextSubmit(query: String?): Boolean {
                MainAdapter(data).filter.filter(query)
                return false
            }

            override fun onQueryTextChange(newText: String?): Boolean {
                MainAdapter(data).filter.filter(newText)
                return false
            }
        })

        return true

    }

    override fun onOptionsItemSelected(item: MenuItem?): Boolean {

        val id = item?.itemId
        return if (id == R.id.search){
            true
        } else super.onOptionsItemSelected(item)
    }

}



//MainAdapter

package com.example.imperialvisualisationsandroid

import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Filter
import android.widget.Filterable
import com.bumptech.glide.Glide
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.visualisation_cell.view.*

class MainAdapter(val data: DataModel) : RecyclerView.Adapter<ViewHolder>(), Filterable {

    val visualisations = data.Visualisations

    var searchVisualisations = data.Visualisations.toMutableList()

    override fun getItemCount(): Int {
        return searchVisualisations.count()
    }

    override fun onCreateViewHolder(p0: ViewGroup, p1: Int): ViewHolder {
        val layoutInflater = LayoutInflater.from(p0.context)
        val cellForRow = layoutInflater.inflate(R.layout.visualisation_cell, p0, false)
        return ViewHolder(cellForRow)
    }


    override fun onBindViewHolder(p0: ViewHolder, p1: Int) {

    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
        super.onBindViewHolder(holder, position, payloads)

        holder.itemView.setBackgroundColor(Color.WHITE)

        val selectedVisualisation = searchVisualisations.get(position)

        holder.view.titleTextView.text = searchVisualisations[position].name
        holder.view.infoTextView.text = searchVisualisations[position].info

        //PICASSO
        //Picasso.get().load(visualisations[position].imageURL).into(holder.view.VisualisationImageView)


        //GLIDE
        val context = holder.view.context
        Glide.with(context)
            .load(searchVisualisations[position].imageURL)
            .into(holder.view.VisualisationImageView)

        holder.selectedVisualisation = selectedVisualisation

    }


    override fun getFilter(): Filter {
        return object: Filter() {

            override fun performFiltering(p0: CharSequence?): FilterResults {
                val searchQuery = p0.toString().toLowerCase()

                var filteredVisualisations = ArrayList<Visualisation>()

                if (searchQuery.isEmpty()) {
                    filteredVisualisations.addAll(visualisations)
                } else {
                    for (vis in visualisations) {

                        var visString = (vis.name + " " + vis.info + " " + vis.tags).toLowerCase()

                        if (visString.contains(searchQuery)) {
                            filteredVisualisations.add(vis)
                        }
                    }

//                    searchVisualisations.clear()
//                    searchVisualisations.addAll(filteredVisualisations)

                }

                val filterResults = FilterResults()
                filterResults.count = filteredVisualisations.size
                filterResults.values = filteredVisualisations
                return filterResults
            }

            override fun publishResults(p0: CharSequence?, p1: FilterResults?) {
                searchVisualisations = p1?.values as ArrayList<Visualisation>
                notifyDataSetChanged()
            }
        }
    }
}

class ViewHolder(val view: View, var selectedVisualisation: Visualisation? = null) : RecyclerView.ViewHolder(view) {

    companion object {
        val SelectedVisualisationTitle = "SelectedVisualisationTitle"
        val SelectedVisualisationWebURL = "SelectedVisualisationWebURL"
    }

    init {
        view.setOnClickListener {
            val intent = Intent(view.context, VisualisationDetailActivity::class.java)

            intent.putExtra(SelectedVisualisationTitle, selectedVisualisation?.name)
            intent.putExtra(SelectedVisualisationWebURL, selectedVisualisation?.url_name)

            view.context.startActivity(intent)
        }

//        view.setOnLongClickListener {
////            val intent = Intent(view.context, GIFDetailActivity::class.java)
////            view.context.startActivity(intent)
////        }
    }

}

//Manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.imperialvisualisationsandroid">

    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <meta-data android:name="android.app.searchable"
                       android:resource="@xml/searchable">

            </meta-data>

        </activity>

        <activity android:name=".VisualisationDetailActivity"
                  android:theme="@style/AppTheme.DetailTheme"
            android:label="Visualisation Detail"
                  android:launchMode="singleTop">

            <!--Back button support-->

            <meta-data android:name="android.support.PARENT_ACTIVITY"
                       android:value=".MainActivity">
            </meta-data>
        </activity>
    </application>

</manifest>


//Options Menu

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      xmlns:tools="http://schemas.android.com/tools"
      tools:context=".MainActivity">
    <item android:id="@+id/search"
          android:title="@string/search_title"
          android:icon="@drawable/ic_search_api_holo_dark"
          app:showAsAction="collapseActionView|ifRoom"
          app:actionViewClass="android.support.v7.widget.SearchView"
    />
</menu>


//searchable.xml

<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
            android:label="@string/app_name"
            android:hint="@string/search_hint"
            android:voiceSearchMode="showVoiceSearchButton|launchRecognizer"/>

Any help would be appreciated


Solution

  • Inside your search listener

    MainAdapter(data).filter.filter(query)
    

    you are creating new instance of adapter which is doing the filter. so the adapter object attached to RecyclerView is different than one having the filtered result.

    You should do something like this instead

    (recyclerView_main.adapter  as Filterable).filter.filter(query)