Skip to content

Commit dcfefc8

Browse files
authored
Merge pull request #1219 from square/ray/return-job
Return the `Job` from `WorkflowLayout.take` so it can be canceled.
2 parents 7316b0d + cd4b848 commit dcfefc8

File tree

3 files changed

+62
-5
lines changed

3 files changed

+62
-5
lines changed

workflow-ui/core-android/api/core-android.api

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,8 @@ public final class com/squareup/workflow1/ui/WorkflowLayout : android/widget/Fra
128128
public synthetic fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
129129
public final fun show (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;)V
130130
public static synthetic fun show$default (Lcom/squareup/workflow1/ui/WorkflowLayout;Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;ILjava/lang/Object;)V
131-
public final fun take (Landroidx/lifecycle/Lifecycle;Lkotlinx/coroutines/flow/Flow;Landroidx/lifecycle/Lifecycle$State;Lkotlin/coroutines/CoroutineContext;)V
132-
public static synthetic fun take$default (Lcom/squareup/workflow1/ui/WorkflowLayout;Landroidx/lifecycle/Lifecycle;Lkotlinx/coroutines/flow/Flow;Landroidx/lifecycle/Lifecycle$State;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)V
131+
public final fun take (Landroidx/lifecycle/Lifecycle;Lkotlinx/coroutines/flow/Flow;Landroidx/lifecycle/Lifecycle$State;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/Job;
132+
public static synthetic fun take$default (Lcom/squareup/workflow1/ui/WorkflowLayout;Landroidx/lifecycle/Lifecycle;Lkotlinx/coroutines/flow/Flow;Landroidx/lifecycle/Lifecycle$State;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lkotlinx/coroutines/Job;
133133
}
134134

135135
public final class com/squareup/workflow1/ui/WorkflowViewStub : android/view/View {

workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowLayout.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import androidx.lifecycle.repeatOnLifecycle
1717
import com.squareup.workflow1.ui.androidx.OnBackPressedDispatcherOwnerKey
1818
import com.squareup.workflow1.ui.androidx.WorkflowAndroidXSupport.onBackPressedDispatcherOwnerOrNull
1919
import kotlinx.coroutines.CoroutineDispatcher
20+
import kotlinx.coroutines.Job
2021
import kotlinx.coroutines.flow.Flow
2122
import kotlinx.coroutines.launch
2223
import kotlin.coroutines.CoroutineContext
@@ -91,19 +92,24 @@ public class WorkflowLayout(
9192
* @param [collectionContext] additional [CoroutineContext] we want for the coroutine that is
9293
* launched to collect the renderings. This should not override the [CoroutineDispatcher][kotlinx.coroutines.CoroutineDispatcher]
9394
* but may include some other instrumentation elements.
95+
*
96+
* @return the [Job] started to collect [renderings], to give callers the option to
97+
* [cancel][Job.cancel] collection -- e.g., before calling [take] again with a new
98+
* [renderings] flow. In most cases the caller can ignore this, interacting with
99+
* the [Job] is very unusual.
94100
*/
95101
@OptIn(ExperimentalStdlibApi::class)
96102
public fun take(
97103
lifecycle: Lifecycle,
98104
renderings: Flow<Screen>,
99105
repeatOnLifecycle: State = STARTED,
100106
collectionContext: CoroutineContext = EmptyCoroutineContext
101-
) {
107+
): Job {
102108
// We remove the dispatcher as we want to use what is provided by the lifecycle.coroutineScope.
103109
val contextWithoutDispatcher = collectionContext.minusKey(CoroutineDispatcher.Key)
104110
val lifecycleDispatcher = lifecycle.coroutineScope.coroutineContext[CoroutineDispatcher.Key]
105111
// Just like https://medium.com/androiddevelopers/a-safer-way-to-collect-flows-from-android-uis-23080b1f8bda
106-
lifecycle.coroutineScope.launch(contextWithoutDispatcher) {
112+
return lifecycle.coroutineScope.launch(contextWithoutDispatcher) {
107113
lifecycle.repeatOnLifecycle(repeatOnLifecycle) {
108114
require(coroutineContext[CoroutineDispatcher.Key] == lifecycleDispatcher) {
109115
"Collection dispatch should happen on the lifecycle's dispatcher."

workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/WorkflowLayoutTest.kt

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,21 @@ import android.os.Bundle
55
import android.os.Parcelable
66
import android.util.SparseArray
77
import android.view.View
8+
import androidx.activity.OnBackPressedDispatcher
89
import androidx.activity.OnBackPressedDispatcherOwner
10+
import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
11+
import androidx.core.view.get
912
import androidx.lifecycle.Lifecycle
1013
import androidx.lifecycle.testing.TestLifecycleOwner
1114
import androidx.test.core.app.ApplicationProvider
1215
import com.google.common.truth.Truth.assertThat
1316
import com.squareup.workflow1.ui.androidx.OnBackPressedDispatcherOwnerKey
1417
import com.squareup.workflow1.ui.navigation.WrappedScreen
1518
import kotlinx.coroutines.ExperimentalCoroutinesApi
19+
import kotlinx.coroutines.flow.MutableSharedFlow
1620
import kotlinx.coroutines.flow.flowOf
1721
import kotlinx.coroutines.test.UnconfinedTestDispatcher
22+
import kotlinx.coroutines.test.runTest
1823
import org.junit.Test
1924
import org.junit.runner.RunWith
2025
import org.robolectric.RobolectricTestRunner
@@ -28,7 +33,15 @@ import kotlin.coroutines.CoroutineContext
2833
internal class WorkflowLayoutTest {
2934
private val context: Context = ApplicationProvider.getApplicationContext()
3035

31-
private val workflowLayout = WorkflowLayout(context).apply { id = 42 }
36+
private val workflowLayout = WorkflowLayout(context).apply {
37+
id = 42
38+
setViewTreeOnBackPressedDispatcherOwner(object : OnBackPressedDispatcherOwner {
39+
override fun getOnBackPressedDispatcher(): OnBackPressedDispatcher {
40+
error("yeah no")
41+
}
42+
override val lifecycle: Lifecycle get() = error("nope")
43+
})
44+
}
3245

3346
@Test fun ignoresAlienViewState() {
3447
val weirdView = BundleSavingView(context)
@@ -91,6 +104,44 @@ internal class WorkflowLayoutTest {
91104
// No crash then we safely removed the dispatcher.
92105
}
93106

107+
@Test fun takes() {
108+
val lifecycleDispatcher = UnconfinedTestDispatcher()
109+
val testLifecycle = TestLifecycleOwner(
110+
initialState = Lifecycle.State.RESUMED,
111+
coroutineDispatcher = lifecycleDispatcher
112+
)
113+
val flow = MutableSharedFlow<Screen>()
114+
115+
runTest(lifecycleDispatcher) {
116+
workflowLayout.take(
117+
lifecycle = testLifecycle.lifecycle,
118+
renderings = flow,
119+
)
120+
assertThat(workflowLayout[0]).isInstanceOf(WorkflowViewStub::class.java)
121+
flow.emit(WrappedScreen())
122+
assertThat(workflowLayout[0]).isNotInstanceOf(WorkflowViewStub::class.java)
123+
}
124+
}
125+
126+
@Test fun canStopTaking() {
127+
val lifecycleDispatcher = UnconfinedTestDispatcher()
128+
val testLifecycle = TestLifecycleOwner(
129+
initialState = Lifecycle.State.RESUMED,
130+
coroutineDispatcher = lifecycleDispatcher
131+
)
132+
val flow = MutableSharedFlow<Screen>()
133+
134+
runTest(lifecycleDispatcher) {
135+
val job = workflowLayout.take(
136+
lifecycle = testLifecycle.lifecycle,
137+
renderings = flow,
138+
)
139+
job.cancel()
140+
flow.emit(WrappedScreen())
141+
assertThat(workflowLayout[0]).isInstanceOf(WorkflowViewStub::class.java)
142+
}
143+
}
144+
94145
private class BundleSavingView(context: Context) : View(context) {
95146
var saved = false
96147

0 commit comments

Comments
 (0)