使用Fragment的Android Jetpack分页:适配器无法接收回调

问题描述 投票:2回答:1

我遵循this帖子(相应的存储库可以找到here)中描述的方法来使用Paging库来部分加载来自我的Firestore数据库的数据。我没有使用不同的活动(就像在原帖中所做的那样),而是将代码放在片段中。

在我的DataSource中,调用loadInitial函数,我们在其中订阅结果。一旦结果可用,调用callback.onResult作为参数,新检索的数据不会到达适配器。单步进入callback.onResult函数后,onNext方法发生以下异常:

java.lang.IllegalStateException: callback.onResult already called, cannot call again.

但是,如果我按下后退按钮,再次登录,callback.Onresult的调用确实会到达适配器。

最初,这让我相信我做错了与活动/片段的生命周期有关的事情,但是改变用活动上下文初始化适配器的位置并没有改变结果。

This帖子还提到了两次加载片段以使事情正常工作的事情,这可以解释按下后退按钮并返回时观察到的结果。

编辑:添加了更多相关(更新)的代码

编辑2:在WorkManager中添加额外的Dagger注释和重构代码以使用RxJava2

编辑3:我完全剥离了所有Dagger 2用法,现在代码工作,所以问题是Dagger相关

编辑4:问题解决了,请看下面的帖子

相关代码如下:

分段

class TradeRequestFragment : Fragment() {

    private val auth: FirebaseAuth by lazy { FirebaseAuth.getInstance() }
    private lateinit var rootView: View

    @SuppressLint("RestrictedApi")
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        rootView = inflater.inflate(R.layout.fragment_trade_requests, container, false)
        return rootView
    }

    private fun initAdapter() {
        // Setup the RecyclerView for the User's trade requests.
        // 1. get a reference to recyclerView
        val tradeRequestRecyclerView = rootView.findViewById<RecyclerView>(R.id.trade_requests_recycler_view)
        // 2. set LayoutManager
        tradeRequestRecyclerView.layoutManager = LinearLayoutManager(activity?.applicationContext)
        val tradeRequestAdapter = TradeRequestAdapter(activity?.applicationContext)

        // Dagger 2 injection
        val component = DaggerTradeRequestFragment_TradeRequestComponent.builder().tradeRequestModule(TradeRequestModule()).build()
        val tradeRequestViewModel = TradeRequestViewModel(component.getTradeRequestDataProvider())

        // 3. Set Adapter
        tradeRequestRecyclerView.adapter = tradeRequestAdapter
        // 4. Set the item animator
        tradeRequestRecyclerView.addItemDecoration(DividerItemDecoration(activity?.applicationContext, LinearLayoutManager.VERTICAL))
        tradeRequestViewModel.getTradeRequests()?.observe(viewLifecycleOwner, Observer(tradeRequestAdapter::submitList))
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        initAdapter()
    }

    @Module
    inner class TradeRequestModule {

        @Provides
        @Singleton
        fun provideDataSource(): TradeRequestsDataSource {
            return TradeRequestsDataSource(auth.currentUser?.uid
                    ?: "")
        }

        @Provides
        @Singleton
        fun provideUserId(): String {
            return auth.currentUser?.uid ?: ""
        }
    }

    @Singleton
    @Component(modules = [TradeRequestModule::class])
    interface TradeRequestComponent {
        fun getTradeRequestDataProvider(): TradeRequestDataProvider
    }
}

TradeRequestManager

class TradeRequestManager private constructor(userID: String) {
    companion object : Utils.Singleton<TradeRequestManager, String>(::TradeRequestManager)


    private val TRADE_REQUEST_ROUTE = "tradeRequests"
    private val userIDKey = "userId"
    private val userTradeRequestsAdapterInvalidation = PublishSubject.create<Any>()
    private val database = FirebaseFirestore.getInstance()
    private val databaseRef: Query? by lazy {
        try {
            database.collection(TRADE_REQUEST_ROUTE).whereEqualTo(userIDKey, userID)
        } catch (e: Exception) {
            Log.e(this.TAG(), "Could not retrieve the user's trade requests", e.cause)
            null
        }
    }

    private val tradeRequestBuilder: Moshi by lazy {
        Moshi.Builder()
                .add(ZonedDateTime::class.java, ZonedDateTimeAdapter())
                .add(CurrencyUnit::class.java, CurrencyUnitAdapter())
                .add(Money::class.java, JodaMoneyAdapter())
                .add(KotlinJsonAdapterFactory())
                .build()
    }
    private val tradeRequestAdapter: JsonAdapter<TradeRequest> by lazy { tradeRequestBuilder.adapter(TradeRequest::class.java) }


    // TODO see [here][https://leaks.wanari.com/2018/07/30/android-jetpack-paging-firebase]
    init {
        databaseRef?.addSnapshotListener(object : EventListener<QuerySnapshot> {
            override fun onEvent(snapshot: QuerySnapshot?, e: FirebaseFirestoreException?) {
                if (e != null) {
                    Log.e(this.TAG(), "listener:error", e)
                    return
                }
                if (snapshot == null) {
                    return
                }
                for (dc in snapshot.documentChanges) {
                    when (dc.type) {
                        DocumentChange.Type.ADDED -> userTradeRequestsAdapterInvalidation.onNext(true)
                        DocumentChange.Type.MODIFIED -> userTradeRequestsAdapterInvalidation.onNext(true)
                        DocumentChange.Type.REMOVED -> userTradeRequestsAdapterInvalidation.onNext(true)
                    }
                }
            }
        })
    }

    fun getUserTradeRequestsChangeSubject(): PublishSubject<Any>? {
        return userTradeRequestsAdapterInvalidation
    }

    // https://stackoverflow.com/questions/45420829/group-data-with-rxjava-2-add-element-to-each-group-and-output-one-list
    fun getTradeRequests(count: Int): Single<List<TradeRequest?>> {
        if (databaseRef == null) {
            return Observable.empty<List<TradeRequest?>>().singleOrError()
        }
        // By default, we order by 'creationDate' descending
        // If the field by which we order does not exists, no results are returned
        return RxFirestore.observeQueryRef(databaseRef!!.orderBy("creationDate", Query.Direction.DESCENDING).limit(count.toLong()))
                .firstElement()
                .toSingle()
                .flattenAsObservable { list -> list.documents }
                .flatMap { doc -> Observable.just(doc.data as? Map<String, String>) }
                .map { json -> tryOrNull { tradeRequestAdapter.fromJsonValue(json) } }
                .filter { tradeRequest -> tradeRequest != null }
                .toList()
    }

    fun getTradeRequestsAfter(key: String, value: String, count: Int, order: Query.Direction): Single<Pair<List<TradeRequest?>, String>> {
        if (databaseRef == null) {
            return Observable.empty<Pair<List<TradeRequest?>, String>>().singleOrError()
        }
        val result = RxFirestore.observeQueryRef(databaseRef!!.whereGreaterThanOrEqualTo(key, value).limit(count.toLong()).orderBy(key, order))
                .firstElement()
                .toSingle()
                .flattenAsObservable { list -> list.documents }
                .flatMap { doc -> Observable.just(doc.data as? Map<String, String>) }
                .map { json -> tryOrNull { tradeRequestAdapter.fromJsonValue(json) } }
                .filter { tradeRequest -> tradeRequest != null }
                .toList()

        val tradeRequests = result.blockingGet()

        // FIXME determine next filter value
        var newFilterValue = ""
        if (tradeRequests.size == count) {
            // Either the number of elements is capped or exactly "count" elements matched
            newFilterValue = ""
        }
        // END FIXME
        return Observable.just(Pair(tradeRequests, newFilterValue)).singleOrError()
    }

    fun getTradeRequestsBefore(key: String, value: String, count: Int, order: Query.Direction): Single<Pair<List<TradeRequest?>, String>> {
        if (databaseRef == null) {
            return Observable.empty<Pair<List<TradeRequest?>, String>>().singleOrError()
        }
        val result = RxFirestore.observeQueryRef(databaseRef!!.whereLessThan(key, value).limit(count.toLong()).orderBy(key, order))
                .firstElement()
                .toSingle()
                .flattenAsObservable { list -> list.documents }
                .flatMap { doc -> Observable.just(doc.data as? Map<String, String>) }
                .map { json -> tryOrNull { tradeRequestAdapter.fromJsonValue(json) } }
                .filter { tradeRequest -> tradeRequest != null }
                .toList()

        val tradeRequests = result.blockingGet()

        // FIXME determine next filter value
        var newFilterValue = ""
        if (tradeRequests.size == count) {
            // Either the number of elements is capped or exactly "count" elements matched
            newFilterValue = ""
        }
        // END FIXME
        return Observable.just(Pair(tradeRequests, newFilterValue)).singleOrError()
    }

数据源

class TradeRequestsDataSource @Inject constructor(var userId: String, var filter: Data) : ItemKeyedDataSource<String, TradeRequest>() {


    private var filterKey: String
    private var filterValue: String
    private var filterOrder: Query.Direction
    private var userTradeRequestsManager: TradeRequestManager = TradeRequestManager.getInstance(userId)

    init {
        userTradeRequestsManager.getUserTradeRequestsChangeSubject()?.observeOn(Schedulers.io())?.subscribeOn(Schedulers.computation())?.subscribe {
            invalidate()
        }

        filterKey = filter.getString("filterKey") ?: ""
        filterValue = filter.getString("filterValue") ?: ""
        filterOrder = try {
            Query.Direction.valueOf(filter.getString("filterOrder") ?: "")
        } catch (e: Exception) {
            Query.Direction.DESCENDING
        }
    }

    override fun loadInitial(params: LoadInitialParams<String>, callback: LoadInitialCallback<TradeRequest>) {
        Log.i(this.TAG(), "Loading the initial items in the RecyclerView")
        userTradeRequestsManager.getTradeRequests(params.requestedLoadSize).singleElement().subscribe({ tradeRequests ->
            Log.i(this.TAG(), "We received the callback")
            callback.onResult(tradeRequests)
        }, {})
    }

    override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<TradeRequest>) {
        userTradeRequestsManager.getTradeRequestsAfter(params.key, this.filterValue, params.requestedLoadSize, this.filterOrder).singleElement().subscribe({
            this.filterValue = it.second
            callback.onResult(it.first)
        }, {})
    }

    override fun loadBefore(params: LoadParams<String>, callback: LoadCallback<TradeRequest>) {
        userTradeRequestsManager.getTradeRequestsBefore(params.key, this.filterValue, params.requestedLoadSize, this.filterOrder).singleElement().subscribe({
            this.filterValue = it.second
            callback.onResult(it.first)
        }, {})
    }

    override fun getKey(item: TradeRequest): String {
        return filterKey
    }

适配器

class TradeRequestAdapter(val context: Context?) : PagedListAdapter<TradeRequest, TradeRequestAdapter.TradeRequestViewHolder>(
        object : DiffUtil.ItemCallback<TradeRequest>() {
            override fun areItemsTheSame(oldItem: TradeRequest, newItem: TradeRequest): Boolean {
                return oldItem == newItem
            }

            override fun areContentsTheSame(oldItem: TradeRequest, newItem: TradeRequest): Boolean {
                return oldItem.amount == newItem.amount &&
                        oldItem.baseCurrency == newItem.baseCurrency &&
                        oldItem.counterCurrency == newItem.counterCurrency &&
                        oldItem.creationDate == newItem.creationDate &&
                        oldItem.userId == newItem.userId


            }
        }) {


    private lateinit var mInflater: LayoutInflater

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TradeRequestViewHolder {
        mInflater = LayoutInflater.from(context)
        val view = mInflater.inflate(R.layout.trade_request_item, parent, false)
        return TradeRequestViewHolder(view)
    }

    override fun onBindViewHolder(holder: TradeRequestViewHolder, position: Int) {
        val tradeRequest = getItem(position)
        holder.tradeRequestBaseCurrency.text = tradeRequest?.baseCurrency.toString()
        holder.tradeRequestCounterCurrency.text = tradeRequest?.counterCurrency.toString()
        holder.tradeRequestAmount.text = tradeRequest?.amount.toString()
    }


    // Placeholder class for displaying a single TradeRequest
    class TradeRequestViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var tradeRequestBaseCurrency: TextView = itemView.trade_request_item_from_currency
        var tradeRequestCounterCurrency: TextView = itemView.trade_request_item_to_currency
        var tradeRequestAmount: TextView = itemView.trade_request_item_amount
    }
}
android kotlin android-recyclerview pagination dagger-2
1个回答
0
投票

问题的原因是在DataFactory中完成依赖注入。原始代码如下:

@Singleton
class AnimalDataFactory @Inject constructor(): DataSource.Factory<String, Animal>() {


    private var datasourceLiveData = MutableLiveData<AnimalDataSource>()


    override fun create(): AnimalDataSource {
        // CORRECT: DataSource MUST be initialized here, otherwise items will not show up on initial activity/fragment launch
        val dataSource = AnimalDataSource()
        datasourceLiveData.postValue(dataSource)
        return dataSource
    }
}

我把它改成了

@Singleton
class AnimalDataFactory @Inject constructor(var dataSource: AnimalDataSourc): DataSource.Factory<String, Animal>() {


    private var datasourceLiveData = MutableLiveData<AnimalDataSource>()


    override fun create(): AnimalDataSource {
        // WRONG
        datasourceLiveData.postValue(dataSource)
        return dataSource
    }
}

我不太清楚为什么这可以解决这个问题,但它确实有效。

© www.soinside.com 2019 - 2024. All rights reserved.