Skip to content

Commit

Permalink
Merge branch 'v1.9.0-beta02-release'
Browse files Browse the repository at this point in the history
* v1.9.0-beta02-release:
  Finish releasing v1.9.0-beta02
  Releasing v1.9.0-beta02
  Close dialogs even if the managing view is never attached.
  • Loading branch information
rjrjr committed Feb 23, 2023
2 parents 1483fe2 + 9d6e91b commit e9b12f4
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 11 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ android.useAndroidX=true
systemProp.org.gradle.internal.publish.checksums.insecure=true

GROUP=com.squareup.workflow1
VERSION_NAME=1.9.0-beta02-SNAPSHOT
VERSION_NAME=1.9.0-beta03-SNAPSHOT

POM_DESCRIPTION=Square Workflow

Expand Down
1 change: 1 addition & 0 deletions workflow-ui/core-android/api/core-android.api
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ public final class com/squareup/workflow1/ui/WorkflowViewStub : android/view/Vie

public final class com/squareup/workflow1/ui/androidx/WorkflowAndroidXSupport {
public static final field INSTANCE Lcom/squareup/workflow1/ui/androidx/WorkflowAndroidXSupport;
public final fun lifecycleOwnerFromContext (Landroid/content/Context;)Landroidx/lifecycle/LifecycleOwner;
public final fun lifecycleOwnerFromViewTreeOrContextOrNull (Landroid/view/View;)Landroidx/lifecycle/LifecycleOwner;
public final fun stateRegistryOwnerFromViewTreeOrContext (Landroid/view/View;)Landroidx/savedstate/SavedStateRegistryOwner;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.app.Dialog
import android.view.View
import android.widget.EditText
import androidx.activity.ComponentActivity
import androidx.lifecycle.Lifecycle.State.DESTROYED
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
Expand Down Expand Up @@ -133,4 +134,19 @@ internal class DialogIntegrationTest {
assertThat(latestDialog).isSameInstanceAs(originalDialogOne)
}
}

@Test fun finishingActivityEarlyDismissesDialogs() {
val screen = BodyAndOverlaysScreen(
ContentRendering("body"),
DialogRendering("dialog", ContentRendering("content"))
)

scenario.onActivity { activity ->
val root = WorkflowLayout(activity)
root.show(screen)
}

scenario.moveToState(DESTROYED)
assertThat(latestDialog?.isShowing).isFalse()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ import kotlin.reflect.cast
* Namespace for some helper functions for interacting with the AndroidX libraries.
*/
public object WorkflowAndroidXSupport {
/**
* Returns the [LifecycleOwner] managing [context].
*
* @throws IllegalArgumentException if [context] is unmanaged
*/
@WorkflowUiExperimentalApi
public fun lifecycleOwnerFromContext(context: Context): LifecycleOwner =
requireNotNull(context.ownerOrNull(LifecycleOwner::class)) {
"Expected $context to lead to a LifecycleOwner"
}

/**
* Tries to get the parent lifecycle from the current view via [ViewTreeLifecycleOwner], if that
* fails it looks up the context chain for a [LifecycleOwner], and if that fails it just returns
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import android.view.MotionEvent
import android.view.View
import android.view.View.OnAttachStateChangeListener
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import androidx.core.view.doOnAttach
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewTreeLifecycleOwner
import com.squareup.workflow1.ui.Compatible
import com.squareup.workflow1.ui.ViewEnvironment
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import com.squareup.workflow1.ui.androidx.WorkflowAndroidXSupport
import com.squareup.workflow1.ui.androidx.WorkflowAndroidXSupport.lifecycleOwnerFromContext
import com.squareup.workflow1.ui.androidx.WorkflowLifecycleOwner
import com.squareup.workflow1.ui.androidx.WorkflowSavedStateRegistryAggregator
import com.squareup.workflow1.ui.container.DialogSession.KeyAndBundle
Expand Down Expand Up @@ -277,11 +280,11 @@ public class LayeredDialogSessions private constructor(
): LayeredDialogSessions {
val boundsRect = Rect()
if (view.isAttachedToWindow) view.getGlobalVisibleRect(boundsRect)
val bounds = MutableStateFlow(Rect(boundsRect))
val boundsStateFlow = MutableStateFlow(Rect(boundsRect))

return LayeredDialogSessions(
context = view.context,
bounds = bounds,
bounds = boundsStateFlow,
cancelEvents = {
// Note similar code in DialogSession.

Expand All @@ -298,16 +301,36 @@ public class LayeredDialogSessions private constructor(
"Expected a ViewTreeLifecycleOwner on $view"
}
}.also { dialogs ->
fun closeAll() {
dialogs.update(emptyList(), ViewEnvironment.EMPTY) {}
}

val boundsListener = OnGlobalLayoutListener {
if (view.getGlobalVisibleRect(boundsRect) && boundsRect != bounds.value) {
bounds.value = Rect(boundsRect)
}
// Should we close the dialogs if getGlobalVisibleRect returns false?
// https://github.com/square/workflow-kotlin/issues/599
// We rely on the hosting View's WorkflowLifecycleOwner to tell us to tear things down.
// WorkflowLifecycleOwner gets hooked up when the View is attached to its window.
// But the Activity might finish before the hosting view is ever attached. And we have
// lots of time to show Dialogs before then. They will leak.
//
// To guard against that we hang a default observer directly off of the Activity that
// will close all Dialogs when it is destroyed; and we remove it as soon as the hosting
// view is attached for the first time.
val failsafe = object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) = closeAll()
}
lifecycleOwnerFromContext(view.context).lifecycle.addObserver(failsafe)
view.doOnAttach {
lifecycleOwnerFromContext(it.context).lifecycle.removeObserver(failsafe)
}

// While the hosting view is attached, monitor its bounds and report them
// through boundsStateFlow so that managed Dialogs can constrain themselves
// accordingly.
val attachStateChangeListener = object : OnAttachStateChangeListener {
val boundsListener = OnGlobalLayoutListener {
if (view.getGlobalVisibleRect(boundsRect) && boundsRect != boundsStateFlow.value) {
boundsStateFlow.value = Rect(boundsRect)
}
}

override fun onViewAttachedToWindow(v: View) {
boundsListener.onGlobalLayout()
v.viewTreeObserver.addOnGlobalLayoutListener(boundsListener)
Expand All @@ -316,9 +339,9 @@ public class LayeredDialogSessions private constructor(
override fun onViewDetachedFromWindow(v: View) {
// Don't leak the dialogs if we're suddenly yanked out of view.
// https://github.com/square/workflow-kotlin/issues/314
dialogs.update(emptyList(), ViewEnvironment.EMPTY) {}
closeAll()
v.viewTreeObserver.removeOnGlobalLayoutListener(boundsListener)
bounds.value = Rect()
boundsStateFlow.value = Rect()
}
}

Expand Down

0 comments on commit e9b12f4

Please sign in to comment.