Implementation of Android fuzzy search box

Posted by Twysted on Tue, 04 Jan 2022 14:31:20 +0100

1. Preface

Recently, the fuzzy search function in the project is very difficult at first, but it's nothing to think about it carefully, because the specific fuzzy search part is the interface provided by the back end, and what we need to do is to perform the search function once when the content of the search box changes.

2. Renderings

Can see

  • The text box searches in real time as you enter
  • When there are no search results, the result display at the bottom is directly hidden
  • Maotai is not strong

3. Ideas

  • The search box part uses EditText and listens to it through the addTextChangedListener method

  • In the interface part, I use Baidu's interface to simply realize the network request through Retrofit2

  • In the fuzzy search result display part, I use Recyclerview to display the fuzzy search results

4. Realization

4.1 EditText monitoring part

Method interpretation:

The addTextChangedListener of EditText listens as follows. You can see that there are three methods

  • beforeTextChanged()

    This method is called once before EditText input

  • afterTextChanged()

    Once the EditText input is completed, the method is called.

  • onTextChanged()

    During EditText input, the method is called back continuously

etContent.addTextChangedListener(object : TextWatcher {
    
    override fun afterTextChanged(s: Editable?) {

    }
    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) 	 {

    }
    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {

    }
})

Effect realization:

The effects we want to achieve are as follows

  • When there is content in the input box, the delete button is displayed, and when there is no content, it is hidden
  • Network requests are made continuously during the input process
  • Recyclerview is displayed when there are search results, and hidden when there is no data

Specific implementation:

etContent.addTextChangedListener(object : TextWatcher {    
    ......

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { 
        if (etContent.text.isNullOrEmpty()) {
            //If the input box is empty, the delete button is hidden
            ivDel.visibility = View.GONE
            //When the input box is empty, the recyclerview is hidden to hide the previous search results
            recy.visibility = View.GONE
        } else {
			//The input box is not empty. Delete the button display
            ivDel.visibility = View.VISIBLE            
          	//Make a network request when the input box changes           	
            mainViewModel.getFuzzySearchList(Const.fuzzySearchUrl,etContent.text.toString())
        }
    }
})

4.2 network request part

Interface part:

  • Fuzzy search

    @GET request

    Parameter: key: wd value: content to search

    Address: https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su

    Using the above interface, just pass in a parameter wd, which is the content of our input box

    interface ApiService {
    @GET
    fun getFuzzySearchList(@Url url:String,@Query("wd") content:String): Call<ResponseBody>
    }
    
  • Detail interface

    Url is: http://www.baidu.com.cn/s?wd= +Text box content + & CL = 3

    Here we simply load the page through webview

Logical part:

  • After the request is completed, if the data is empty, the Recyclerview panel will be hidden, otherwise it will be displayed
  • Update the Recyclerview after the request is completed

5. Concrete realization

MainActivity

class MainActivity : AppCompatActivity() {
    
    //Create ViewModel
    private val mainViewModel: MainViewModel by lazy {
        ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory(application)).get(
            MainViewModel::class.java
        )
    }
    private val data = mutableListOf<String>()
    private val myAdapter = MyAdapter(data)

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

        //Recyclerview related configurations
        recy.layoutManager = LinearLayoutManager(this)
        recy.adapter = myAdapter

        //Jump to details page
        myAdapter.setOnMyClickListener(object :MyAdapter.MyClickListener{
            override fun onClick(position: Int) {
                val intent = Intent(this@MainActivity,NewsShowActivity::class.java)
                val url = Const.serchUrl+data[position]+"&cl=3"
                intent.putExtra("url",url)
                startActivity(intent)
                //After jump, clear the search box and hide the Recyclerview panel
                etContent.setText("")
                recy.visibility = View.GONE
                
            }

        //Fuzzy search result monitoring
        mainViewModel.getResultLiveData().observe(this, Observer {
            if(it.isEmpty()){
                //If the data is empty, hide the RecyclerView panel
                recy.visibility = View.GONE
            }else{
                recy.visibility = View.VISIBLE
            }
            //Update RecyclerView after search
            data.clear()
            data.addAll(it)
            myAdapter.notifyDataSetChanged()            
        })
            
        //EditText search box to listen   
        etContent.addTextChangedListener(object : TextWatcher {
            override fun afterTextChanged(s: Editable?) {     
            }
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {              
            }
            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                if (etContent.text.isNullOrEmpty()) {
                    ivDel.visibility = View.GONE
                    //When it is empty, if it is not hidden, the results of the previous search will be displayed below and will not be hidden
                    recy.visibility = View.GONE
                } else {
                    ivDel.visibility = View.VISIBLE                                       
                    //Make a network request
                    mainViewModel.getFuzzySearchList(Const.fuzzySearchUrl,etContent.text.toString())
                    }
                }
            })
    }
}

MainViewModel

The data returned by fuzzy search is in the following format and GBK coding, so we need to process the data after the request is successful

window.baidu.sug({q:"Olympic Games",p:false,s:["Olympic Games","Olympic women's 200m butterfly final","Olympic medal list","Olympic schedule","Olympic gold medal list 2021","Olympic table tennis","Live broadcast of the Olympic Games","How many years are the Olympic Games held","Where do you watch the Olympic Games","Olympic referee"]});
class MainViewModel :ViewModel(){
    
    private val httpClient = OkHttpClient.Builder()
    	.connectTimeout(5, TimeUnit.SECONDS)
    	.readTimeout(5, TimeUnit.SECONDS)
    	.writeTimeout(5, TimeUnit.SECONDS)
    	.build()
    private val retrofit: Retrofit =Retrofit.Builder()
    	.baseUrl("http://a")
    	.client(httpClient)
    	.addConverterFactory(GsonConverterFactory.create())
    	.build()
    private val api = retrofit.create(ApiService::class.java)
    
    
     /**
     * Fuzzy search
     */
    fun getFuzzySearchList(url:String,content:String){
        api.getFuzzySearchList(url,content).enqueue(object : retrofit2.Callback<ResponseBody> {
            override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
                errorLiveData.value = t.message
            }

            override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
                if(response?.body() != null){
                    //Because the result of fuzzy search is GBK code, it needs to be processed here
                    val charset = response.body()!!.contentType()!!.charset()!!
                    val source = response?.body()?.source()
                    source?.let {                       
                        source.skip(ByteString.decodeHex("efbbbf").size().toLong())
                    }
                    
                    val string = response?.body()?.string()
                    string?.let {
                        val startIndex = it.indexOf("(")
                        val endIndex =  it.length-2
                        //intercept
                        val json = it.substring(startIndex+1, endIndex)
                        val jsonObject = gson.fromJson<ResultBean>(json, ResultBean::class.java)
                        //Update data
                        resultLiveData.value = jsonObject.s
                    }
                }
            }
        })
    }        
}

6. Summary

The above is the whole content. In fact, it's very simple. We actually listen to EditText and process the results of network requests. The logic of the rest is when to display and hide.

If this article is helpful to you, please don't forget Sanlian. If there are inappropriate places, please put forward them. See the next article.

7. Reference articles

Baidu fuzzy query_ Sanfanyu CSDN blog

Call Baidu search interface to query_ Great mind 829 column - CSDN blog

Android retro fit requests to return String data. Chinese garbled code solution

Topics: Android