1

I am working with paging 3.0.0-alpha11 library in my app.
There is a fragment that has a view model. in the view model I implemented paging data like this. there is one mutable live data for a parameter named promoId. promoId is a string that represents id of a model. i use it with another parameter named page to get a page of data from network. so the problem is that the first time i navigate to te fragment every thing works fine, but after popping the fragment and navigating to it for the second time, I get an error and app crashes.
my fragment code.

class ExploreFragment : DaggerFragment() {

    private var _binding: FragmentExploreBinding? = null
    private val binding get() = _binding!!

    @Inject
    lateinit var viewModelProviderFactory: ViewModelProviderFactoryImpl
    private lateinit var exploreViewModel: ExploreViewModel

    
    lateinit var commentsAdapter: LiveCommentAdapter

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        initLists()
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
       
        exploreViewModel =
            ViewModelProvider(this, viewModelProviderFactory)[ExploreViewModel::class.java]

        observeComments()
        commentsViewModel.getPromoComments(promoId)
        }
    }

    private fun initLists() {
    
        commentsAdapter = LiveCommentAdapter()
        binding.rvComments.adapter = commentsAdapter
        binding.rvSearchResults.adapter = searchResultAdapter
    }
   

    private fun observeComments() {
        exploreViewModel.commentsLiveData.observe(viewLifecycleOwner) { comments ->
            commentsAdapter.submitData(viewLifecycleOwner.lifecycle, comments)
        }
    }

   
 

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

This is my view model
also note that I don't use cachedIn(viewModelScope) after commentRepository.getCommentsByPromo(promoId?:"") because it caches the data and does not call load method of paging data for the second time.

class ExploreViewModel @Inject constructor(
    @Inject @JvmField var commentRepository: CommentRepository,
) :
    ViewModel() {


    private val promoIdLiveData: MutableLiveData<String?> = MutableLiveData()

    var commentsLiveData: LiveData<PagingData<Comment>> = promoIdLiveData.switchMap { promoId ->
        commentRepository.getCommentsByPromo(promoId?:"")
    }

    fun getComments(promoId: String) {
        promoIdLiveData.value = promoId
    }
}

And this is my repository code.

class CommentRepository @Inject constructor(@Inject @JvmField var retrofit: Retrofit) {

    private val api: CommentsApi = retrofit.create(CommentsApi::class.java)

    fun getCommentsByPromo(promoId: String):LiveData<PagingData<Comment>> = Pager(
        config = PagingConfig(pageSize = 20, enablePlaceholders = false,prefetchDistance = 100),
        pagingSourceFactory = { PromoCommentPagingSource(promoId, api) }
    ).liveData

}

and the error i get is

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.tivasoft.project, PID: 28896
    java.lang.IllegalStateException: Attempt to collect twice from pageEventFlow, which is an illegal operation. Did you forget to call Flow<PagingData<*>>.cachedIn(coroutineScope)?
        at androidx.paging.PageFetcherSnapshot$pageEventFlow$1.invokeSuspend(PageFetcherSnapshot.kt:88)
        at androidx.paging.PageFetcherSnapshot$pageEventFlow$1.invoke(Unknown Source:10)
        at androidx.paging.CancelableChannelFlowKt$cancelableChannelFlow$1.invokeSuspend(CancelableChannelFlow.kt:35)
        at androidx.paging.CancelableChannelFlowKt$cancelableChannelFlow$1.invoke(Unknown Source:10)
        at kotlinx.coroutines.flow.ChannelFlowBuilder.collectTo$suspendImpl(Builders.kt:344)
        at kotlinx.coroutines.flow.ChannelFlowBuilder.collectTo(Unknown Source:0)
        at kotlinx.coroutines.flow.internal.ChannelFlow$collectToFun$1.invokeSuspend(ChannelFlow.kt:60)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at kotlinx.coroutines.EventLoop.processUnconfinedEvent(EventLoop.common.kt:69)
        at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:349)
        at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
        at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:27)
        at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:109)
        at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:158)
        at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56)
        at kotlinx.coroutines.BuildersKt.launch(Unknown Source:1)
        at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:49)
        at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source:1)
        at androidx.paging.AsyncPagingDataDiffer.submitData(AsyncPagingDataDiffer.kt:156)
        at androidx.paging.PagingDataAdapter.submitData(PagingDataAdapter.kt:172)
        at com.tivasoft.project.ui.explore.ExploreFragment$observeComments$$inlined$observe$1.onChanged(LiveData.kt:52)
        at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
        at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:144)
        at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:443)
        at androidx.lifecycle.LiveData$LifecycleBoundObserver.onStateChanged(LiveData.java:395)
        at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:361)
        at androidx.lifecycle.LifecycleRegistry.forwardPass(LifecycleRegistry.java:300)
        at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:339)
        at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:145)
        at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:131)
        at androidx.fragment.app.FragmentViewLifecycleOwner.handleLifecycleEvent(FragmentViewLifecycleOwner.java:51)
        at androidx.fragment.app.Fragment.performStart(Fragment.java:2737)
        at androidx.fragment.app.FragmentStateManager.start(FragmentStateManager.java:365)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1194)
        at androidx.fragment.app.FragmentManager.addAddedFragments(FragmentManager.java:2224)
        at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1997)
        at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1953)
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1849)
        at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:413)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

I would be very thankful if anybody can tell me what's wrong

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Reza
  • 845
  • 13
  • 18
  • The 2 answers [here](https://stackoverflow.com/questions/62554267/changing-request-and-get-a-new-data-stream-when-using-paging3-library) helped me get rid of this error – lasec0203 Jun 08 '21 at 05:22

3 Answers3

3

I'm a bit confused on exactly what you want to happen, but my understanding is that you want to reload from scratch whenever you navigate back.

The error is saying you cannot call .submitData on the same instance of PagingData twice. You're essentially trying load cached data unintentionally and since you didn't call cachedIn, Paging is throwing an exception.

An easy way to force Paging to load from scratch every time, is to make sure you construct a new instance of Pager each time.

So I would try calling observeComments() in onViewCreated and turn commentsLiveData into a function (instead of a var) which returns a new LiveData<PagingData> each time its called.

dlam
  • 3,547
  • 17
  • 20
1

In my case I had a Transformations.switchMap(liveData) and that was causing the issue, the fix was to make the PagingData to cache before the switchMap

fun observable(): LiveData<PagingData<SomeModel>> {
    val pagingData = //obtained somewhere
    //make sure to cache it
    val chachedPagingData = pagingData.cachedIn(viewModelScope)
    return Transformations.switchMap(otherLiveData) { observed ->
        //use observed for obtaining live attributes needed
        chachedPagingData.filter {...}.map{...}
    }
}

For me, that was the solution because the pagingData sometimes can be suspend, so the above would be more explanatory like this:

suspend fun observable(): LiveData<PagingData<SomeModel>> {
    val someAttributeFromTheDb = //get it using ROOM
    val pagingData = if (someAttributeFromTheDb == /*condition*/) {
        suspendPagingData } else regularPagingData
    val chachedPagingData = ...
}
cutiko
  • 9,887
  • 3
  • 45
  • 59
0

Just replace MutableLiveData

private val promoIdLiveData: MutableLiveData<String?> = MutableLiveData()

with SingleLiveEvent

private val promoIdLiveData: SingleLiveEvent<String?> = SingleLiveEvent()

The SingleLiveEvent

class SingleLiveEvent<T> : MutableLiveData<T>() {
    private val pending = AtomicBoolean(false)
    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        if (hasActiveObservers()) {
            Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
        }
        // Observe the internal MutableLiveData
        super.observe(
            owner,
            Observer { t ->
                if (pending.compareAndSet(true, false)) {
                    observer.onChanged(t)
                }
            }
        )
    }

    @MainThread
    override fun setValue(t: T?) {
        pending.set(true)
        super.setValue(t)
    }
    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    fun call() {
        value = null
    }
    companion object {
        private val TAG = "SingleLiveEvent"
    }
}
Sri Harsha Chilakapati
  • 11,744
  • 6
  • 50
  • 91