Search bar cannot retrieve results after entering the input

25 views Asked by At

I wanted to include a search bar for finding movies by name, but in the way I failed after trying 2 kinds of search bar. First I made the one on the app bar. After that one failed, I made another one under the app bar in frame layout. In both, when I typed the text inside them then pressed Enter button or clicked the search icon, nothing happened. The movie list didn't change. There's only the loading progress indicator.

Here's MovieListActivity.kt the activity where code blocks for those two search bars belong:

class MovieListActivity : AppCompatActivity() {
    private var _binding: ActivityMovieListBinding? = null
    private var adapter: MovieListAdapter? = null
    private var layoutManager : LayoutManager? = null
    private lateinit var viewModel: MovieListViewModel

    private val binding get() = _binding!!


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        _binding = ActivityMovieListBinding.inflate(layoutInflater)
        setContentView(binding.root)

        viewModel = ViewModelProvider(this)[MovieListViewModel::class.java]
        viewModel.getMovieList()

        showMovieList()
        showMovieGenres()
        setupRecyclerView()


        adapter?.clickListener(object : MovieOnClickListener {
            override fun onClick(movie: Movie, genres: String) {
                val intent = Intent(
                    this@MovieListActivity,
                    MovieDetailActivity::class.java)
                intent.putExtra(MovieDetailActivity.MOVIE, movie)
                intent.putExtra(MovieDetailActivity.GENRES, genres)
                startActivity(intent)
            }
        })
    }

    /**making the menu*/
    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        val inflater = menuInflater
        inflater.inflate(R.menu.menu_main, menu)
        return true
    }

    /**switching between pages*/
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when(item.itemId){
            R.id.action_search -> {
                val searchManager = getSystemService(SEARCH_SERVICE) as SearchManager
                val searchItem : MenuItem = item
                val searchView = searchItem.actionView as SearchView

                searchView.setSearchableInfo(searchManager.getSearchableInfo(componentName))
                searchView.queryHint = resources.getString(R.string.search_hint)

                searchItem.setOnActionExpandListener(object: MenuItem.OnActionExpandListener {
                    override fun onMenuItemActionExpand(p0: MenuItem): Boolean { return true }
                    override fun onMenuItemActionCollapse(p0: MenuItem): Boolean {
                        viewModel.getMovieList()
                        return true
                    }
                })

                searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
                    override fun onQueryTextSubmit(query: String?): Boolean {
                        viewModel.getMovieByNameList(query.toString())
                        searchView.clearFocus()
                        return true
                    }
                    override fun onQueryTextChange(newText: String?): Boolean { return false }
                })
                return true
            }
            R.id.action_switch_list -> {
                val intent = Intent(
                    this@MovieListActivity,
                    GenreListActivity::class.java
                )
                startActivity(intent)
                return true
            }
            else -> return true
        }
    }


    override fun onDestroy() {
        super.onDestroy()
        _binding = null
    }

    private fun showMovieList(){
        viewModel.response.observe(this){
            if (it != null){
                when(it){
                    is RequestState.Loading -> showLoading()
                    is RequestState.Success -> {
                        hideLoading()
                        it.data?.results?.let {
                                data -> adapter?.differ?.submitList(data.toList())
                        }
                    }
                    is RequestState.Error -> {
                        hideLoading()
                        Toast.makeText(this,it.message, Toast.LENGTH_SHORT).show()
                    }
                }
            }
        }
    }

    private fun showMovieGenres(){
        viewModel.getMovieGenres().observe(this){
            if (it != null){
                when(it){
                    is RequestState.Loading -> {}
                    is RequestState.Success -> it.data.genres?.let { data -> adapter?.setGenres(data) }
                    is RequestState.Error -> Toast.makeText(this,it.message, Toast.LENGTH_SHORT).show()
                }
            }
        }
    }

    private fun setupRecyclerView(){
        adapter = MovieListAdapter()
        layoutManager = GridLayoutManager(this, 2)
        binding.apply {
            movieList.adapter = adapter
            movieList.layoutManager = layoutManager
            movieList.addOnScrollListener(scrollListener)
            iconSearch.setOnClickListener { searchMovie() }
            editInputLayout.setOnKeyListener { _, i, keyEvent ->
                if (keyEvent.action == KeyEvent.ACTION_DOWN && i == KeyEvent.KEYCODE_ENTER) {
                    searchMovie()
                    showLoading()
                    return@setOnKeyListener true
                }
                return@setOnKeyListener false
            }
        }
    }

    private val scrollListener = object : OnScrollListener(){
        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
            super.onScrollStateChanged(recyclerView, newState)
            if(!recyclerView.canScrollVertically(1)){
                viewModel.getMovieList()
            }
        }
    }

    private fun searchMovie() {
        binding.apply {
            val query = editInputLayout.text.toString()
            if (query.isNotEmpty()) {
                showLoading()
                viewModel.getMovieByNameList(query)
            }
        }
    }




    private fun showLoading(){ binding.loading.show() }
    private fun hideLoading(){ binding.loading.hide() }
}

I searched some solution for this, some of them told me to modify the AndroidManifest.xml, so it has meta-data with name "android.app.default_searchable" and resource searchable.xml. I've made the searchable.xml too. In the end it became like this.

AndroidManifest.xml

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

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

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Nameful7"
        tools:targetApi="31" >
        <activity
            android:name=".page.movie.detail.MovieDetailActivity"
            android:exported="false" />
        <activity
            android:name=".page.MovieSearch"
            android:exported="false" />
        <activity
            android:name=".page.movie.list.MovieListActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

                <action android:name="android.intent.action.SEARCH" />
            </intent-filter>
            <meta-data
                android:name="android.app.default_searchable"
                android:resource="@xml/searchable" />
        </activity>
        <activity
            android:name=".page.genre.GenreListActivity"
            android:exported="false" />
        <activity
            android:name=".page.movie.list.MovieByGenreListActivity"
            android:exported="false" />
        <activity
            android:name=".page.movie.review.ReviewListActivity"
            android:exported="false" />
        <activity
            android:name=".page.Splash"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

                <action android:name="android.intent.action.SEARCH" />
            </intent-filter>
            <meta-data
                android:name="android.app.default_searchable"
                android:resource="@xml/searchable" />
        </activity>

        <meta-data
            android:name="preloaded_fonts"
            android:resource="@array/preloaded_fonts" />
    </application>

</manifest>

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" />

I started wondering if the problem is neither in the activity nor manifest and searchable, but inside some classes related to the activity.

MovieListViewModel.kt

class MovieListViewModel : ViewModel() {
    private val repository : GeneralRepository = GeneralRepository()
    private var page = 1
    private var listResponse: MovieListResponse? = null
    private var _response = MutableLiveData<RequestState<MovieListResponse?>>()
    var response:LiveData<RequestState<MovieListResponse?>> = _response




    fun getMovieList(){
        viewModelScope.launch {
            _response.postValue(RequestState.Loading)
            _response.postValue(handleMovieListResponse(repository.getMovies(page)))
        }
    }

    fun getMovieByNameList(query: String){
        viewModelScope.launch {
            _response.postValue(RequestState.Loading)
            _response.postValue(handleMovieListResponse(repository.getMoviesByName(page,query)))
        }
    }

    fun getMovieGenres() : LiveData<RequestState<GenreListResponse>> = liveData {
        emit(RequestState.Loading)
        try {
            emit(RequestState.Success(repository.getGenres().body()!!))
        }catch (e: HttpException){
            e.response()?.errorBody()?.string()?.let { RequestState.Error(it) }?.let { emit(it) }
        }
    }

    private fun handleMovieListResponse(
        response: Response<MovieListResponse>
    ): RequestState<MovieListResponse?> {
        return if(response.isSuccessful){
            response.body()?.let {
                page++
                if (listResponse == null)
                    listResponse = it
                else {
                    val moviesOld = listResponse?.results
                    val moviesNew = it.results
                    moviesOld?.addAll(moviesNew)
                }
            }
            RequestState.Success(listResponse ?: response.body())
        }else RequestState.Error(
            try{
                response.errorBody()?.string()?.let{
                    JSONObject(it).get("status_message")
                }
            }catch (e:JSONException){
                e.localizedMessage
            }as String
        )
    }

}

GeneralRepository.kt

class GeneralRepository {
    private val client = ApiConfig.getApiService()

    suspend fun getMovies(page:Int) =
        client.getMovies(BuildConfig.API_KEY, page)

    suspend fun getMoviesByGenre(page: Int, withGenres: String) =
        client.getMoviesByGenre(BuildConfig.API_KEY, page, withGenres)

    suspend fun getMoviesByName(page: Int, query: String) =
        client.getMoviesByName(BuildConfig.API_KEY,page,query)

    suspend fun getGenres() =
        client.getGenres(BuildConfig.API_KEY)

    suspend fun getMovieTrailers(movieId: Int) =
        client.getMovieTrailers(movieId,BuildConfig.API_KEY)

    suspend fun getMovieReviews(movieId: Int, page: Int) =
        client.getMovieReviews(movieId,BuildConfig.API_KEY,page)
}

ApiService.kt

interface ApiService {

    @GET("movie/popular")
    suspend fun getMovies(
        @Query("api_key") key: String?,
        @Query("page") page: Int?
    ): Response<MovieListResponse>

    @GET("discover/movie")
    suspend fun getMoviesByGenre(
        @Query("api_key") key: String?,
        @Query("page") page: Int?,
        @Query("with_genres") withGenres: String?
    ): Response<MovieListResponse>

    @GET("search/movie")
    suspend fun getMoviesByName(
        @Query("api_key") key: String?,
        @Query("page") page: Int?,
        @Query("query") query: String?,
    ): Response<MovieListResponse>

    @GET("genre/movie/list")
    suspend fun getGenres(
        @Query("api_key") key: String?,
    ): Response<GenreListResponse>

    @GET("movie/{movie_id}/videos")
    suspend fun getMovieTrailers(
        @Path("movie_id") movieId: Int?,
        @Query("api_key") key: String?,
    ): Response<VideoListResponse>

    @GET("movie/{movie_id}/reviews")
    suspend fun getMovieReviews(
        @Path("movie_id") movieId: Int?,
        @Query("api_key") key: String?,
        @Query("page") page: Int?
    ): Response<ReviewListResponse>
}

But I'm not sure what's wrong with them.

0

There are 0 answers