Measure logo

Layout snapshots

Capture a lightweight wireframe of the screen with every tap to see the UI a gesture acted on.

Measure captures a layout snapshot with every click: a wireframe of the screen at the moment of the gesture. A snapshot shows what the user was acting on at a fraction of the cost of capturing, storing, and rendering a screenshot.

ScreenshotLayout snapshot
ScreenshotLayout snapshot

Snapshots are throttled to at most one every 750 ms, so bursts of taps don't add overhead.

Android

Snapshots cover the whole screen with no setup, whether it's built with Views, Jetpack Compose, or a mix of both.

iOS

Snapshots capture the full view hierarchy of the screen with no setup. SwiftUI content appears with its underlying system view names rather than your SwiftUI view names.

Flutter

A screen can hold thousands of widgets, so snapshots include only common widget types by default and skip the rest:

CategoryWidgets
ButtonsFilledButton, OutlinedButton, TextButton, ElevatedButton, CupertinoButton, ButtonStyleButton, MaterialButton, IconButton, FloatingActionButton
Menus and listsListTile, PopupMenuButton, PopupMenuItem, DropdownButton, DropdownMenuItem, ExpansionTile
StructureScaffold, CupertinoPageScaffold, MaterialApp, CupertinoApp, Container, Row, Column, Card
ScrollingListView, PageView, SingleChildScrollView, ScrollView
TextText, RichText

Include your own widgets

If your app is built from custom widgets, the default list won't say much about your UI. The measure_build package scans your project and generates a map of every widget type your code declares or uses, including widgets from packages you depend on, so snapshots show your real widget names. Private widgets (names starting with _) are left out.

  1. Add the dev dependencies in pubspec.yaml:

    dev_dependencies:
      measure_build: ^0.1.0
      build_runner: ^2.4.0
  2. Generate the widget map:

    dart run build_runner build

    This writes lib/msr_widgets.g.dart containing a map named widgetFilter. Re-run it after adding new widget types, or use dart run build_runner watch to regenerate on every change.

  3. Pass the map to the SDK during initialization:

    import 'package:measure_flutter/measure_flutter.dart';
    import 'package:your_app/msr_widgets.g.dart';
    
    Future<void> main() async {
      await Measure.instance.init(
        () => runApp(MeasureWidget(child: MyApp())),
        config: const MeasureConfig(widgetFilter: widgetFilter),
      );
    }

Configuration

The generator works without configuration. To change its defaults, configure it in your app's build.yaml:

targets:
  $default:
    builders:
      measure_build|widget_analyzer:
        options:
          output_path: lib/src/msr_widgets.g.dart
          scan_directories:
            - lib
            - custom_widgets
          variable_name: msrWidgetFilter
  • output_path sets where the generated file is written. Defaults to lib/msr_widgets.g.dart.
  • scan_directories lists the directories scanned for widgets. Defaults to lib; add entries if widget code lives elsewhere.
  • variable_name names the generated map. Defaults to widgetFilter.

React Native

Snapshots are generated by the native SDK for the platform the app runs on, so the Android and iOS behavior applies as is.

Performance

See Performance Impact for the full benchmark setup.

Android

Detecting and creating a snapshot adds 0.6 ms to 1 ms per click.

iOS

Capturing the layout hierarchy takes 7.5 ms at the 95th percentile, measured on an iPhone 14 Plus.

Flutter

Generating a snapshot and finding the tapped widget takes about 3 ms in a widget tree 50 levels deep, and the time grows linearly with depth (benchmarks).

Think this page can be better?

Open an issue