android / architecture-components-samples

Samples for Android Architecture Components.
https://d.android.com/arch
Apache License 2.0
23.44k stars 8.28k forks source link

[Paging with Network] Fix consumeAsFlow can be collected just once #892

Closed eneim closed 4 years ago

eneim commented 4 years ago

The issue

Currently, the Paging with Network demo uses ReceiveChannel.consumeAsFlow which crashes the App on configuration changes. The reason, IMO, is when the Activity is recreated, the ViewModel's posts value starts collecting again for the new Adapter. This goes against the contract of consumeAsFlow which allows up to one consumer.

How to reproduce:


    Process: com.android.example.paging.pagingwithnetwork, PID: 21282
    java.lang.IllegalStateException: ReceiveChannel.consumeAsFlow can be collected just once
        at kotlinx.coroutines.flow.ChannelAsFlow.markConsumed(Channels.kt:125)
        at kotlinx.coroutines.flow.ChannelAsFlow.collect(Channels.kt:150)
        at com.android.example.paging.pagingwithnetwork.reddit.ui.SubRedditViewModel$$special$$inlined$map$1.collect(SafeCollector.common.kt:114)
        at kotlinx.coroutines.flow.internal.ChannelFlowMerge$collectTo$$inlined$collect$1$lambda$1.invokeSuspend(Merge.kt:67)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
        at androidx.lifecycle.DispatchQueue.drainQueue(DispatchQueue.kt:76)
        at androidx.lifecycle.DispatchQueue.resume(DispatchQueue.kt:55)
        at androidx.lifecycle.LifecycleController$observer$1.onStateChanged(LifecycleController.kt:40)
        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.lifecycle.ReportFragment.dispatch(ReportFragment.java:68)
        at androidx.lifecycle.ReportFragment$LifecycleCallbacks.onActivityPostCreated(ReportFragment.java:170)
        at android.app.Activity.dispatchActivityPostCreated(Activity.java:1216)
        at android.app.Activity.performCreate(Activity.java:7821)
        at android.app.Activity.performCreate(Activity.java:7801)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1307)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3298)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3462)
        at android.app.ActivityThread.handleRelaunchActivityInner(ActivityThread.java:5429)
        at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:5337)
        at android.app.servertransaction.ActivityRelaunchItem.execute(ActivityRelaunchItem.java:69)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2063)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7615)
        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:964)

The fix

This PR use the ReceiveChannel.receiveAsFlow instead. It allows multiple receivers.

dlam commented 4 years ago

Thanks for this!