我正在遵循this中所述的方法(可以在here中找到相应的存储库) 使用分页库以从Firestore数据库中部分加载数据。 我没有使用其他活动(如原始文章中所述),而是将代码放在Fragment中。
在我的数据源中,调用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
}
}
答案 0 :(得分: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
}
}
我不太确定为什么能解决这个问题,但是可以。