@@ -17,35 +17,48 @@ package com.uber.rib.core
1717
1818import android.app.Application
1919import com.uber.autodispose.ScopeProvider
20- import com.uber.autodispose.coroutinesinterop.asCoroutineScope
20+ import com.uber.autodispose.lifecycle.LifecycleEndedException
2121import com.uber.rib.core.internal.CoroutinesFriendModuleApi
22+ import io.reactivex.CompletableObserver
23+ import io.reactivex.disposables.Disposable
2224import java.util.WeakHashMap
2325import kotlin.coroutines.CoroutineContext
2426import kotlin.coroutines.EmptyCoroutineContext
2527import kotlin.reflect.KProperty
2628import kotlinx.coroutines.CoroutineName
2729import kotlinx.coroutines.CoroutineScope
2830import kotlinx.coroutines.SupervisorJob
31+ import kotlinx.coroutines.cancel
2932import kotlinx.coroutines.job
3033
3134/* *
3235 * [CoroutineScope] tied to this [ScopeProvider]. This scope will be canceled when ScopeProvider is
3336 * completed
3437 *
3538 * This scope is bound to
36- * [RibDispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
39+ * [RibDispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
40+ *
41+ * Calling this property outside of the lifecycle of the [ScopeProvider] will throw
42+ * [OutsideScopeException][com.uber.autodispose.OutsideScopeException]. By setting
43+ * [RibCoroutinesConfig.shouldCoroutineScopeFailSilentlyOnLifecycleEnded] to `true`, accessing this
44+ * property after the [ScopeProvider] has completed will instead return a [CoroutineScope] that is
45+ * immediately cancelled.
3746 */
3847@OptIn(CoroutinesFriendModuleApi ::class )
39- public val ScopeProvider .coroutineScope: CoroutineScope by
40- LazyCoroutineScope <ScopeProvider > {
41- val context: CoroutineContext =
42- SupervisorJob () +
43- RibDispatchers .Main .immediate +
44- CoroutineName (" ${this ::class .simpleName} :coroutineScope" ) +
45- (RibCoroutinesConfig .exceptionHandler ? : EmptyCoroutineContext )
46-
47- asCoroutineScope(context)
48+ public val ScopeProvider .coroutineScope: CoroutineScope by LazyCoroutineScope {
49+ val context = createCoroutineContext()
50+ try {
51+ ScopeProviderCoroutineScope (this , context)
52+ } catch (e: LifecycleEndedException ) {
53+ if (RibCoroutinesConfig .shouldCoroutineScopeFailSilentlyOnLifecycleEnded) {
54+ CoroutineScope (context).also {
55+ it.cancel(" ScopeProvider is outside of scope. context = $context " , e)
56+ }
57+ } else {
58+ throw e
59+ }
4860 }
61+ }
4962
5063/* *
5164 * [CoroutineScope] tied to this [Application]. This scope will not be cancelled, it lives for the
@@ -55,16 +68,40 @@ public val ScopeProvider.coroutineScope: CoroutineScope by
5568 * [RibDispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
5669 */
5770@OptIn(CoroutinesFriendModuleApi ::class )
58- public val Application .coroutineScope: CoroutineScope by
59- LazyCoroutineScope <Application > {
60- val context: CoroutineContext =
61- SupervisorJob () +
62- RibDispatchers .Main .immediate +
63- CoroutineName (" ${this ::class .simpleName} :coroutineScope" ) +
64- (RibCoroutinesConfig .exceptionHandler ? : EmptyCoroutineContext )
71+ public val Application .coroutineScope: CoroutineScope by LazyCoroutineScope {
72+ CoroutineScope (createCoroutineContext())
73+ }
74+
75+ private fun Any.createCoroutineContext () =
76+ SupervisorJob () +
77+ RibDispatchers .Main .immediate +
78+ CoroutineName (" ${this ::class .simpleName} :coroutineScope" ) +
79+ (RibCoroutinesConfig .exceptionHandler ? : EmptyCoroutineContext )
6580
66- CoroutineScope (context)
81+ private class ScopeProviderCoroutineScope (
82+ scopeProvider : ScopeProvider ,
83+ coroutineContext : CoroutineContext ,
84+ ) :
85+ ScopeProvider by scopeProvider,
86+ CoroutineScope by CoroutineScope (coroutineContext),
87+ CompletableObserver {
88+
89+ init {
90+ requestScope().subscribe(this )
91+ }
92+
93+ override fun onSubscribe (d : Disposable ) {
94+ coroutineContext.job.invokeOnCompletion { d.dispose() }
95+ }
96+
97+ override fun onComplete () {
98+ cancel()
99+ }
100+
101+ override fun onError (e : Throwable ) {
102+ cancel(" ScopeProvider completed with error" , e)
67103 }
104+ }
68105
69106@CoroutinesFriendModuleApi
70107public class LazyCoroutineScope <This : Any >(private val initializer : This .() -> CoroutineScope ) {
0 commit comments