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

Introduce support for rendering Compose hierarchies. #33

Merged
merged 1 commit into from
Aug 22, 2020

Conversation

zach-klippenstein
Copy link
Collaborator

@zach-klippenstein zach-klippenstein commented Aug 12, 2020

Based on #53.

The API could probably use some improvement, but it is very low-impact and all the Compose stuff is completely invisible if you're not using Compose, and automatic if you start using it. Where the View API lets you render and filter Views, the Compose API lets you render Modifiers (including semantics, which is how Compose expresses accessibility and testing metadata), and filter by specific types of metadata: Modifier.testTag and Modifier.layoutId.

The output looks like this:


com.squareup.radiography.sample.compose/com.squareup.radiography.sample.compose.MainActivity:
window-focus:false
 DecorView { 1080×2160px }
 +-LinearLayout { 1080×2028px }
 | +-ViewStub { id:action_mode_bar_stub, GONE, 0×0px }
 | `-FrameLayout { 1080×1962px }
 |   `-FitWindowsLinearLayout { id:action_bar_root, 1080×1962px }
 |     +-ViewStubCompat { id:action_mode_bar_stub, GONE, 0×0px }
 |     `-ContentFrameLayout { id:content, 1080×1962px }
 |       `-AndroidComposeView { 1080×1230px, focused }
 |         `-Providers { 1080×1230px }
 |           `-ComposeSampleApp { 1080×1230px }
 |             +-Text { 462×52px, text-length:24, text:"The…" }
 |             +-Row { 385×67px }
 |             | +-Checkbox { 55×55px, value:"Checked" }
 |             | `-Text { 318×52px, text-length:19, text:"Che…" }
 |             +-TextField { 770×154px }
 |             | +-Box { 152×44px, layout-id:"Label" }
 |             | | `-ProvideTextStyle { 152×44px, text-length:10, text:"Tex…" }
 |             | `-ProvideTextStyle { 682×52px, layout-id:"TextField" }
 |             |   `-BaseTextField { 682×52px, text-length:13, text:"Hel…" }
 |             |     `-Layout { 682×52px }
 |             +-AndroidView {  }
 |             | `-ViewBlockHolder { 160×53px }
 |             |   `-TextView { 160×53px, text-length:9, text:"inc…" }
 |             +-Box { 659×99px, test-tag:"show-rendering" }
 |             | `-Button { 659×99px }
 |             |   `-Providers { 571×55px }
 |             |     `-Text { 571×52px, text-length:28, text:"Sho…" }
 |             `-ScrollableRow { 1222×805px, test-tag:"live-hierarchy" }
 |               `-Text { 1222×805px, text-length:1729, text:"com…" }
 +-View { id:navigationBarBackground, 1080×132px }
 `-View { id:statusBarBackground, 1080×66px }

Added a live-updating view of the hierarchy directly in the sample screen.
ezgif com-resize (2)

@zach-klippenstein zach-klippenstein force-pushed the zachklipp/compose-support branch 9 times, most recently from 1072f37 to 37a7160 Compare August 19, 2020 05:54
@zach-klippenstein zach-klippenstein force-pushed the zachklipp/compose-support branch 3 times, most recently from 3a9e450 to e41fece Compare August 21, 2020 04:41
@zach-klippenstein zach-klippenstein force-pushed the zachklipp/compose-support branch from e41fece to 51b59cc Compare August 21, 2020 05:34
@zach-klippenstein zach-klippenstein changed the base branch from main to zachklipp/compose-sample August 21, 2020 05:35
@zach-klippenstein zach-klippenstein changed the title WIP Compose support. Introduce support for rendering Compose hierarchies. Aug 21, 2020
@zach-klippenstein zach-klippenstein marked this pull request as ready for review August 21, 2020 05:40
@zach-klippenstein
Copy link
Collaborator Author

zach-klippenstein commented Aug 21, 2020

Just rewrote and simplified all the analysis code, polished up the API and documentation, and added tests. This is ready for review.

compose-tests/README.md Outdated Show resolved Hide resolved
radiography/build.gradle.kts Outdated Show resolved Hide resolved

// Note that this class can't use viewStateRendererFor, since that function is defined in the
// ViewStateRenderers object, whose class initializer may initialize _this_ class, which will cause
// NoClassDefFoundExceptions. This can happen when a debugger is attached.
Copy link
Contributor

Choose a reason for hiding this comment

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

what fresh hell is this? Are you saying that if ComposeLayoutRenderers uses a function defined in ViewStateRenderers, then when loading ViewStateRenderers the classloader may also also load ComposeLayoutRenderers? If yes, something's wrong with the compiler..

Copy link
Contributor

Choose a reason for hiding this comment

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

btw, why would initializing this class cause NoClassDefFoundExceptions ? Looks like everything here has if (!isComposeAvailable) NoRenderer

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

My response too. Doesn't happen normally, only happens when the debugger was connected and about to stop at a breakpoint that involved ViewStateRenderers.

The NoClassDef wasn't for compose, it was for this class, because of the circular ref. VSR starts initializing, asks this class for its defaults, this class starts initializing, which calls functions on VSR which require that class to be loaded, but it's already in the process of initializing.

I'm not sure why this wasn't happening unless the debugger was connected. Anyway a better solution might just be to make the VSR.Defaults* fields initialize lazily.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Tracked: #59.

SemanticsProperties.Focused -> if (value == true) append("FOCUSED")
SemanticsProperties.Hidden -> append("HIDDEN")
SemanticsProperties.IsDialog -> append("DIALOG")
SemanticsProperties.IsPopup -> append("POPUP")
Copy link
Contributor

Choose a reason for hiding this comment

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

why all the uppercase yelling :) ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Just more consistent with the output for classic views (e.g. GONE).

*/
@ExperimentalRadiographyComposeApi
@JvmSynthetic
fun SlotTable.scan(
Copy link
Contributor

Choose a reason for hiding this comment

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

what's a use case for using this public API directly? Since we're saying "you probably want scan instead", would be interesting to also mention why you'd want this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Excellent question. The only use case I had for it at all was getting rid of noise from Android views in some of the UI tests. I justified making this public because the SlotTable type is so burried in the Compose API anyway that I doubt anyone would ever even see this. And if they did, they're obviously doing something weird and low-level enough that it might be useful to scan the table directly.

Having said that out loud, the warning in the kdoc seems unnecessary.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Another solution to this problem, which would be much more useful for devs, would be the ability to start the scan at some point further down in the tree, similar to passing a non-root View to scan(). However that presents a few (solvable) issues:

  1. Composables don't have any implicit identity, so we'd probably need to do something like for filtering and use test tags or layout IDs to identify the root.
  2. The visitor infra doesn't currently support filtering nodes above, only below, so that would have to be implemented. This could also make it possible to visit Groups directly instead of using the intermediate LayoutInfo type - I'm not sure we should, since that type lets us decouple a bit from compose APIs that might change a lot, but we'd have the option.

Having thought about this more, that API makes much more sense than this one. I'll take a stab at implementing it, but would like to not block this PR on it since this API is marked as unstable anyway.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Tracked: #58.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We might eventually end up re-introducing this API, although I'm still for dropping it for now: #61

@zach-klippenstein
Copy link
Collaborator Author

zach-klippenstein commented Aug 21, 2020

I want to add at least one more thing, although I could do it as a follow-up. We should have integration tests that try use different versions of compose so we can validate that this library doesn't crash when a consumer updates their compose version without updating this library. Just because we can load one or two classes doesn't mean the rest of the classes will be what we expect, and we should degrade gracefully.

Update: Added test and guards in this PR.

@zach-klippenstein zach-klippenstein force-pushed the zachklipp/compose-support branch from 51b59cc to 1354b17 Compare August 21, 2020 17:22
@@ -0,0 +1,132 @@
package radiography.compose
Copy link
Contributor

Choose a reason for hiding this comment

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

A lot of missing copyright blocks.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We are not adding copyright blocks to new files in this repo. It's not actually required for Apache 2.0, and it's been a huge pain in the ass to keep adding them in Workflow. Anvil isn't doing this either.

Base automatically changed from zachklipp/compose-sample to main August 22, 2020 00:52
Copy link
Contributor

@rjrjr rjrjr left a comment

Choose a reason for hiding this comment

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

Can't say I read this as closely as I should, but since it's a WIP anyway…

@zach-klippenstein
Copy link
Collaborator Author

I've got two approvals, and stuff is stacking up on top of this, so I'm going to go ahead and merge. Filed issues for the comments that need follow-up.

@zach-klippenstein zach-klippenstein merged commit 7780e1c into main Aug 22, 2020
@zach-klippenstein zach-klippenstein deleted the zachklipp/compose-support branch August 22, 2020 00:56
@rjrjr
Copy link
Contributor

rjrjr commented Aug 22, 2020 via email

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.

3 participants