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

WorkflowLayout.start needs a Lifecycle, BackStackScreen is supported. #632

Merged
merged 1 commit into from
Jan 31, 2022

Conversation

rjrjr
Copy link
Contributor

@rjrjr rjrjr commented Jan 18, 2022

On a lot of Samsung devices running Anroid 12 / API 30, the sketchy WorkflowLayout.takeWhileAttached method at the heart of WorkflowLayout.start was having surprising effects -- we'd see redundant calls to OnAttachStateChangeListener.onViewAttachedToWindow. The problem goes away if we get more conventional and use the new repeatOnLifecycle function as described in this blog post.

The old methods are deprecated and will be deleted soon.

Making this change required making our BackPressHandler mechanism more robust, as it too was pretending that OnAttachStateChangeListener was an adequate lifecycle. Specifically, we were relying on onViewAttachedToWindow to be called in a predicable order, which stopped being the case with the move away from WorkflowLayout.takeWhileAttached. This PR changes things so that…

  • We register handlers immediately, so that we can count on them being stacked in the expected order
  • We lean on OnAttachStateChangeListener solely to enable and disable the BackPressHandler associated with a view
  • And take advantage of our recently added support for ViewTreeLifecycleOwner for clean up

ModalContainer's contribution to coping with the back button also had to be beefed up, for similar reasons. It needs to ensure that back button events are blocked by modal windows, just like any other event, which is tricky with the singleton OnBackPressedDispatcher that androidx provides. It does this by putting a no-op handler in place as a backstop for any set by modal renderings. Its bespoke code for doing this was also inadvertently relying on the order of onViewAttachedToWindow calls. We now achieve this by wrapping modal renderings with a reliable BackStackScreen, which is promoted from the sample container set.

@rjrjr rjrjr force-pushed the ray/embrace-the-viewmodel branch 3 times, most recently from 7bf2fd6 to 2d789d1 Compare January 19, 2022 22:50
@rjrjr rjrjr changed the title WorkflowLayout now relies on Lifecycle. WorkflowLayout.start now relies on Lifecycle. Jan 19, 2022
@rjrjr rjrjr changed the base branch from main to ray/upgrades January 19, 2022 23:04
@rjrjr rjrjr force-pushed the ray/upgrades branch 2 times, most recently from 1c65e56 to 1a9569b Compare January 20, 2022 00:33
rjrjr added a commit that referenced this pull request Jan 20, 2022
We make the cored update method for `WorkflowLayout` public to give apps
complete control over how exactly they collect renderings.

Doing this because the `takeWhileAttached` method used by `start` is causing
problems. Opening this up lets those impacted by these problems avoid them.
In a downstream release, we will upgrade
`androidx.lifecycle:lifecycle-viewmodel` and update the convenient `start` to
use its improved API. Details in #632.
rjrjr added a commit that referenced this pull request Jan 20, 2022
We make the cored update method for `WorkflowLayout` public to give apps
complete control over how exactly they collect renderings.

Doing this because the `takeWhileAttached` method used by `start` is causing
problems. Opening this up lets those impacted by these problems avoid them.
In a downstream release, we will upgrade
`androidx.lifecycle:lifecycle-viewmodel` and update the convenient `start` to
use its improved API. Details in #632.
rjrjr added a commit that referenced this pull request Jan 20, 2022
We make the core update method for `WorkflowLayout` public to give apps
complete control over how exactly they collect renderings.

Doing this because the `takeWhileAttached` method used by `start` is causing
problems. Opening this up lets those impacted by these problems avoid them.
In a downstream release, we will upgrade
`androidx.lifecycle:lifecycle-viewmodel` and update the convenient `start` to
use its improved API. Details in #632.
rjrjr added a commit that referenced this pull request Jan 20, 2022
We make the core `update` method for `WorkflowLayout` public to give apps
complete control over how exactly they collect renderings.

Doing this because the `takeWhileAttached` method used by `start` is causing
problems. Opening this up lets those impacted by these problems avoid them.
In a downstream release, we will upgrade
`androidx.lifecycle:lifecycle-viewmodel` and update the convenient `start` to
use its improved API. Details in #632.
@rjrjr rjrjr force-pushed the ray/upgrades branch 2 times, most recently from aec5713 to 03ab545 Compare January 25, 2022 17:44
Base automatically changed from ray/upgrades to main January 25, 2022 18:28
@rjrjr rjrjr changed the title WorkflowLayout.start now relies on Lifecycle. WorkflowLayout.start now requires a Lifecycle parameter. Jan 25, 2022
@rjrjr rjrjr marked this pull request as ready for review January 25, 2022 18:35
@rjrjr rjrjr requested review from zach-klippenstein and a team as code owners January 25, 2022 18:35
@rjrjr
Copy link
Contributor Author

rjrjr commented Jan 25, 2022

Cool, should have left this as a draft:

Starting 7 tests on Pixel_4_API_30(AVD) - 11

com.squareup.sample.TicTacToeEspressoTest > configChangePreservesBackStackViewStateCache[Pixel_4_API_30(AVD) - 11] FAILED 
        androidx.test.espresso.NoMatchingViewException: No views in hierarchy found matching: with id is <com.squareup.sample.tictacworkflow:id/login_email>
        

com.squareup.sample.TicTacToeEspressoTest > showRenderingTagStaysFresh[Pixel_4_API_30(AVD) - 11] FAILED 
        leakcanary.NoLeakAssertionFailedError: Application memory leaks were detected:
        ====================================

Thanks @pyricau!

@rjrjr rjrjr marked this pull request as draft January 25, 2022 18:54
@rjrjr
Copy link
Contributor Author

rjrjr commented Jan 25, 2022

Meh, I think the LeakCanary bit is noise: seems like the NoMatchingViewException exception from the previous test is what is leaking, nothing in the library code:

├─ androidx.test.espresso.NoMatchingViewException instance
│    Leaking: UNKNOWN
│    Retaining 219.1 kB in 3819 objects
│    ↓ NoMatchingViewException.rootView
│                              ~~~~~~~~

@rjrjr
Copy link
Contributor Author

rjrjr commented Jan 25, 2022

The real broken test is because the change somehow breaks the device back button after config changes in (at least) the Tic Tac Toe sample.

@rjrjr
Copy link
Contributor Author

rjrjr commented Jan 25, 2022

Maybe it's about breaking the back button on modals after config change. Good times.

@rjrjr rjrjr force-pushed the ray/embrace-the-viewmodel branch 3 times, most recently from 3e83946 to c2434a4 Compare January 29, 2022 01:15
@rjrjr rjrjr changed the title WorkflowLayout.start now requires a Lifecycle parameter. WorkflowLayout.start needs a Lifecycle, BackStackScreen is supported. Jan 29, 2022
@rjrjr rjrjr force-pushed the ray/embrace-the-viewmodel branch 2 times, most recently from 8b51bba to 0176bb4 Compare January 29, 2022 17:56
@rjrjr rjrjr marked this pull request as ready for review January 29, 2022 17:56
@WorkflowUiExperimentalApi
public class BackButtonScreen<W : Any>(
public val wrapped: W,
public val override: Boolean = false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: now that this is promoted can we not use a reserved keyword? Maybe overrideWrappedBackHandling? Too verbose?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about shadow?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm shadow seems vague. Maybe with good kdoc we'd be fine :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

 * @param shadow If `true`, [onBackPressed] is set as the
 * [backPressedHandler][android.view.View.backPressedHandler] after
 * the [wrapped] rendering's view is built / updated, effectively overriding it.
 * If false (the default), [onBackPressed] is set afterward, to allow the wrapped rendering to
 * take precedence if it sets a `backPressedHandler` of its own -- the handler provided
 * here serves as a default.

Comment on lines +107 to +87
lifecycle.coroutineScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
renderings.collect { update(it, environment) }
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is quite tidy and makes very clear sense!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really grateful they cleaned this up. The androidx Lifecycle stuff is flawed, but its sooo much better than everything that came before it.

On a lot of Samsung devices running Anroid 12 / API 30, the sketchy `WorkflowLayout.takeWhileAttached` method at the heart of `WorkflowLayout.start` was having surprising effects -- we'd see redundant calls to `OnAttachStateChangeListener.onViewAttachedToWindow`. The problem goes away if we get more conventional and use the new `repeatOnLifecycle` function as described in [this blog post](https://medium.com/androiddevelopers/a-safer-way-to-collect-flows-from-android-uis-23080b1f8bda).

The old methods are deprecated and will be deleted soon.

Making this change required making our `BackPressHandler` mechanism more robust, as it too was pretending that `OnAttachStateChangeListener` was an adequate lifecycle. Specifically, we were relying on `onViewAttachedToWindow` to be called in a predicable order, which stopped being the case with the move away from `WorkflowLayout.takeWhileAttached`. This PR changes things so that…

- We register handlers immediately, so that we can count on them being stacked in the expected order
- We lean on `OnAttachStateChangeListener` solely to enable and disable the `BackPressHandler` associated with a view
- And take advantage of our recently added support for `ViewTreeLifecycleOwner` for clean up

`ModalContainer`'s contribution to coping with the back button also had to be beefed up, for similar reasons. It needs to ensure that back button events are blocked by modal windows, just like any other event, which is tricky with the singleton `OnBackPressedDispatcher` that androidx provides. It does this by putting a no-op handler in place as a backstop for any set by modal renderings. Its bespoke code for doing this was also inadvertently relying on the order of `onViewAttachedToWindow` calls. We now achieve this by wrapping modal renderings with a reliable `BackStackScreen`, which is promoted from the sample container set.
@rjrjr
Copy link
Contributor Author

rjrjr commented Jan 31, 2022

Update addresses all of @steve-the-edwards nits but one -- named params on the various start() calls seem soooo redundant.

Copy link
Contributor

@steve-the-edwards steve-the-edwards left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! LGTM

@rjrjr rjrjr merged commit 61b94d1 into main Jan 31, 2022
@rjrjr rjrjr deleted the ray/embrace-the-viewmodel branch January 31, 2022 19:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants