Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change LifecycleCoroutineScope argument to a CoroutineContext, add LifecycleCoroutineScopeFactory #145

Merged
merged 5 commits into from
Jul 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ class IdlingCoroutineScopeRuleWithLifecycleSample {

@Before
fun setUp() {
LifecycleScopeFactory.set {
MainImmediateCoroutineScope(customDispatcherProvider)
}
LifecycleScopeFactory.set { customDispatcherProvider }
ViewModelScopeFactory.set {
MainImmediateCoroutineScope(customDispatcherProvider)
}
Expand Down
8 changes: 4 additions & 4 deletions dispatch-android-lifecycle-extensions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class SomeApplication : Application() {
override fun onCreate() {
super.onCreate()
// A custom factory can be set to add elements to the CoroutineContext
LifecycleScopeFactory.set { MainImmediateCoroutineScope() + SomeCustomElement() }
LifecycleScopeFactory.set { MainImmediateProvidedCoroutineContext() + SomeCustomElement() }
}
}
```
Expand All @@ -62,13 +62,13 @@ class SomeEspressoTest {
fun setUp() {
// This custom factory can be used to use custom scopes for testing,
// such as an idling dispatcher
LifecycleScopeFactory.set { MainImmediateIdlingCoroutineScope() }
LifecycleScopeFactory.set { MainImmediateIdlingCoroutineScope().coroutineContext }
}

@After
fun tearDown() {
// The factory can also be reset to default
LifecycleScopeFactory.reset()
LifecycleCoroutineScopeFactory.reset()
}
}
```
Expand All @@ -93,7 +93,7 @@ class SomeFragmentEspressoTest {
fun setUp() {
// set a custom factory which is applied to all newly created lifecycleScopes
LifecycleScopeFactory.set {
MainImmediateCoroutineScope() + idlingRule.dispatcherProvider
MainImmediateProvidedCoroutineContext() + idlingRule.dispatcherProvider
}

// now SomeFragment will use an IdlingDispatcher in its CoroutineScope
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
public final class dispatch/android/lifecycle/LifecycleScopeFactory {
public static final field INSTANCE Ldispatch/android/lifecycle/LifecycleScopeFactory;
public final fun reset ()V
public final fun set (Ldispatch/android/lifecycle/LifecycleCoroutineScopeFactory;)V
public final fun set (Lkotlin/jvm/functions/Function0;)V
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package samples
import dispatch.android.espresso.*
import dispatch.android.lifecycle.*
import dispatch.core.*
import kotlinx.coroutines.*

class LifecycleScopeFactorySample {

Expand All @@ -29,7 +30,8 @@ class LifecycleScopeFactorySample {
class MyApplication : Application {

override fun onCreate() {
LifecycleScopeFactory.set { MainImmediateCoroutineScope() }

LifecycleScopeFactory.set { MyCustomElement() + MainImmediateContext() }
}
}
}
Expand All @@ -41,19 +43,25 @@ class LifecycleScopeFactorySample {

@Before
fun setUp() {
LifecycleScopeFactory.set { MainImmediateIdlingCoroutineScope() }

val dispatcherProvider = IdlingDispatcherProvider()

LifecycleScopeFactory.set { SupervisorJob() + dispatcherProvider + dispatcherProvider.mainImmediate }
}
}
}

@Sample
fun lifecycleScopeFactoryResetSample() {
fun LifecycleScopeFactoryResetSample() {

class MyEspressoTest {

@Before
fun setUp() {
LifecycleScopeFactory.set { MainImmediateIdlingCoroutineScope() }

val dispatcherProvider = DefaultDispatcherProvider()

LifecycleScopeFactory.set { SupervisorJob() + dispatcherProvider + dispatcherProvider.mainImmediate }
}

@After
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,11 @@ package samples

import dispatch.android.lifecycle.*
import dispatch.core.*
import dispatch.test.*
import kotlinx.coroutines.*
import org.junit.jupiter.api.*

@CoroutineTest
@ExperimentalCoroutinesApi
class LifecycleCoroutineScopeSample(
val testScope: TestProvidedCoroutineScope
) {

@BeforeEach
fun beforeEach() {

LifecycleScopeFactory.set { testScope }
}
class LifecycleScopeSample {

@Sample
fun lifecycleCoroutineScopeSample() {
fun lifecycleScopeSample() {

// This could be any LifecycleOwner -- Fragments, Activities, Services...
class SomeFragment : Fragment() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,45 +18,84 @@ package dispatch.android.lifecycle
import androidx.lifecycle.*
import dispatch.android.lifecycle.LifecycleScopeFactory.reset
import dispatch.core.*
import kotlinx.coroutines.*
import kotlin.coroutines.*

/**
* Factory holder for [LifecycleCoroutineScope]'s.
*
* By default, `create` returns a [MainImmediateCoroutineScope].
* By default, `create` returns a [MainImmediateContext].
*
* This factory can be overridden for testing or to include a custom [CoroutineContext][kotlin.coroutines.CoroutineContext]
* in production code. This may be done in [Application.onCreate][android.app.Application.onCreate]
* in production code. This may be done in [Application.onCreate][android.app.Application.onCreate],
* or else as early as possible in the Application's existence.
*
* A custom factory will persist for the application's full lifecycle unless overwritten or [reset].
*
* [reset] may be used to reset the factory to default at any time.
*
* @see MainImmediateContext
* @see LifecycleCoroutineScope
* @see LifecycleCoroutineScopeFactory
* @sample samples.LifecycleScopeFactorySample.setLifecycleScopeFactoryProductionSample
* @sample samples.LifecycleScopeFactorySample.setLifecycleScopeFactoryEspressoSample
*/
public object LifecycleScopeFactory {
object LifecycleScopeFactory {

private val defaultFactory: LifecycleCoroutineScopeFactory
get() = LifecycleCoroutineScopeFactory { MainImmediateContext() }

private var _factory: () -> MainImmediateCoroutineScope = { MainImmediateCoroutineScope() }
private var factoryInstance = defaultFactory

internal fun create(lifecycle: Lifecycle): LifecycleCoroutineScope =
factoryInstance.create(lifecycle)

/**
* Immediately resets the factory function to its default.
*
* @sample samples.LifecycleScopeFactorySample.LifecycleScopeFactoryResetSample
*/
@Suppress("UNUSED")
public fun reset() {
factoryInstance = defaultFactory
}

/**
* Override the default [MainImmediateCoroutineScope] factory, for testing or to include a custom [CoroutineContext][kotlin.coroutines.CoroutineContext]
* in production code. This may be done in [Application.onCreate][android.app.Application.onCreate]
*
* @param factory sets a custom [MainImmediateCoroutineScope] factory to be used for all new instance creations until reset.
* @param factory sets a custom [CoroutineContext] factory to be used for all new instance creations until reset.
* Its [Elements][CoroutineContext.Element] will be re-used, except:
* 1. If a [DispatcherProvider] element isn't present, a [DefaultDispatcherProvider] will be added.
* 2. If a [Job] element isn't present, a [SupervisorJob] will be added.
* 3. If the [ContinuationInterceptor][kotlin.coroutines.ContinuationInterceptor] does not match
* the one referenced by the [possibly new] [DispatcherProvider.mainImmediate] property, it will be updated to match.
*
* @sample samples.LifecycleScopeFactorySample.setLifecycleScopeFactoryProductionSample
* @sample samples.LifecycleScopeFactorySample.setLifecycleScopeFactoryEspressoSample
*/
public fun set(factory: () -> MainImmediateCoroutineScope) {
_factory = factory
@Suppress("UNUSED")
public fun set(factory: LifecycleCoroutineScopeFactory) {
factoryInstance = factory
}

internal fun create(lifecycle: Lifecycle) = LifecycleCoroutineScope(lifecycle, _factory.invoke())

/**
* Immediately resets the factory function to its default.
* Override the default [MainImmediateCoroutineScope] factory, for testing or to include a custom [CoroutineContext][kotlin.coroutines.CoroutineContext]
* in production code. This may be done in [Application.onCreate][android.app.Application.onCreate]
*
* @sample samples.LifecycleScopeFactorySample.lifecycleScopeFactoryResetSample
* @param factory sets a custom [CoroutineContext] factory to be used for all new instance creations until reset.
* Its [Elements][CoroutineContext.Element] will be re-used, except:
* 1. If a [DispatcherProvider] element isn't present, a [DefaultDispatcherProvider] will be added.
* 2. If a [Job] element isn't present, a [SupervisorJob] will be added.
* 3. If the [ContinuationInterceptor][kotlin.coroutines.ContinuationInterceptor] does not match
* the one referenced by the [possibly new] [DispatcherProvider.mainImmediate] property, it will be updated to match.
*
* @sample samples.LifecycleScopeFactorySample.setLifecycleScopeFactoryProductionSample
* @sample samples.LifecycleScopeFactorySample.setLifecycleScopeFactoryEspressoSample
*/
public fun reset() {
_factory = { MainImmediateCoroutineScope() }
@Suppress("UNUSED")
public fun set(factory: () -> CoroutineContext) {
factoryInstance = LifecycleCoroutineScopeFactory(factory)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import kotlinx.coroutines.*
* The `viewModelScope` is automatically cancelled when the [LifecycleOwner]'s
* [lifecycle][LifecycleOwner.getLifecycle]'s [Lifecycle.State] drops to [Lifecycle.State.DESTROYED].
*
* @sample samples.LifecycleCoroutineScopeSample.lifecycleCoroutineScopeSample
* @sample samples.LifecycleScopeSample.lifecycleScopeSample
*/
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
get() = LifecycleCoroutineScopeStore.get(this.lifecycle)
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,12 @@ internal class LifecycleScopeExtensionTest : HermitJUnit5() {

val dispatcher = TestCoroutineDispatcher()

LifecycleScopeFactory.set {
TestProvidedCoroutineScope(
dispatcher,
TestDispatcherProvider(dispatcher),
Job()
)
}
LifecycleScopeFactory.set { dispatcher + TestDispatcherProvider(dispatcher) + Job() }
}

@AfterEach
fun afterEach() {
LifecycleScopeFactory.reset()
storeMap.clear()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ internal class LifecycleScopeFactoryTest : HermitJUnit5() {

@BeforeAll
fun beforeAll() {
LifecycleScopeFactory.reset()
Dispatchers.setMain(mainDispatcher)
}

Expand All @@ -59,7 +60,7 @@ internal class LifecycleScopeFactoryTest : HermitJUnit5() {
}

@Test
fun `default factory should be a default MainImmediateCoroutineScope`() = runBlockingTest {
fun `default factory should be a default MainImmediateContext`() = runBlockingTest {

val scope = LifecycleScopeFactory.create(lifecycleOwner.lifecycle)

Expand All @@ -68,14 +69,12 @@ internal class LifecycleScopeFactoryTest : HermitJUnit5() {
scope.coroutineContext[Job]!!.shouldBeSupervisorJob()

scope.coroutineContext[ContinuationInterceptor] shouldBe Dispatchers.Main

scope.shouldBeInstanceOf<MainImmediateCoroutineScope>()
}

@Test
fun `a custom factory should be used after being set`() = runBlockingTest {

LifecycleScopeFactory.set { MainImmediateCoroutineScope(originContext) }
LifecycleScopeFactory.set { originContext }

val scope = LifecycleScopeFactory.create(lifecycleOwner.lifecycle)

Expand All @@ -85,7 +84,7 @@ internal class LifecycleScopeFactoryTest : HermitJUnit5() {
@Test
fun `reset after setting a custom factory should return to the default`() = runBlockingTest {

LifecycleScopeFactory.set { MainImmediateCoroutineScope(originContext) }
LifecycleScopeFactory.set { originContext }

val custom = LifecycleScopeFactory.create(lifecycleOwner.lifecycle)

Expand Down
Loading