{"id":18124485,"url":"https://github.com/block/radiography","last_synced_at":"2026-02-20T01:06:20.156Z","repository":{"id":43709198,"uuid":"285901584","full_name":"block/radiography","owner":"block","description":"Text-ray goggles for your Android UI.","archived":false,"fork":false,"pushed_at":"2024-08-27T20:02:03.000Z","size":2744,"stargazers_count":866,"open_issues_count":18,"forks_count":42,"subscribers_count":11,"default_branch":"main","last_synced_at":"2025-04-02T05:29:24.593Z","etag":null,"topics":["android","compose","debugging","ui"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/block.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-08-07T19:01:33.000Z","updated_at":"2025-03-13T18:22:41.000Z","dependencies_parsed_at":"2022-08-12T10:42:08.328Z","dependency_job_id":"1e580432-d81a-44ac-bc00-9c675e05d12c","html_url":"https://github.com/block/radiography","commit_stats":null,"previous_names":["block/radiography","square/radiography"],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/block%2Fradiography","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/block%2Fradiography/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/block%2Fradiography/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/block%2Fradiography/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/block","download_url":"https://codeload.github.com/block/radiography/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247994119,"owners_count":21030050,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","compose","debugging","ui"],"created_at":"2024-11-01T08:02:16.162Z","updated_at":"2026-02-20T01:06:20.107Z","avatar_url":"https://github.com/block.png","language":"Kotlin","funding_links":[],"categories":["Kotlin"],"sub_categories":[],"readme":"# ![Radiography logo](assets/icon_32.png) Radiography\n\n[![Maven Central](https://img.shields.io/maven-central/v/com.squareup.radiography/radiography.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22com.squareup.radiography%22)\n[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0)\n![Android CI](https://github.com/square/radiography/workflows/Android%20CI/badge.svg)\n\nText-ray goggles for your Android UI.\n\n```\nDecorView { 1080×2160px }\n├─LinearLayout { id:main, 1080×1962px }\n│ ├─EditText { id:username, 580×124px, focused, text-length:0, ime-target }\n│ ├─EditText { id:password, 580×124px, text-length:0 }\n│ ╰─LinearLayout { 635×154px }\n│   ├─Button { id:signin, 205×132px, text-length:7 }\n│   ╰─Button { id:forgot_password, 430×132px, text-length:15 }\n├─View { id:navigationBarBackground, 1080×132px }\n╰─View { id:statusBarBackground, 1080×66px }\n```\n\n* [Usage](#usage)\n* [Result example](#result-example)\n* [Jetpack Compose support](#jetpack-compose-support)\n* [FAQ](#faq)\n* [License](#license)\n\n## Usage\n\nAdd the `radiography` dependency to your app's `build.gradle` file:\n\n```gradle\ndependencies {\n  implementation 'com.squareup.radiography:radiography:2.7'\n}\n```\n\n`Radiography.scan()` returns a pretty string rendering of the view hierarchy of all windows managed by the current process.\n\n```kotlin\n// Render the view hierarchy for all windows.\nval prettyHierarchy = Radiography.scan()\n\n// Include the text content from TextView instances.\nval prettyHierarchy = Radiography.scan(viewStateRenderers = DefaultsIncludingPii)\n\n// Append custom attribute rendering\nval prettyHierarchy = Radiography.scan(viewStateRenderers = DefaultsNoPii +\n    androidViewStateRendererFor\u003cLinearLayout\u003e {\n      append(if (it.orientation == LinearLayout.HORIZONTAL) \"horizontal\" else \"vertical\")\n    })\n```\n\nYou can print a subset of the view hierarchies by specifying a `ScanScope`. By default, Radiography\nwill scan all the windows owned by your app.\n\n```kotlin\n// Extension function on View, renders starting from that view.\nval prettyHierarchy = someView.scan()\n\n// Render only the view hierarchy from the focused window, if any.\nval prettyHierarchy = Radiography.scan(scanScope = FocusedWindowScope)\n\n// Filter out views with specific ids.\nval prettyHierarchy = Radiography.scan(viewFilter = skipIdsViewFilter(R.id.debug_drawer))\n\n// Combine view filters.\nval prettyHierarchy = Radiography.scan(\n  viewFilter = skipIdsViewFilter(R.id.debug_drawer) and MyCustomViewFilter()\n)\n```\n\n## Result example\n\n![screenshot](assets/sample_screenshot.png)\n\n```\ncom.squareup.radiography.sample/com.squareup.radiography.sample.MainActivity:\nwindow-focus:false\n DecorView { 1080×2160px }\n ├─LinearLayout { 1080×2028px }\n │ ├─ViewStub { id:action_mode_bar_stub, GONE, 0×0px }\n │ ╰─FrameLayout { id:content, 1080×1962px }\n │   ╰─LinearLayout { id:main, 1080×1962px }\n │     ├─ImageView { id:logo, 1080×352px }\n │     ├─EditText { id:username, 580×124px, text-length:0 }\n │     ├─EditText { id:password, 580×124px, text-length:0 }\n │     ├─CheckBox { id:remember_me, 343×88px, text-length:11 }\n │     ├─LinearLayout { 635×154px }\n │     │ ├─Button { id:signin, 205×132px, text-length:7 }\n │     │ ╰─Button { id:forgot_password, 430×132px, text-length:15 }\n │     ├─View { 1080×812px }\n │     ╰─Button { id:show_dialog, 601×132px, text-length:23 }\n ├─View { id:navigationBarBackground, 1080×132px }\n ╰─View { id:statusBarBackground, 1080×66px }\n```\n\nThis sample app lives in this repo in the `sample` directory.\n\n## Custom Hierarchy Exploration\n\nThe `Radiography.scan` function provides a String representation of the view hierarchy, but you can also work\nwith the raw hierarchy data directly if you want to programmatically explore it and integrate it\ninto your own custom tools.\n\nUse the `ScanScopes` object to identify the root of a hierarchy you want to explore. You can use the\nresulting `ScannableView` objects, and their `ScannableView.children`, to explore the hierarchy.\n\nFor example, to get the hierarchy starting with a specific Compose test tag in a given activity,\nyou can do this:\n```kotlin\n@OptIn(ExperimentalRadiographyComposeApi::class)\nfun Activity.findComposableRootWithTestTag(tag: String): ScannableView.ComposeView {\n   val rootView = window.decorView.findViewById\u003cViewGroup\u003e(R.id.content)\n\n   return ScanScopes.composeTestTagScope(\n      testTag = tag,\n      inScope = ScanScopes.singleViewScope(rootView)\n   ).findRoots()\n      .firstOrNull()\n      as? ScannableView.ComposeView\n      ?: error(\"No composable found with test tag $tag\")\n}\n```\n\nThe information provided by the ScannableView.ComposeView can be powerful. In particular, the\nsemantic information can be used to validate certain expectations and programmatically test your\napplication. For example:\n```kotlin\nval myComposableScreen = findComposableRootWithTestTag(\"My screen tag\")\n\nmyComposableScreen.allDescendentsDepthFirst\n  .filterIsInstance\u003cScannableView.ComposeView\u003e()\n  .onEach { composeView -\u003e\n    \n    // Validate that all images have a content description set\n    if (composeView.displayName == \"Image\") {\n      check(composeView.semanticsConfigurations.any { it.contains(SemanticsProperties.ContentDescription) })\n    }\n    \n    // Perform a click on all clickable elements to ensure none of them cause a crash\n    composeView.semanticsConfigurations\n      .map { it[SemanticsActions.OnClick] }\n      .onEach { clickAction -\u003e clickAction.action?.invoke() }\n}\n\nval ScannableView.allDescendentsDepthFirst: Sequence\u003cScannableView\u003e\n    get() = children.flatMap { sequenceOf(it) + it.allDescendentsDepthFirst }\n```\n\n## Jetpack Compose support\n\n[Jetpack Compose](https://developer.android.com/jetpack/compose) is Google's new declarative UI\ntoolkit. It is a completely new implementation, and does not use Android views itself (although it\n[interoperates](https://developer.android.com/jetpack/compose/interop#views-in-compose) with them\nseamlessly).\n\nRadiography will automatically render composables found in your view tree if the Compose Tooling\nlibrary is on the classpath. If you are using Compose, you're probably already using this library\n(the `@Preview` annotation lives in the Tooling library). On the other hand, if you're not using\nCompose, Radiography won't bloat your app with transitive dependencies on any Compose artifacts.\n\nCompose occasionally has internal implementation changes that affect Radiography. If you are using\nRadiography with an unsupported version of Compose, or you don't depend on the Tooling library, then\nRadiography will still try to detect compositions, but instead of rendering the actual hierarchy, it\nwill just include a message asking you to upgrade Radiography or add the Tooling library.\n\n### Compose usage\n\nThe only thing required for Radiography to render composables is to include the tooling library as\na dependency:\n```kotlin\ndependencies {\n  implementation(\"androidx.compose.ui:ui-tooling:1.0.0-betaXY\")\n}\n```\n\nWhen the tooling library is present, Radiography will automatically render composables. However,\nRadiography's Compose support is experimental. To use any of the compose-specific APIs, you will\nneed to opt-in using the `@OptIn(ExperimentalRadiographyComposeApi::class)` annotation.\n\n### Rendering composables\n\nThe `DefaultsNoPii` and `DefaultsIncludingPii` renderers include default Compose renderers – you\ndon't need to do anything special. Additional Compose-specific renderers can be found in the\n`ComposableRenderers` object.\n\nTo create a custom renderer for Compose, implement a `ViewStateRenderer` to handle values of type\n`ComposeView`. However, since Radiography gets most of its information about composables from their\nsemantics properties, in most cases you shouldn't need to define any custom rendering logic. \n`ComposeView` has a list of all the `Modifier`s that have been applied to the composable, including\nits [semantics](https://developer.android.com/reference/kotlin/androidx/compose/ui/semantics/SemanticsModifier?hl=en#semanticsConfiguration:androidx.compose.ui.semantics.SemanticsConfiguration).\n\n```kotlin\n// Custom modifier that tells its parent if it's a favorite child or not.\ndata class IsFavoriteChildModifier(val isFavorite: Boolean) : ParentDataModifier {\n  override fun Density.modifyParentData(parentData: Any?): Any? = this@IsFavoriteChildModifier\n}\n\n// Renderer for the above modifier.\n@OptIn(ExperimentalRadiographyComposeApi::class)\nval IsFavoriteChildRenderer = ViewStateRenderer { view -\u003e\n  val modifier = (view as? ComposeView)\n      ?.modifiers\n      ?.filterIsInstance\u003cIsFavoriteChildModifier\u003e()\n      ?.singleOrNull()\n      ?: return@ViewStateRenderer\n  append(if (modifier.isFavorite) \"FAVORITE\" else \"NOT FAVORITE\")\n}\n```\n\n### Selecting which composables to render\n\nRadiography lets you start scanning from a particular view by using the `singleViewScope`\n`ScanScope` or the `View.scan()` extension function. Compose doesn't have the concept a \"view\" as\nsomething that can be stored in a variable and passed around, but you can explicitly tag composables\nwith strings using the [`testTag`](https://developer.android.com/reference/kotlin/androidx/compose/ui/platform/package-summary#(androidx.compose.ui.Modifier).testTag(kotlin.String))\nmodifier, and then tell Radiography to only scan certain tags by passing a `composeTestTagScope`.\n\nFor example, say you have an app with some navigation controls at the top and bottom of the screen,\nbut you only want to scan the main body of the screen in between them:\n```kotlin\n@Composable fun App() {\n  Column {\n    ActionBar()\n    Body(Modifier.testTag(\"app-body\"))\n    BottomBar()\n  }\n}\n```\n\nTo start scanning from `Body`:\n```kotlin\n@OptIn(ExperimentalRadiographyComposeApi::class)\nval prettyHierarchy = Radiography.scan(scanScope = composeTestTagScope(\"app-body\"))\n```\n\nYou can also filter composables out using test tags. For example, say you have a screen that has a\ndebug drawer:\n```kotlin\n@Composable fun App() {\n  ModalDrawerLayout(drawerContent = {\n    DebugDrawer(Modifier.testTag(\"debug-drawer\"))\n  }) {\n    Scaffold(…) {\n      …\n    }\n  }\n}\n```\n\nTo exclude the debug drawer and its children from the output, use `skipComposeTestTagsFilter`:\n```kotlin\n@OptIn(ExperimentalRadiographyComposeApi::class)\nval prettyHierarchy = Radiography.scan(viewFilter = skipComposeTestTagsFilter(\"debug-drawer\"))\n```\n\nTo write a custom filter for Compose, implement a `ViewFilter` to handle values of type\n`ComposeView`. For example, a filter that excludes composables with a particular `Modifier.layoutId`\nmight look something like this:\n```kotlin\nfun skipLayoutIdsFilter(skipLayoutId: (Any) -\u003e Boolean) = ViewFilter { view -\u003e\n  (view as? ComposeView)\n      ?.modifiers\n      ?.asSequence()\n      ?.filterIsInstance\u003cLayoutIdParentData\u003e()\n      ?.none { layoutId -\u003e skipLayoutId(layoutId.id) }\n      // Include all views and composables that don't have any layoutId.\n      ?: true\n}\n```\n\n### Subcomposition scanning\n\nIn Compose, the term \"composition\" is often used to refer to a single tree of composables that all\nshare some core state (e.g. ambients) and use the same underlying slot table. A subcomposition is a\ndistinct composition that has a reference to a particular point in another composition (its parent).\nSubcompositions share the parent's ambients, but can be created at any time and disposed\nindependently of the parent composition.\n\nSubcomposition is used for a number of things:\n\n1. Compose children which have a data dependency on a property of the parent composition that is\n   only available after the composition pass, e.g. `WithConstraints` (which can only compose its\n   children during layout).\n2. Lazy composition, where the \"current\" actual children of a composable depend on some runtime\n   state, and old/uncreated children should be not be composed when not needed, to save resources.\n   `LazyColumn` does this.\n3. Linking compositions that need to be hosted in entirely separate windows together. `Dialog` uses\n   this to make the dialog children act as children of the composable that invokes them, even though\n   they're hosted in a separate window, with a Android view host.\n\nRendering subcomposition is tricky, because there's no explicit reference from the parent\n`CompositionReference` to where the subcompositions' composables actually appear. Fortunately,\n`SubcomposeLayout` is a helper composable which provides a convenient wrapper around subcomposition\nfor common use cases such as 1 and 2 above – basically any time the subcomposition is actually a\nvisual child of the parent composable, but can only be created during the layout pass.\n`SubcomposeLayout` shows up as a pattern in the slot tables' groups which Radiography detects and\nrenders in a way that makes the subcomposition look like regular children of the parent composable.\n\nNon-`SubcomposeLayout` subcompositions, like the one from `Dialog`, are rendered a bit more\nawkwardly. The subcomposition is shown as a child of the parent layout node. In the case of\n`Dialog`, this is fine, since there's no actual layout node in the parent composition which acts as\na parent for the subcomposition. More complex use cases may be rendered differently, e.g. if there's\na layout node which \"hosts\" the subcomposition, it will appear after the actual\n`CompositionReference` in the slot table, and thus the subcomposition and its subtree will appear\nbefore the layout node in the rendering.\n\nSubcompositions have their own slot table that is not shared with their parent. For this reason,\nRadiography needs to do some extra work to scan subcompositions, since they won't be processed\nsimply by reading the parent's slot table. Subcompositions are detected by looking for instances of\n[`CompositionReference`](https://developer.android.com/reference/kotlin/androidx/compose/runtime/CompositionReference)\nin the parent slot table. `CompositionReference` is an abstract class, but the only concrete\nimplementation currently in Compose contains references to all actual compositions that use it as a\nparent. Reflection is used to pull the actual subcompositions out of the parent reference, and then\nthose compositions' slot tables are analyzed in turn, and its root composables are rendered as\nchildrens of the node that owns the `CompositionReference`.\n\n### Call group collapsing\nTo simplify the hierarchy output of the Compose tree, only nodes that are \"emitted\" to the layout are\nincluded. This means that intermediate \"call\" nodes are collapsed together with the emitted node\nthat they wrap. Each emitted node inherits the display name of the top level call node wrapping it.\n\nFor example, with this Compose code:\n```kotlin\n@Composable\nfun Call3() {\n  BasicText(text = \"hello\")\n}\n\n@Composable\nfun Call2() {\n  Call3()\n}\n\n@Composable\nfun Call1() {\n  Call2()\n}\n\nColumn {\n  Call1()\n}\n```\n\nThe hierarchy output will simply look like this: `Column -\u003e Call1`.\n\nThe `BasicText` composable and its text are represented by the name `Call1`, because that is the\ncomposable function that wraps it. The `Call3` and `Call2` nodes are not shown because they don't emit a layout,\nand they are wrapped by `Call1`.\n\nThis approach helps to manage the many levels of nesting that can occur in a Compose tree. However,\nsometimes you may want to see the granular view of all the calls in the tree. To do this, you can use\nthe `ComposeView.callChain` property to see the full call chain that led to the emitted node. In this\ncase, the values of the property would look like this:\n```\n\"Call1\", \"Call2\", \"Call3\", \"BasicText\", \"Layout\", \"ReusableComposeNode\"\n```\n\nYou can access this property if you implement a custom `ViewStateRenderer` or use [Custom Hierarchy Exploration](#custom-hierarchy-exploration).\n\nFor more details on how Radiography works with Compose, see [How are compositions rendered?](#How are compositions rendered?)\n\n### Compose example output\n\n![screenshot](assets/compose_sample_screenshot.png)\n\n```\ncom.squareup.radiography.sample.compose/com.squareup.radiography.sample.compose.MainActivity:\nwindow-focus:false\n DecorView { 1080×2160px }\n ├─LinearLayout { 1080×2028px }\n │ ├─ViewStub { id:action_mode_bar_stub, GONE, 0×0px }\n │ ╰─FrameLayout { 1080×1962px }\n │   ╰─FitWindowsLinearLayout { id:action_bar_root, 1080×1962px }\n │     ├─ViewStubCompat { id:action_mode_bar_stub, GONE, 0×0px }\n │     ╰─ContentFrameLayout { id:content, 1080×1962px }\n │       ╰─AndroidComposeView { 1080×1962px, focused }\n │         ╰─Providers { 1080×1962px }\n │           ╰─ComposeSampleApp { 992×1874px }\n │             ├─Image { 240×352px }\n │             ├─TextField { 770×154px, test-tag:\"text-field\" }\n │             │ ├─Box { 200×59px, layout-id:\"Label\" }\n │             │ │ ╰─ProvideTextStyle { 200×59px, text-length:8 }\n │             │ ╰─ProvideTextStyle { 682×59px, layout-id:\"TextField\" }\n │             │   ╰─BaseTextField { 682×59px, text-length:0 }\n │             │     ╰─Layout { 682×59px }\n │             ├─TextField { 770×154px }\n │             │ ├─Box { 196×59px, layout-id:\"Label\" }\n │             │ │ ╰─ProvideTextStyle { 196×59px, text-length:8 }\n │             │ ╰─ProvideTextStyle { 682×59px, layout-id:\"TextField\" }\n │             │   ╰─BaseTextField { 682×59px, text-length:0 }\n │             │     ╰─Layout { 682×59px }\n │             ├─Row { 387×67px }\n │             │ ├─Checkbox { 55×55px, value:\"Unchecked\" }\n │             │ ├─Spacer { 22×0px }\n │             │ ╰─Text { 298×59px, text-length:11 }\n │             ├─Row { 685×99px }\n │             │ ├─TextButton { 199×99px }\n │             │ │ ╰─Providers { 155×55px }\n │             │ │   ╰─Text { 155×52px, text-length:7 }\n │             │ ╰─TextButton { 442×99px }\n │             │   ╰─Providers { 398×55px }\n │             │     ╰─Text { 398×52px, text-length:15 }\n │             ├─AndroidView {  }\n │             │ ╰─ViewBlockHolder { 919×53px }\n │             │   ╰─TextView { 919×53px, text-length:53 }\n │             ├─ScrollableRow { 1324×588px }\n │             │ ╰─ScrollableColumn { 1324×1026px }\n │             │   ╰─Text { 1324×1026px, test-tag:\"live-hierarchy\", text-length:2525 }\n │             ╰─TextButton { 737×99px }\n │               ╰─Providers { 693×55px }\n │                 ╰─Text { 693×52px, text-length:28 }\n ├─View { id:navigationBarBackground, 1080×132px }\n ╰─View { id:statusBarBackground, 1080×66px }\n```\n\nThis sample can be found in the `sample-compose` directory.\n\n## FAQ\n\n### What is Radiography useful for?\n\nRadiography is useful whenever you want to look at the view hierarchy and don't have the ability to connect the hierarchy viewer tool. You can add the view hierarchy string as metadata to crash reports, add a debug drawer button that will print it to Logcat, and use it to improve Espresso errors ([here's an example](https://twitter.com/Piwai/status/1291771701584252928)).\n\n### Is Radiography production ready?\n\nThe code that retrieves the root views is based on Espresso's [RootsOracle](https://github.com/android/android-test/blob/master/espresso/core/java/androidx/test/espresso/base/RootsOracle.java) so it's unlikely to break in newer Android versions. We've been using Radiography for crash reports in production since 2015 without any issue.\n\n### Why use custom attribute string rendering instead of View.toString() ?\n\nThe output of `View.toString()` is useful but harder to read:\n\n```\n// View.toString():\nButton { VFED..C.. ........ 0,135-652,261 #7f010001 app:id/show_dialog }\n// Radiography:\nButton { id:show_dialog, 652x126px, text-length:28 }\n```\n\nIf you'd rather rely on `View.toString()`, you can provide a custom state renderer.\n\n```kotlin\nval prettyHierarchy = Radiography.scan(viewStateRenderers = listOf(androidViewStateRendererFor\u003cView\u003e {\n  append(\n      it.toString()\n          .substringAfter(' ')\n          .substringBeforeLast('}')\n  )\n}))\n```\n\n### How are compositions rendered?\n\n_Disclaimer: Compose is changing frequently, so many of these details may change without warning,\nand none of this is required to use Radiography!_\n\nThe API for configuring how composables are rendered is slightly different than for regular views,\nsince composables simply _are not_ `View`s. What might define a UI \"component\" or \"widget\" logically\nisn't made up of any single, nicely-encapsulated object. It is likely a few layers of convenience\n`@Composable` functions, with some `Modifier`s applied at various levels, and state is stored in\nthe slot table via `remember{}`, not in instance fields.\n\nRadiography uses the Compose Tooling library to parse a composition's slot table into a tree of\nobjects which represent \"groups\" – each group can represent some data stored in the slot table,\na function call, or an emitted layout node or Android view. Groups may include source locations in\ncertain cases, and contain information about modifiers and function parameters. This is a really\npowerful API, but there's a _lot_ of data there, and much of it is not helpful for a description\nthat should be easy to read to get a general sense of the state of your UI.\n\nRadiography filters this detailed tree of groups down to only contain the actual `LayoutNode`s and\nAndroid `View`s emitted by the composition. It identifies each such node by the name of the function\ncall that is highest in the subtree below the parent emitted node. The node's modifiers are used to\nextract interesting data about the composable. Most of the interesting data is stored in a single\nmodifier, the `SemanticsModifier`. This is the modifier used to store \"semantics\" information which\nincludes accessibility descriptions, actions, and flags, and is also used for writing UI tests.\n\n## License\n\n\u003cpre\u003e\nCopyright 2020 Square Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\u003c/pre\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblock%2Fradiography","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fblock%2Fradiography","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblock%2Fradiography/lists"}