Text-ray goggles for your Android UI.

Overview

Radiography logo Radiography

Maven Central GitHub license Android CI

Text-ray goggles for your Android UI.

DecorView { 1080×2160px }
├─LinearLayout { id:main, 1080×1962px }
│ ├─EditText { id:username, 580×124px, focused, text-length:0, ime-target }
│ ├─EditText { id:password, 580×124px, text-length:0 }
│ ╰─LinearLayout { 635×154px }
│   ├─Button { id:signin, 205×132px, text-length:7 }
│   ╰─Button { id:forgot_password, 430×132px, text-length:15 }
├─View { id:navigationBarBackground, 1080×132px }
╰─View { id:statusBarBackground, 1080×66px }

Usage

Add the radiography dependency to your app's build.gradle file:

dependencies {
  implementation 'com.squareup.radiography:radiography:2.4.1'
}

Radiography.scan() returns a pretty string rendering of the view hierarchy of all windows managed by the current process.

// Render the view hierarchy for all windows.
val prettyHierarchy = Radiography.scan()

// Include the text content from TextView instances.
val prettyHierarchy = Radiography.scan(viewStateRenderers = DefaultsIncludingPii)

// Append custom attribute rendering
val prettyHierarchy = Radiography.scan(viewStateRenderers = DefaultsNoPii +
    androidViewStateRendererFor<LinearLayout> {
      append(if (it.orientation == LinearLayout.HORIZONTAL) "horizontal" else "vertical")
    })

You can print a subset of the view hierarchies by specifying a ScanScope. By default, Radiography will scan all the windows owned by your app.

// Extension function on View, renders starting from that view.
val prettyHierarchy = someView.scan()

// Render only the view hierarchy from the focused window, if any.
val prettyHierarchy = Radiography.scan(scanScope = FocusedWindowScanScope)

// Filter out views with specific ids.
val prettyHierarchy = Radiography.scan(viewFilter = skipIdsViewFilter(R.id.debug_drawer))

// Combine view filters.
val prettyHierarchy = Radiography.scan(
  viewFilter = skipIdsViewFilter(R.id.debug_drawer) and MyCustomViewFilter()
)

Result example

screenshot

com.squareup.radiography.sample/com.squareup.radiography.sample.MainActivity:
window-focus:false
 DecorView { 1080×2160px }
 ├─LinearLayout { 1080×2028px }
 │ ├─ViewStub { id:action_mode_bar_stub, GONE, 0×0px }
 │ ╰─FrameLayout { id:content, 1080×1962px }
 │   ╰─LinearLayout { id:main, 1080×1962px }
 │     ├─ImageView { id:logo, 1080×352px }
 │     ├─EditText { id:username, 580×124px, text-length:0 }
 │     ├─EditText { id:password, 580×124px, text-length:0 }
 │     ├─CheckBox { id:remember_me, 343×88px, text-length:11 }
 │     ├─LinearLayout { 635×154px }
 │     │ ├─Button { id:signin, 205×132px, text-length:7 }
 │     │ ╰─Button { id:forgot_password, 430×132px, text-length:15 }
 │     ├─View { 1080×812px }
 │     ╰─Button { id:show_dialog, 601×132px, text-length:23 }
 ├─View { id:navigationBarBackground, 1080×132px }
 ╰─View { id:statusBarBackground, 1080×66px }

This sample app lives in this repo in the sample directory.

Jetpack Compose support

Jetpack Compose is Google's new declarative UI toolkit. It is a completely new implementation, and does not use Android views itself (although it interoperates with them seamlessly).

Radiography will automatically render composables found in your view tree if the Compose Tooling library is on the classpath. If you are using Compose, you're probably already using this library (the @Preview annotation lives in the Tooling library). On the other hand, if you're not using Compose, Radiography won't bloat your app with transitive dependencies on any Compose artifacts.

Compose changes frequently, and while in beta, is being released every two weeks. If you are using Radiography with an unsupported version of Compose, or you don't depend on the Tooling library, then Radiography will still try to detect compositions, but instead of rendering the actual hierarchy, it will just include a message asking you to upgrade Radiography or add the Tooling library.

Compose usage

The only thing required for Radiography to render composables is to include the tooling library as a dependency:

dependencies {
  implementation("androidx.compose.ui:ui-tooling:1.0.0-betaXY")
}

When the tooling library is present, Radiography will automatically render composables. However, Radiography's Compose support is experimental. To use any of the compose-specific APIs, you will need to opt-in using the @OptIn(ExperimentalRadiographyComposeApi::class) annotation.

Rendering composables

The DefaultsNoPii and DefaultsIncludingPii renderers include default Compose renderers – you don't need to do anything special. Additional Compose-specific renderers can be found in the ComposableRenderers object.

To create a custom renderer for Compose, implement a ViewStateRenderer to handle values of type ComposeView. However, since Radiography gets most of its information about composables from their semantics properties, in most cases you shouldn't need to define any custom rendering logic. ComposeView has a list of all the Modifiers that have been applied to the composable, including its semantics.

// Custom modifier that tells its parent if it's a favorite child or not.
data class IsFavoriteChildModifier(val isFavorite: Boolean) : ParentDataModifier {
  override fun Density.modifyParentData(parentData: Any?): Any? = this@IsFavoriteChildModifier
}

// Renderer for the above modifier.
@OptIn(ExperimentalRadiographyComposeApi::class)
val IsFavoriteChildRenderer = ViewStateRenderer { view ->
  val modifier = (view as? ComposeView)
      ?.modifiers
      ?.filterIsInstance<IsFavoriteChildModifier>()
      ?.singleOrNull()
      ?: return@ViewStateRenderer
  append(if (modifier.isFavorite) "FAVORITE" else "NOT FAVORITE")
}

Selecting which composables to render

Radiography lets you start scanning from a particular view by using the singleViewScope ScanScope or the View.scan() extension function. Compose doesn't have the concept a "view" as something that can be stored in a variable and passed around, but you can explicitly tag composables with strings using the testTag modifier, and then tell Radiography to only scan certain tags by passing a composeTestTagScope.

For example, say you have an app with some navigation controls at the top and bottom of the screen, but you only want to scan the main body of the screen in between them:

@Composable fun App() {
  Column {
    ActionBar()
    Body(Modifier.testTag("app-body"))
    BottomBar()
  }
}

To start scanning from Body:

@OptIn(ExperimentalRadiographyComposeApi::class)
val prettyHierarchy = Radiography.scan(scanScope = composeTestTagScope("app-body"))

You can also filter composables out using test tags. For example, say you have a screen that has a debug drawer:

@Composable fun App() {
  ModalDrawerLayout(drawerContent = {
    DebugDrawer(Modifier.testTag("debug-drawer"))
  }) {
    Scaffold(…) {
      …
    }
  }
}

To exclude the debug drawer and its children from the output, use skipComposeTestTagsFilter:

@OptIn(ExperimentalRadiographyComposeApi::class)
val prettyHierarchy = Radiography.scan(viewFilter = skipComposeTestTagsFilter("debug-drawer"))

To write a custom filter for Compose, implement a ViewFilter to handle values of type ComposeView. For example, a filter that excludes composables with a particular Modifier.layoutId might look something like this:

fun skipLayoutIdsFilter(skipLayoutId: (Any) -> Boolean) = ViewFilter { view ->
  (view as? ComposeView)
      ?.modifiers
      ?.asSequence()
      ?.filterIsInstance<LayoutIdParentData>()
      ?.none { layoutId -> skipLayoutId(layoutId.id) }
      // Include all views and composables that don't have any layoutId.
      ?: true
}

Subcomposition scanning

In Compose, the term "composition" is often used to refer to a single tree of composables that all share some core state (e.g. ambients) and use the same underlying slot table. A subcomposition is a distinct composition that has a reference to a particular point in another composition (its parent). Subcompositions share the parent's ambients, but can be created at any time and disposed independently of the parent composition.

Subcomposition is used for a number of things:

  1. Compose children which have a data dependency on a property of the parent composition that is only available after the composition pass, e.g. WithConstraints (which can only compose its children during layout).
  2. Lazy composition, where the "current" actual children of a composable depend on some runtime state, and old/uncreated children should be not be composed when not needed, to save resources. LazyColumn does this.
  3. Linking compositions that need to be hosted in entirely separate windows together. Dialog uses this to make the dialog children act as children of the composable that invokes them, even though they're hosted in a separate window, with a Android view host.

Rendering subcomposition is tricky, because there's no explicit reference from the parent CompositionReference to where the subcompositions' composables actually appear. Fortunately, SubcomposeLayout is a helper composable which provides a convenient wrapper around subcomposition for common use cases such as 1 and 2 above – basically any time the subcomposition is actually a visual child of the parent composable, but can only be created during the layout pass. SubcomposeLayout shows up as a pattern in the slot tables' groups which Radiography detects and renders in a way that makes the subcomposition look like regular children of the parent composable.

Non-SubcomposeLayout subcompositions, like the one from Dialog, are rendered a bit more awkwardly. The subcomposition is shown as a child of the parent layout node. In the case of Dialog, this is fine, since there's no actual layout node in the parent composition which acts as a parent for the subcomposition. More complex use cases may be rendered differently, e.g. if there's a layout node which "hosts" the subcomposition, it will appear after the actual CompositionReference in the slot table, and thus the subcomposition and its subtree will appear before the layout node in the rendering.

Subcompositions have their own slot table that is not shared with their parent. For this reason, Radiography needs to do some extra work to scan subcompositions, since they won't be processed simply by reading the parent's slot table. Subcompositions are detected by looking for instances of CompositionReference in the parent slot table. CompositionReference is an abstract class, but the only concrete implementation currently in Compose contains references to all actual compositions that use it as a parent. Reflection is used to pull the actual subcompositions out of the parent reference, and then those compositions' slot tables are analyzed in turn, and its root composables are rendered as childrens of the node that owns the CompositionReference.

Compose example output

screenshot

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×1962px, focused }
 │         ╰─Providers { 1080×1962px }
 │           ╰─ComposeSampleApp { 992×1874px }
 │             ├─Image { 240×352px }
 │             ├─TextField { 770×154px, test-tag:"text-field" }
 │             │ ├─Box { 200×59px, layout-id:"Label" }
 │             │ │ ╰─ProvideTextStyle { 200×59px, text-length:8 }
 │             │ ╰─ProvideTextStyle { 682×59px, layout-id:"TextField" }
 │             │   ╰─BaseTextField { 682×59px, text-length:0 }
 │             │     ╰─Layout { 682×59px }
 │             ├─TextField { 770×154px }
 │             │ ├─Box { 196×59px, layout-id:"Label" }
 │             │ │ ╰─ProvideTextStyle { 196×59px, text-length:8 }
 │             │ ╰─ProvideTextStyle { 682×59px, layout-id:"TextField" }
 │             │   ╰─BaseTextField { 682×59px, text-length:0 }
 │             │     ╰─Layout { 682×59px }
 │             ├─Row { 387×67px }
 │             │ ├─Checkbox { 55×55px, value:"Unchecked" }
 │             │ ├─Spacer { 22×0px }
 │             │ ╰─Text { 298×59px, text-length:11 }
 │             ├─Row { 685×99px }
 │             │ ├─TextButton { 199×99px }
 │             │ │ ╰─Providers { 155×55px }
 │             │ │   ╰─Text { 155×52px, text-length:7 }
 │             │ ╰─TextButton { 442×99px }
 │             │   ╰─Providers { 398×55px }
 │             │     ╰─Text { 398×52px, text-length:15 }
 │             ├─AndroidView {  }
 │             │ ╰─ViewBlockHolder { 919×53px }
 │             │   ╰─TextView { 919×53px, text-length:53 }
 │             ├─ScrollableRow { 1324×588px }
 │             │ ╰─ScrollableColumn { 1324×1026px }
 │             │   ╰─Text { 1324×1026px, test-tag:"live-hierarchy", text-length:2525 }
 │             ╰─TextButton { 737×99px }
 │               ╰─Providers { 693×55px }
 │                 ╰─Text { 693×52px, text-length:28 }
 ├─View { id:navigationBarBackground, 1080×132px }
 ╰─View { id:statusBarBackground, 1080×66px }

This sample can be found in the sample-compose directory.

FAQ

What is Radiography useful for?

Radiography 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).

Is Radiography production ready?

The code that retrieves the root views is based on Espresso's RootsOracle 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.

Why use custom attribute string rendering instead of View.toString() ?

The output of View.toString() is useful but harder to read:

// View.toString():
Button { VFED..C.. ........ 0,135-652,261 #7f010001 app:id/show_dialog }
// Radiography:
Button { id:show_dialog, 652x126px, text-length:28 }

If you'd rather rely on View.toString(), you can provide a custom state renderer.

val prettyHierarchy = Radiography.scan(viewStateRenderers = listOf(androidViewStateRendererFor<View> {
  append(
      it.toString()
          .substringAfter(' ')
          .substringBeforeLast('}')
  )
}))

How are compositions rendered?

Disclaimer: Compose is changing frequently, so many of these details may change without warning, and none of this is required to use Radiography!

The API for configuring how composables are rendered is slightly different than for regular views, since composables simply are not Views. What might define a UI "component" or "widget" logically isn't made up of any single, nicely-encapsulated object. It is likely a few layers of convenience @Composable functions, with some Modifiers applied at various levels, and state is stored in the slot table via remember{}, not in instance fields.

Radiography uses the Compose Tooling library to parse a composition's slot table into a tree of objects which represent "groups" – each group can represent some data stored in the slot table, a function call, or an emitted layout node or Android view. Groups may include source locations in certain cases, and contain information about modifiers and function parameters. This is a really powerful API, but there's a lot of data there, and much of it is not helpful for a description that should be easy to read to get a general sense of the state of your UI.

Radiography filters this detailed tree of groups down to only contain the actual LayoutNodes and Android Views emitted by the composition. It identifies each such node by the name of the function call that is highest in the subtree below the parent emitted node. The node's modifiers are used to extract interesting data about the composable. Most of the interesting data is stored in a single modifier, the SemanticsModifier. This is the modifier used to store "semantics" information which includes accessibility descriptions, actions, and flags, and is also used for writing UI tests.

License

Copyright 2020 Square Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Comments
  • Version 2.0.0 is crashing

    Version 2.0.0 is crashing

    We've had a lot of crashes coming from this library on Android 11 and vivo devices:

    Caused by: java.lang.NoSuchFieldException: No field mKeyedTags in class Landroid/view/View; (declaration of 'android.view.View' appears in /system/framework/framework.jar!classes3.dex)
    	at java.lang.Class.getDeclaredField(Class.java)
    	at radiography.internal.ComposeViewsKt.<clinit>(ComposeViewsKt.java:14)
    	at radiography.internal.ComposeViewsKt.isComposeAvailable(ComposeViewsKt.java:0)
    	at radiography.ViewStateRenderers.<clinit>(ViewStateRenderers.java:58)
    

    The offending usage is:

    ImmutableList<ViewStateRenderer> renderers =
        ImmutableList.of(
            ViewStateRenderers.ViewRenderer,
            ViewStateRenderers.textViewRenderer(true, 128),
            ViewStateRenderers.CheckableRenderer);
    

    Can you please advise how to handle this?

    opened by yoavgray 7
  • Cleanup some renderers.

    Cleanup some renderers.

    Based on #70.

    • Makes the standard text value renderer also render compose text, so there's only one place to configure text rendering.
    • Merges LayoutIdRenderer into ComposeViewRenderer, since layoutId can be on any Composable and there's no reason to have it separate.
    • Renames the parameters to textViewRenderer to be more concise.

    Fixes #62.

    opened by zach-klippenstein 4
  • Introduce support for rendering Compose hierarchies.

    Introduce support for rendering Compose hierarchies.

    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)

    opened by zach-klippenstein 4
  • Enable emulator for API 30 in CI

    Enable emulator for API 30 in CI

    After the updates to the android-emulator-runner to run on 2 cores. It looks like the tests are running well on API 30: https://github.com/AfzalivE/radiography/pull/1/checks?check_run_id=2040687033

    API 23 timed out in that run but 30 went well.

    opened by AfzalivE 3
  • Two radiography core UI tests seem to be broken on API 30

    Two radiography core UI tests seem to be broken on API 30

    These are failing on main, as well as on the last two PRs. Doesn't seem to be related to a code change.

    radiography.test.RadiographyUiTest > when_showDialog_then_hierarchyHasTwoWindows[test(AVD) - 11] FAILED 
    	expected to be true
    	at radiography.test.RadiographyUiTest.waitForFocus(RadiographyUiTest.kt:142)
    
    radiography.test.RadiographyUiTest > when_onlyFocusedWindow_then_hierarchyHasOnlyDialog[test(AVD) - 11] FAILED 
    	expected to be true
    	at radiography.test.RadiographyUiTest.waitForFocus(RadiographyUiTest.kt:142)
    
    
    opened by zach-klippenstein 3
  • Remove the skipLayoutIdsFilter.

    Remove the skipLayoutIdsFilter.

    I think testTag is better-suited for this purpose, layoutId is really only meant for communicating with a particular custom parent layout. But I used the implementation for a new example in the README of how to write a custom filter.

    opened by zach-klippenstein 3
  • Upgrade stdlib to Kotlin 1.4

    Upgrade stdlib to Kotlin 1.4

    This was initially done in #45, but then was reverted in #51 to support older consumers for now. We will probably upgrade to 1.4 after the 2.0.0 release.

    opened by zach-klippenstein 3
  • Restrict stdlib usage to 1.3.

    Restrict stdlib usage to 1.3.

    This reverts the changes to stdlib call sites made in 0a0f4d266537f8bdc78fa94cc4f9166a790d2376.

    This change ensures the library is still consumable from projects built with kotlin 1.3.

    Some discussion on Kotlin Slack about this, using Kt 1.4 with apiVersion = "1.3" isn't enough, and using Kt 1.3 setting languageLevel = "1.4" won't work either.

    opened by zach-klippenstein 3
  • Misleading subtitle (!?): Text-ray goggles for your Android views.

    Misleading subtitle (!?): Text-ray goggles for your Android views.

    It is said in README that the project has Jetpack Compose support as well. But the subtitle limits the scope to Android View. It gives wrong impression that it only works with the View system of Android.

    opened by donenoyes 2
  • WIP Add support for only rendering certain sub-trees.

    WIP Add support for only rendering certain sub-trees.

    This change allows filters to be written which not only exclude certain subtrees, but also which only include certain subtrees.

    It has three parts:

    1. Change ViewFilter's matches method to return a three-value type instead of a boolean, where the third value means don't include this view, but include its children.
    2. Give ComposeLayoutInfo a pointer to its parent.
    3. Write a filter for compose that returns "include only children" for all nodes that don't have a parent which matches a certain test tag.

    Open questions

    • [ ] Is the new ViewFilter API acceptable?
    • [ ] Should viewFilterFor still accept a boolean lambda instead of all 3 values, since the third value is likely not needed by most filters and it would allow more readable single-expression filters.
    • [ ] Is searching up the tree on every node an acceptable cost?
    • [ ] This approach requires the filter to be aware of all types that are visitable - a composable nested in an Android view nested inside a composable would also need to walk up the inner View hierarchy so that it included the complete subtrees. Would a better approach to make the filter stateful, so it could set an internal flag while visiting children that match? This would also be more efficient, since it wouldn't need to walk up the parent chain for every node. However it requires a slightly bigger change to the filter API.

    One way to solve the last point would be to pass an arbitrary value down the filter chain:

    fun interface ViewFilter<S> {
      fun matches(view: Any, filterState: S): Pair<FilterResult, S>
    }
    
    fun startFromTestTag(testTag: String): ViewFilter =
      viewFilterFor<ComposeLayoutInfo> { layoutInfo, alreadyMatched ->
        if (alreadyMatched || (testTag in testTags)) {
          Pair(INCLUDE, true)
        } else {
          Pair(INCLUDE_ONLY_CHILDREN, false)
        }
      }
    

    We could also then provide two versions of viewFilterFor, a simple one (no filter state, boolean return) and a complete one (same signature as the above method). One issue with this is that the returned filter state value would be ignored if the result is EXCLUDE, so a sealed class might be better for the result than an enum at that point:

    sealed class FilterResult<out S> {
      class Include<S>(val filterState: S) : FilterResult<S>()
      class IncludeOnlyChildren<S>(val filterState: S) : FilterResult<S>()
      object Exclude : FilterResult<Nothing>()
    }
    

    Fixes #58.

    opened by zach-klippenstein 2
  • Fix ViewFilters' behavior for non-View nodes.

    Fix ViewFilters' behavior for non-View nodes.

    They should "match" non-View nodes, and only filter when the node is actually a View. This behavior is never exercised right now, but would cause any of the existing filters to filter out all compose nodes when compose support is added.

    opened by zach-klippenstein 2
  • Move threading concerns to API surface

    Move threading concerns to API surface

    The scanning implementation is making decisions on threading that aren't optimal:

    1. It scans for view roots from the calling thread
    2. But then it always performs the scanning of each hierarchy from the main thread (but only if they're Android views). There's one post to the main thread per Android root view and each gets 5 seconds for the traversal, so if the main thread is blocked and we have N windows we could wait N * 5 seconds.

    This change moves these decisions around threading behavior to the callers, with a default provided configuration that matches the previous behavior, except there is a single post and the root scanning is done as part of it.

    opened by pyricau 4
  • Filtering isn't working in the sample app

    Filtering isn't working in the sample app

    Both the README and sample app (XML layouts) says the Filter should use it to match the view:

    viewFilter = { it !is LinearLayout }

    Or this doesn't work, it being an AndroidView. it.view must be used instead:

    viewFilter = { (it as ScannableView.AndroidView).view !is LinearLayout }

    opened by st-f 0
  • Snapshot testing

    Snapshot testing

    Hello,

    I was wondering if this lib could eventually be used for something very similar to Snapshot testing?
    Very similar to Shot, but instead of screenshots with a textual output?

    Feel free to close if this is completely out of scope.

    Thanks in advance

    opened by timrijckaert 1
  • Consider showing InspectorInfo in the default output of compose nodes

    Consider showing InspectorInfo in the default output of compose nodes

    Many compose Modifiers explicitly describe themselves and their parameters in a human-readable way for the AndroidStudio LayoutInspector via InspectorInfo. This would probably be useful data to show in Radiography outputs.

    opened by zach-klippenstein 0
  • Introduce SlotTableInspector tool for Compose.

    Introduce SlotTableInspector tool for Compose.

    This introduces a new module, slot-table-inspector, which provides two composable functions for peeking under the Compose hood to look at the slot table. They can be used like this:

    @Composable fun App() {
      val inspectorState = remember { SlotTableInspectorState() }
      var showInspector by remember { mutableStateOf(false) }
    
      // This defines the content to inspect (roughly).
      SlotTableInspectable(inspectorState) {
        Column {
          Button(onClick = {
            inspectorState.captureSlotTables()
            showInspector = true
          }) {
            Text("Inspect")
          }
        }
      }
    
      if (showInspector) {
        Dialog {
          // This reads the slot tables from all SlotTableInspectables and displays them.
          SlotTableInspector(inspectorState)
        }
      }
    }
    

    This change also adds it into the sample app. To play with it, just check out this branch and run the Compose sample app! (It works best on a tablet.)

    https://user-images.githubusercontent.com/101754/129277048-52f46ffb-ca3b-403e-8546-3dbae1764aa3.mp4

    https://user-images.githubusercontent.com/101754/129277637-115bb98e-1922-492c-ad8b-682d1bb643c3.mp4

    TODO

    • [ ] Write some UI tests.
    • [ ] Better name for the module/artifact than slot-table-inspector?
    • [ ] Naming of composable functions?
    • [ ] Package of stuff in new module?
    • [ ] Make the stuff in the new module experimental/opt-in.
    • [ ] Add a section to the repo README about this API.
    opened by zach-klippenstein 0
  • Allow any type of ScannableView

    Allow any type of ScannableView

    Note: there's more work to make this not Android dependent. Notably refs to the WindowManager which likely belong to displayName for Views instead (?)

    opened by pyricau 2
Releases(v2.4.1)
  • v2.4.0(Aug 23, 2021)

  • v2.3.0(Mar 1, 2021)

  • v2.2.0(Jan 26, 2021)

    • Don't render curly braces when they would be empty. (#108)
    • Upgrade Compose to 1.0.0-alpha09. (#117)
    • Lazily reflect the mKeyedTags field and don't crash if it's not there. (#122)
    Source code(tar.gz)
    Source code(zip)
  • v2.1.0(Oct 15, 2020)

    • Introduce support for rendering across subcompositions. Children of WithConstraints, LazyColumn, etc. will now be rendered. (#104)
    • Fix for bug where layout IDs to skip were queried incorrectly. (#94 – thanks @samruston!)
    • Fix grammatical errors in documentation. (#92 – thanks @androiddevnotes!)
    • Upgrade Compose to 1.0.0-alpha05. (#106)
    Source code(tar.gz)
    Source code(zip)
  • v2.0.0(Sep 4, 2020)

    • Use fancy drawing characters for rendering tree lines. (#83)
    • Don't include "text-length" when full text included. (#82)
    • Move internal code to internal package. (#87)
    • Rename showTextValue parameter to renderTextValue. (#90)
    • Update Compose to 1.0.0-alpha02. (#79)
    Source code(tar.gz)
    Source code(zip)
  • v2.0.0-beta.1(Sep 2, 2020)

    • Correctly render nodes whose descriptions are more than one line. (#42)
    • Support rendering trees nested deeper than 64 levels. (#41)
    • Refactor ViewStateRenderer and ViewFilter API to be subclassable and more accessible from Java. (#44)
    • Use the × character for formatting dimensions instead of the letter x. (#49)
    • Add a sample app that uses Compose. (#53)
    • Introduce support for rendering Compose hierarchies. (#33)
    • Make SAM interfaces fun interfaces so they can be given as lambdas in Kotlin 1.4. (#47)
    • Introduce ScanScope, a more flexible way to define what to scan. (#70)
    Source code(tar.gz)
    Source code(zip)
  • v2.0.0-alpha.2(Aug 13, 2020)

    • Only access the view hierarchy from the main thread. (#30)
    • Remove build config class. (#36)
    • Change ViewFilter input from View to Any. (#37)
    • Rename StateRenderer to ViewStateRenderer. (#38)
    • Generate JVM overloads for methods with default args. (#38)
    • Make ViewStateRenderers properties static and fix case. (#38)
    • Make ViewFilter.and an extension function. (#38)
    Source code(tar.gz)
    Source code(zip)
  • v2.0.0-alpha.1(Aug 11, 2020)

    • First public release!
    • Convert the code to Kotlin. (#8, #9)
    • Rewrite the API to be more Kotlin-friendly. (#16)
    • Introduce StateRenderer to allow custom rendering of arbitrary view attributes. (#23)
    • Drop the com.squareup package prefix, so the package is now just radiography. (#15)
    • Add a sample app. (#18)
    • Fix: Crash when skippedIds is empty. (#16)
    • Fix: FocusedWindowViewFilter wasn't filtering correctly. (#18)

    Version starts at 2.0.0 to differentiate from some internal releases.

    Source code(tar.gz)
    Source code(zip)
Owner
Square
Square
Expandable text, similar to Text() in Jetpack Compose

A small library to display expandable texts in Jetpack Compose.

null 3 Sep 8, 2022
Notes is a simple and private notes app. Organize your thoughts, discoveries, and ideas and simplify planning important moments in your life with your digital notepad.

Notes Example Download Download the latest version of the Android app from this link. Building Using Android Studio Clone the repo, open it in Android

Dmitry Savin 1 Jan 3, 2022
A simple tool to display a text tree with Jetpack Compose🌲

A simple tool to display a text tree with Jetpack Compose??

Takahiro Menju 50 Oct 5, 2022
Drawing text around other content in Jetpack Compose

TextAroundContent Drawing text around other content in Jetpack Compose Installat

Dmitry Mysenko 8 Feb 9, 2022
ComposeTextBug - Issue with MotionLayout+Compose in Text functionality

ComposeTextBug Issue with MotionLayout+Compose in Text functionality Demo: devic

Yahor 0 Jan 12, 2022
ComposeChips - A gmail like chip edit text for compose ui

ComposeChips A gmail like chip edit text for compose ui Installation implementat

null 2 Feb 5, 2022
A Jetpack Compose component used for displaying Markdown-formatted text.

MarkdownText A library for displaying Markdown contents within Jetpack Compose. Uses Coil Current limitations Lists that are annotated with the * must

Arnau Mora 4 Dec 15, 2022
Add Cloud Firestore to your Android apps built with Jetpack Compose

JetFirestore Add Cloud Firestore to your Android apps built with Jetpack Compose Now with Jetpack Compose you can easily add Cloud Firestore to your e

Pankaj Rai 13 Oct 26, 2022
From Swedish "Öppettider", an app to quickly access your favorite places' opening times. Built to practice Android development and try out Jetpack Compose.

Appettider From Swedish "Öppettider", an app to quickly access your favorite places' opening times. Built to practice Android development and try out

Arianna Masciolini 0 Dec 6, 2021
📊 A web tool to visualise and compare your android benchmark results

benchart A web tool to visualise and compare your android benchmark result ✨ Demo Screen.Recording.2022-11-26.at.2.36.55.PM.mov ✍️ Author ?? theapache

theapache64 65 Dec 20, 2022
Holi is a lightweight Jetpack Compose library of colors, gradients and cool utility functions for all your palette needs!

Holi A library of colors, gradients and utils built using Jetpack Compose for Android Features A wide collection of colors from different palettes for

Siddhesh Patil 167 Dec 5, 2022
Holi is a lightweight Jetpack Compose library of colors, gradients and cool utility functions for all your palette needs!

Holi is a lightweight Jetpack Compose library of colors, gradients and cool utility functions for all your palette needs!

Sid Patil 167 Dec 5, 2022
How to use Jetpack Compose’s theming APIs to style your application

Jetpack Compose Theming Codelab This folder contains the source code for the Jetpack Compose Theming codelab. In this codelab you will learn how to us

David Merino 0 Oct 7, 2021
Migrating To Jetpack Compose For Your app

Migrating To Jetpack Compose Adding Jetpack Compose to your app If you want to use Jetpack Compose in an existing project, you’ll need to configure yo

Lubna Mariyam 1 Oct 18, 2021
Add chips to your apps built with Jetpack Compose!

Chip Add chips to your apps built with Jetpack Compose! To get started with Chip just add the maven url and the Chip dependency build.gradle (Project

Pankaj Rai 6 Sep 27, 2022
Simple app to locally track your public transport trips.

ÖffiTracker Simple app to locally track your public transport trips. License Copyright 2021 Patrick Löwenstein Licensed under the Apache License, Ver

Patrick Löwenstein 5 Dec 14, 2022
Make your device sip only small amounts of battery when not in use.

trickle Make your device sip only small amounts of battery when not in use. What Automatically place your device into battery-saver mode when the scre

pyamsoft 3 Dec 21, 2022
FullMangement - an application that helps you manage your tasks effectively. built with the latest tachs like Compose UI, Jetpack libraries, and MVVM design pattern.

Full Management is an application that helps you manage your tasks effectively. built with the latest tachs like Compose UI, Jetpack libraries and MVVM design pattern.

Amr algnyat 4 Nov 1, 2022
Danilo Lemes 5 Jul 31, 2022