Using paging 3.0 , I am successful in implemented it. Now I want to add search functionality to it.
I simply display photo gallery along with paging functionality. Now I want to invalidate pagination when someone search
But whenever I call invalidate on search. App crashes..
PhotoFragment.kt
@AndroidEntryPoint
class PhotosFragment : BaseFragment<FragmentPhotosBinding,PhotosFragmentViewModel>(R.layout.fragment_photos),
SearchView.OnQueryTextListener, LifecycleObserver {
override val mViewModel: PhotosFragmentViewModel by viewModels()
private lateinit var photoAdapter: PhotoCollectionAdapter
override fun onAttach(context: Context) {
super.onAttach(context)
activity?.lifecycle?.addObserver(this)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setHasOptionsMenu(true)
///mViewModel.setFilter(getString(R.string.search_filter_default_value))
initAdapter()
}
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onCreated(){
mViewModel.trendingPhotos.observe(viewLifecycleOwner, Observer {
photoAdapter.submitData(lifecycle,it)
})
}
private fun initAdapter() {
photoAdapter = PhotoCollectionAdapter()
photoAdapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
mBinding.recyclerView.apply {
layoutManager = LinearLayoutManager(context)
setHasFixedSize(true)
adapter = photoAdapter
}
photoAdapter.addLoadStateListener { loadState ->
mBinding.recyclerView.isVisible = loadState.refresh is LoadState.NotLoading
val errorState = loadState.source.append as? LoadState.Error
?: loadState.source.prepend as? LoadState.Error
?: loadState.append as? LoadState.Error
?: loadState.prepend as? LoadState.Error
errorState?.let {
}
}
}
var timer: CountDownTimer? = null
override fun onQueryTextSubmit(p0: String?): Boolean = false
override fun onQueryTextChange(newText: String?): Boolean {
timer?.cancel()
timer = object : CountDownTimer(1000, 2500) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
Timber.d("query : %s", newText)
if (newText!!.trim().replace(" ", "").length >= 3) {
mViewModel.cachedFilter = newText
mViewModel.setFilter(newText)
}
///afterTextChanged.invoke(editable.toString())
}
}.start()
return true
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.search_menu, menu)
// Get the SearchView and set the searchable configuration
val searchManager = activity?.getSystemService(Context.SEARCH_SERVICE) as SearchManager
//val searchManager = activity!!.getSystemService(Context.SEARCH_SERVICE) as SearchManager
(menu.findItem(R.id.app_bar_search).actionView as SearchView).apply {
// Assumes current activity is the searchable activity
setSearchableInfo(searchManager.getSearchableInfo(activity?.componentName))
setIconifiedByDefault(false) // Do not iconify the widget; expand it by default
queryHint = getString(R.string.search_view_hint)
setQuery(
if (mViewModel.cachedFilter.isEmpty()) getString(R.string.search_filter_default_value) else mViewModel.cachedFilter,
true
)
isSubmitButtonEnabled = true
}.setOnQueryTextListener(this)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return view?.let {
NavigationUI.onNavDestinationSelected(item,it.findNavController())
}?: kotlin.run {
super.onOptionsItemSelected(item)
}
}
}
PhotosFragmentViewModel.kt
@HiltViewModel
class PhotosFragmentViewModel @Inject constructor(
private val photoPagingSourceRx: PhotoPagingSourceRx
): BaseViewModel() {
private val _trendingPhotos = MutableLiveData<PagingData<Models.PhotoResponse>>()
val trendingPhotos: LiveData<PagingData<Models.PhotoResponse>>
get() = _trendingPhotos
var cachedFilter: String = ""
fun setFilter(filter: String) {
photoPagingSourceRx.setFilter(if (cachedFilter.isEmpty()) filter else cachedFilter)
}
init {
viewModelScope.launch {
getPhotosRx().cachedIn(viewModelScope).subscribe {
_trendingPhotos.value = it
}
}
}
private fun getPhotosRx(): Flowable<PagingData<Models.PhotoResponse>> {
return Pager(
config = PagingConfig(
pageSize = 20,
enablePlaceholders = false,
prefetchDistance = 5
),
pagingSourceFactory = { photoPagingSourceRx }
).flowable
}
}
PhotoPagingSourceRx.kt
@Singleton
class PhotoPagingSourceRx @Inject constructor(
private val restApi: RestApi
): RxPagingSource<Int, Models.PhotoResponse>() {
private var filter: String = "Flowers"
private var lastFilter = filter
fun setFilter(filter: String) {
this.filter = filter
}
override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, Models.PhotoResponse>> {
val page = if(lastFilter == filter) params.key ?: 1 else 1
lastFilter = filter
return restApi.getPhotos(filter,20,page).subscribeOn(Schedulers.io()).map {
Log.v("pagingLog","page -> $page ) ")
LoadResult.Page(
data = it.response,
prevKey = if (page == 1) null else page - 1,
nextKey = page + 1
) as LoadResult<Int, Models.PhotoResponse>
}.onErrorReturn {
LoadResult.Error(it)
}
}
override fun getRefreshKey(state: PagingState<Int, Models.PhotoResponse>): Int? {
return state.anchorPosition
}
}
.switchMap
onLivePagedList
, passing search query intoDataSource
and trigger new generations that way. – Photocomposition