Search News Fragment

Learn how to implement search functionality in our application.

Searching our data

We will implement an Android search widget in our fragment, allowing the user to search for articles by typing in a query. When the search is executed, the query will be sent to a function for processing, and the search will be performed.

Implementing search functionality

We’ll implement a search feature that allows users to search for specific articles by typing in the source, publishing company, or author’s name. This is done by subscribing to an observer in the ViewModel and passing the latest data as a search query to the searchNews() method within a coroutine. The searchNews() method takes in a search query as one of its arguments and uses it to request the API to perform the desired search.

Press + to interact
class SearchNewsFragment : Fragment(R.layout.fragment_search_news) {
lateinit var viewModel: NewsViewModel
lateinit var newsAdapter: NewsAdapter
lateinit var binding: FragmentSearchNewsBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentSearchNewsBinding.bind(view)
val newsRepository = NewsRepository(ArticleDatabase(requireContext() as NewsActivity))
val viewModelProviderFactory =
NewsViewModelProvider(activity?.application!!, newsRepository)
viewModel = ViewModelProvider(this, viewModelProviderFactory).get(NewsViewModel::class.java)
setUpRecyclerView(binding)
newsAdapter.setOnItemClickListener {
val bundle = Bundle().apply {
putSerializable("article", it)
}
findNavController().navigate(
R.id.action_searchNewsFragment2_to_articleFragment,
bundle
)
}
var job: Job? = null
binding.editTextSearch.addTextChangedListener { editable ->
job?.cancel()
job = MainScope().launch {
delay(Constants.SEARCH_NEWS_TIME_DELAY)
editable?.let {
if (editable.toString().isNotEmpty()) {
viewModel.searchNews(editable.toString())
}
}
}
}
viewModel.searchNews.observe(viewLifecycleOwner, Observer { response ->
response.data?.let { newsResponse ->
newsAdapter.differ.submitList(newsResponse.articles.toList())
val totalPages = newsResponse.totalResults
isLastPage = viewModel.searchNewsPage == totalPages
}
})
}
private fun setUpRecyclerView(binding: FragmentSearchNewsBinding) {
newsAdapter = NewsAdapter()
binding.searchNewsRecyclerView.apply {
adapter = newsAdapter
layoutManager = LinearLayoutManager(activity)
addOnScrollListener(this@SearchNewsFragment.scrollListener)
}
}
private fun hideProgressBar(binding: FragmentSearchNewsBinding) {
binding.paginationProgressBar.visibility = View.INVISIBLE
isLoading = false
}
private fun showProgressBar(binding: FragmentSearchNewsBinding) {
binding.paginationProgressBar.visibility = View.VISIBLE
isLoading = false
}
var isLastPage = false
var isLoading = false
var isScrolling = false
val scrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
val visibleItemCount = layoutManager.childCount
val totalItemCount = layoutManager.itemCount
val isNotLoadingAndNotLastPage = !isLoading && !isLastPage
val isAtLastItem = firstVisibleItemPosition + visibleItemCount >= totalItemCount
val isNotAtBeginning = firstVisibleItemPosition >= 0
val isTotalMoreThanVisible = totalItemCount >= Constants.QUERY_PAGE_SIZE
val shouldPaginate = isNotLoadingAndNotLastPage && isAtLastItem && isNotAtBeginning &&
isTotalMoreThanVisible && isScrolling
if (shouldPaginate) {
viewModel.searchNews(binding.editTextSearch.text.toString())
isScrolling = false
} else {
binding.searchNewsRecyclerView.setPadding(0, 0, 0, 0)
}
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
isScrolling = true
}
}
}
}

As you may have noticed, some of the ...