Skip to main content
Roveflow navigates your app using visible text, the Flutter semantics tree, and vision reasoning. You never need to add a single ValueKey to adopt Roveflow. This page exists for the case where a scenario becomes fragile — multiple similar labels on the same screen, tightly dynamic copy, heavy i18n churn — and the report flags it. Adding a ValueKey on the offending widget makes future runs faster and more reliable, but it’s a tuning step, not a prerequisite.

When to reach for a key

Add a key when:
  • The report returns "fragile": true for a scenario that runs frequently.
  • A screen has multiple buttons with the same visible label.
  • Copy churn keeps breaking a text-based waypoint.
Don’t add keys for:
  • Read-only labels. The agent uses tap_by_text or screenshots to verify.
  • Decorative icons that aren’t tap targets.

Where to key

Key the widgets a scenario actually drives:
  • Every primary call-to-action button referenced in a scenario.
  • Every bottom navigation tab the agent navigates between.
  • Every list item the agent selects — one key per row is overkill; key the list and use index-based finders, or key the per-item action button.
  • Modal and bottom-sheet primary buttons used in cold-setup or recovery.

Naming convention

<lowercase_snake_case> describing the action or target:
  • login_button, email_field, password_field
  • home_tab, settings_tab, profile_tab
  • open_detail_button, confirm_button
  • dismiss_face_id_sheet, dismiss_notifications_sheet
When the same widget appears multiple times — one button per list item, for example — include the disambiguating id:
ElevatedButton(
  key: ValueKey('list_item_${item.id}'),
  onPressed: () => open(item),
  child: Text(item.label),
)

Example: keying a bottom-nav tab

// Before
BottomNavigationBarItem(
  icon: const Icon(Icons.settings),
  label: 'Settings',
)

// After
BottomNavigationBarItem(
  key: const ValueKey('settings_tab'),
  icon: const Icon(Icons.settings),
  label: 'Settings',
)

Example: keying a primary button

// Before
ElevatedButton(
  onPressed: () => login(),
  child: Text(context.l10n.loginButton),
)

// After
ElevatedButton(
  key: const ValueKey('login_button'),
  onPressed: () => login(),
  child: Text(context.l10n.loginButton),
)

Declaring keys in a scenario

List the keys under preferred_keys so the agent tries them before text or vision:
preferred_keys:
  - open_detail_button
  - settings_tab
  - confirm_button
See Authoring scenarios.

Verifying a key is reachable

With the app running in debug mode, in your Claude Code session:
mcp__flutter-inspector__tap_by_key(key: "login_button")
A successful response reads Tapped key "login_button". An error like widget with key "login_button" not found means the key isn’t in the current widget tree yet — wrong screen, or the code change hasn’t hot-reloaded.