Skip to content

Commit 2d789d1

Browse files
committed
WorkflowLayout.start now relies on Lifecycle.
Our sketchy homegrown `View.takeWhileAttached` somehow causes a strange issue on API 30 devices where `View.onAttached` can be called twice, at least since the recent change to introduce `View.start` landed in #602. We've seen crashes on a variety of Samsung devices out in the wild, and can reproduce the issue on API 30 AVDs via Android Studio's _Apply Changes and Restart Activity_. The fix is to be mainstream and use `Lifecycle`, which is added as a new required parameter to `WorkflowLayout.start`. The old overloads are now deprecated, and will be deleted soon. We also promote `WorkflowLayout.show` to be publicly visible, to give apps the option of taking control over how they collect their renderings.
1 parent 0347338 commit 2d789d1

File tree

3 files changed

+50
-5
lines changed

3 files changed

+50
-5
lines changed

samples/hello-workflow/src/main/java/com/squareup/sample/helloworkflow/HelloWorkflowActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class HelloWorkflowActivity : AppCompatActivity() {
2121
// succeeding calls.
2222
val model: HelloViewModel by viewModels()
2323
setContentView(
24-
WorkflowLayout(this).apply { start(model.renderings) }
24+
WorkflowLayout(this).also { contentView -> contentView.start(lifecycle, model.renderings) }
2525
)
2626
}
2727
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,11 @@ public abstract interface class com/squareup/workflow1/ui/ViewStarter {
203203
public final class com/squareup/workflow1/ui/WorkflowLayout : android/widget/FrameLayout {
204204
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;)V
205205
public synthetic fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
206+
public final fun start (Landroidx/lifecycle/Lifecycle;Lkotlinx/coroutines/flow/Flow;Lcom/squareup/workflow1/ui/ViewEnvironment;)V
207+
public final fun start (Landroidx/lifecycle/Lifecycle;Lkotlinx/coroutines/flow/Flow;Lcom/squareup/workflow1/ui/ViewRegistry;)V
206208
public final fun start (Lkotlinx/coroutines/flow/Flow;Lcom/squareup/workflow1/ui/ViewEnvironment;)V
207209
public final fun start (Lkotlinx/coroutines/flow/Flow;Lcom/squareup/workflow1/ui/ViewRegistry;)V
210+
public static synthetic fun start$default (Lcom/squareup/workflow1/ui/WorkflowLayout;Landroidx/lifecycle/Lifecycle;Lkotlinx/coroutines/flow/Flow;Lcom/squareup/workflow1/ui/ViewEnvironment;ILjava/lang/Object;)V
208211
public static synthetic fun start$default (Lcom/squareup/workflow1/ui/WorkflowLayout;Lkotlinx/coroutines/flow/Flow;Lcom/squareup/workflow1/ui/ViewEnvironment;ILjava/lang/Object;)V
209212
}
210213

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

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,17 @@ import android.view.View
1010
import android.view.ViewGroup
1111
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
1212
import android.widget.FrameLayout
13+
import androidx.lifecycle.Lifecycle
14+
import androidx.lifecycle.coroutineScope
1315
import kotlinx.coroutines.CoroutineScope
1416
import kotlinx.coroutines.Dispatchers
1517
import kotlinx.coroutines.Job
1618
import kotlinx.coroutines.flow.Flow
19+
import kotlinx.coroutines.flow.collect
1720
import kotlinx.coroutines.flow.launchIn
1821
import kotlinx.coroutines.flow.onEach
22+
import kotlinx.coroutines.launch
23+
import androidx.lifecycle.repeatOnLifecycle
1924

2025
/**
2126
* A view that can be driven by a stream of renderings (and an optional [ViewRegistry])
@@ -43,31 +48,68 @@ public class WorkflowLayout(
4348

4449
private var restoredChildState: SparseArray<Parcelable>? = null
4550

51+
@Suppress("DeprecatedCallableAddReplaceWith")
52+
@Deprecated("Provide an androidx Lifecyle")
53+
public fun start(
54+
renderings: Flow<Any>,
55+
registry: ViewRegistry
56+
) {
57+
@Suppress("DEPRECATION")
58+
start(renderings, ViewEnvironment(mapOf(ViewRegistry to registry)))
59+
}
60+
4661
/**
4762
* Subscribes to [renderings], and uses [registry] to
4863
* [build a new view][ViewRegistry.buildView] each time a new type of rendering is received,
4964
* making that view the only child of this one.
65+
*
66+
* @see [show]
5067
*/
5168
public fun start(
69+
lifecycle: Lifecycle,
5270
renderings: Flow<Any>,
5371
registry: ViewRegistry
5472
) {
55-
start(renderings, ViewEnvironment(mapOf(ViewRegistry to registry)))
73+
start(lifecycle, renderings, ViewEnvironment(mapOf(ViewRegistry to registry)))
74+
}
75+
76+
@Deprecated("Provide an androidx Lifecyle")
77+
public fun start(
78+
renderings: Flow<Any>,
79+
environment: ViewEnvironment = ViewEnvironment()
80+
) {
81+
takeWhileAttachedIfYouAreAnIdiotNoDoNotUseThis(renderings) { show(it, environment) }
5682
}
5783

5884
/**
5985
* Subscribes to [renderings], and uses the [ViewRegistry] in the given [environment] to
6086
* [build a new view][ViewRegistry.buildView] each time a new type of rendering is received,
6187
* making that view the only child of this one.
88+
*
89+
* @see [show]
6290
*/
6391
public fun start(
92+
lifecycle: Lifecycle,
6493
renderings: Flow<Any>,
6594
environment: ViewEnvironment = ViewEnvironment()
6695
) {
67-
takeWhileAttached(renderings) { show(it, environment) }
96+
lifecycle.coroutineScope.launch {
97+
lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
98+
renderings.collect { show(it, environment) }
99+
}
100+
}
68101
}
69102

70-
private fun show(
103+
/**
104+
* Called from [start] as each rendering is collected from its `renderings: Flow<Any>`
105+
* parameter. Updates the current child view from [newRendering] and [environment]
106+
* if [newRendering] is [compatible] with the current one, or else replaces the
107+
* current view with a new one.
108+
*
109+
* This method is exposed as an alternative to [start], to offer clients complete control
110+
* over collecting their renderings.
111+
*/
112+
public fun show(
71113
newRendering: Any,
72114
environment: ViewEnvironment
73115
) {
@@ -131,7 +173,7 @@ public class WorkflowLayout(
131173
/**
132174
* Subscribes [update] to [source] only while this [View] is attached to a window.
133175
*/
134-
private fun <S : Any> View.takeWhileAttached(
176+
private fun <S : Any> View.takeWhileAttachedIfYouAreAnIdiotNoDoNotUseThis(
135177
source: Flow<S>,
136178
update: (S) -> Unit
137179
) {

0 commit comments

Comments
 (0)