实现分页库 3.0 过滤/搜索功能 [英] implmenent Paging Library 3.0 Filter/Search functionality

查看:28
本文介绍了实现分页库 3.0 过滤/搜索功能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用 paging 3.0 ,我成功地实现了它.现在我想给它添加搜索功能.

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
    }
}

推荐答案

我还没有机会看到你的崩溃,使失效工作绝对重要,因为 PagingSource 的单个实例意味着代表一个不可变的快照并在更改时失效(因此动态设置过滤器在这里不起作用).

I didn't get a chance to look at your crash yet, getting invalidation working is definitely important as a single instance of PagingSource is meant to represent an immutable snapshot and invalidate when it changes (so setting filter dynamically does not work well here).

改为尝试这种方法,因为看起来您需要将过滤器传递给网络 api:

Instead try this approach since it looks like you need to pass filter to network api:

ViewModel.kt

ViewModel.kt

val queryFlow = MutableStateFlow<String>("")
val pagingDataFlow = queryFlow.flatMapLatest { query ->
  Pager(...) {
    PhotoPagingSourceRx(query)
  }.flow
}.cachedIn(viewModelScope)

PhotoPagingSourceRx(顺便说一句,这不能是单例)

PhotoPagingSourceRx (btw, this cannot be a singleton)

class PhotoPagingSourceRx @Inject constructor(
    private val restApi: RestApi,
    private val filter: String,
): RxPagingSource<Int, Models.PhotoResponse>() {

    override fun loadSingle(..): Single<LoadResult<Int, Models.PhotoResponse>> { ... }

    override fun getRefreshKey(..): Int? { ... }
}

这篇关于实现分页库 3.0 过滤/搜索功能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆