Skip to content

refacto: replace hardcoded widget keys with type-safe enums#2915

Open
9clg6 wants to merge 1 commit intomainfrom
refacto/widget-keys-enums
Open

refacto: replace hardcoded widget keys with type-safe enums#2915
9clg6 wants to merge 1 commit intomainfrom
refacto/widget-keys-enums

Conversation

@9clg6
Copy link
Collaborator

@9clg6 9clg6 commented Mar 12, 2026

Ticket
No ticket, refacto: hardcoded widget Key strings scattered across 20+ files with inconsistent conventions and duplicated values in tests.

Root cause
N/A — refactoring, not a bug.

Solution

Created 5 typed enums (NavigationKeys, SettingsKeys, DialogKeys, ChatKeys, LinkPreviewKeys) in lib/presentation/widget_keys/ that auto-derive key strings from enum member names
Migrated all ~45 hardcoded Key('...') / ValueKey('...') declarations to use these enums
Updated integration test robots to reference enums instead of duplicating raw strings
Removed dead key reference ConnectPageListView from default_flows.dart
Added integration_test/test_bundle.dart to .gitignore (auto-generated file)

Impact description

Type safety: typos in key names are now compile-time errors instead of silent test failures
Single source of truth: no more string duplication between widgets and tests
IDE support: autocomplete and refactoring work out of the box
Consistency: all keys now follow camelCase convention (derived from enum names)
No functional change — widget behavior is identical.

Test recommendations

dart analyze lib/ passes with 0 issues
flutter test test/widget/message/twake_link_preview_item_test.dart — 7/7 pass
Run full integration test suite to confirm key matching still works

Pre-merge

No.

Resolved

No visual change — pure refactoring. No screenshots needed.

Summary by CodeRabbit

  • Refactor

    • Centralized widget key management into shared registries for more consistent UI element identification; no user-facing behavior changes.
  • Tests

    • Updated integration tests to use the centralized keys, improving stability and reliability.
  • Chores

    • Replaced scattered hard-coded keys with registry lookups and consolidated imports across multiple modules for cleaner maintenance.

@coderabbitai
Copy link

coderabbitai bot commented Mar 12, 2026

Walkthrough

This pull request centralizes widget key definitions by adding a new lib/presentation/widget_keys/ barrel and five enum-based registries (ChatKeys, DialogKeys, LinkPreviewKeys, NavigationKeys, SettingsKeys) that expose Key and ValueKey getters. It replaces scattered hard-coded Key/ValueKey literals and removed local key constants across many widgets, pages, dialogs, and integration tests with references to these centralized enums. No behavioral or control-flow changes beyond key indirection are introduced.

Possibly related PRs

Suggested reviewers

  • dab246
  • nqhhdev
  • tddang-linagora
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title clearly and concisely summarizes the main change: replacing hardcoded widget keys with type-safe enums, which is the primary objective of this refactoring effort.
Description check ✅ Passed The pull request description is mostly complete and follows the provided template structure with all key sections addressed: Ticket, Root cause, Solution, Impact description, Test recommendations, Pre-merge, and Resolved.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refacto/widget-keys-enums
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold.dart (1)

20-20: Route the default scaffold key through NavigationKeys too.

Line 20 still duplicates 'scaffoldWithNestedNavigation' as a raw ValueKey, while lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body_view.dart:32-45 already reads the same selector from NavigationKeys. That leaves two sources of truth for the scaffold key inside the same refactor and makes a future rename easy to miss here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold.dart` at line 20,
The constructor in AppAdaptiveScaffold currently uses a hard-coded
ValueKey('scaffoldWithNestedNavigation') in the super call; change it to reuse
the canonical key from NavigationKeys (the same key used in
AppAdaptiveScaffoldBodyView) by replacing the raw ValueKey with
NavigationKeys.scaffoldWithNestedNavigation (or the matching identifier in
NavigationKeys) so there is a single source of truth for the scaffold key.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold.dart`:
- Line 20: The constructor in AppAdaptiveScaffold currently uses a hard-coded
ValueKey('scaffoldWithNestedNavigation') in the super call; change it to reuse
the canonical key from NavigationKeys (the same key used in
AppAdaptiveScaffoldBodyView) by replacing the raw ValueKey with
NavigationKeys.scaffoldWithNestedNavigation (or the matching identifier in
NavigationKeys) so there is a single source of truth for the scaffold key.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4ee703cb-fe48-4ece-8fb5-80a0de99a3a1

📥 Commits

Reviewing files that changed from the base of the PR and between 36f47fb and 7667851.

📒 Files selected for processing (31)
  • integration_test/extensions/default_flows.dart
  • integration_test/robots/home_robot.dart
  • integration_test/robots/setting/settings_privacy_and_security_robot.dart
  • lib/pages/bootstrap/init_client_dialog.dart
  • lib/pages/bootstrap/tom_bootstrap_dialog.dart
  • lib/pages/chat/chat.dart
  • lib/pages/chat/chat_scroll_view.dart
  • lib/pages/chat/send_file_dialog/send_file_dialog.dart
  • lib/pages/chat_adaptive_scaffold/chat_adaptive_scaffold_builder.dart
  • lib/pages/chat_details/chat_details.dart
  • lib/pages/chat_draft/draft_chat.dart
  • lib/pages/chat_list/chat_list_view.dart
  • lib/pages/chat_profile_info/chat_profile_info_details.dart
  • lib/pages/settings_dashboard/settings/settings_view.dart
  • lib/pages/settings_dashboard/settings_profile/settings_profile_view.dart
  • lib/pages/settings_dashboard/settings_security/settings_security_view.dart
  • lib/presentation/mixins/chat_details_tab_mixin.dart
  • lib/presentation/mixins/media_viewer_app_bar_mixin.dart
  • lib/presentation/widget_keys/chat_keys.dart
  • lib/presentation/widget_keys/dialog_keys.dart
  • lib/presentation/widget_keys/link_preview_keys.dart
  • lib/presentation/widget_keys/navigation_keys.dart
  • lib/presentation/widget_keys/settings_keys.dart
  • lib/presentation/widget_keys/widget_keys.dart
  • lib/utils/dialog/twake_dialog.dart
  • lib/widgets/layouts/adaptive_layout/adaptive_scaffold_appbar_style.dart
  • lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold.dart
  • lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body_view.dart
  • lib/widgets/layouts/enum/adaptive_destinations_enum.dart
  • lib/widgets/twake_components/twake_preview_link/twake_link_preview.dart
  • lib/widgets/twake_components/twake_preview_link/twake_link_preview_item.dart
💤 Files with no reviewable changes (1)
  • lib/pages/chat_list/chat_list_view.dart

@github-actions
Copy link
Contributor

This PR has been deployed to https://linagora.github.io/twake-on-matrix/2915

Copy link
Member

@hoangdat hoangdat left a comment

Choose a reason for hiding this comment

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

Did you run one round again of E2E test to ensure nothing break?

@9clg6
Copy link
Collaborator Author

9clg6 commented Mar 13, 2026

Did you run one round again of E2E test to ensure nothing break?

No, I only ran a single random test, because it's E2E and it would take me a really long time to run everything. And in reality, I didn't change anything functional, only structural, so there's little chance that it would have an impact on proper functioning.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
lib/presentation/widget_keys/dialog_keys.dart (1)

3-8: Add/keep key-focused integration smoke for dialog flows in CI.

Since runtime key values are derived from enum names, a member rename can silently break finder-based E2E selectors. A lightweight smoke on bootstrap/init/confirm-dialog paths helps catch that early.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/presentation/widget_keys/dialog_keys.dart` around lines 3 - 8, Add a
small CI integration smoke test that asserts the dialog-related enum names are
available as runtime keys and that the bootstrap/init/confirm dialog flows can
be found by those keys; specifically create a test (e.g., DialogFlowSmokeTest or
dialog_keys_smoke_test) which boots the relevant widget tree, then uses finder
by Key(ValueKey(DialogKeys.showConfirmAlertDialog.name)),
Key(ValueKey(DialogKeys.bootstrapBreakpointMobile.name))/WebAndDesktop, and
Key(ValueKey(DialogKeys.initClientBreakpointMobile.name))/WebAndDesktop to
locate the widgets, tap the confirm dialog trigger, and verify the dialog
appears; this ensures any future renames of DialogKeys (enum DialogKeys and its
members showConfirmAlertDialog, bootstrapBreakpointMobile,
bootstrapBreakpointWebAndDesktop, initClientBreakpointMobile,
initClientBreakpointWebAndDesktop) break CI so they are caught early.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@lib/presentation/widget_keys/dialog_keys.dart`:
- Around line 3-8: Add a small CI integration smoke test that asserts the
dialog-related enum names are available as runtime keys and that the
bootstrap/init/confirm dialog flows can be found by those keys; specifically
create a test (e.g., DialogFlowSmokeTest or dialog_keys_smoke_test) which boots
the relevant widget tree, then uses finder by
Key(ValueKey(DialogKeys.showConfirmAlertDialog.name)),
Key(ValueKey(DialogKeys.bootstrapBreakpointMobile.name))/WebAndDesktop, and
Key(ValueKey(DialogKeys.initClientBreakpointMobile.name))/WebAndDesktop to
locate the widgets, tap the confirm dialog trigger, and verify the dialog
appears; this ensures any future renames of DialogKeys (enum DialogKeys and its
members showConfirmAlertDialog, bootstrapBreakpointMobile,
bootstrapBreakpointWebAndDesktop, initClientBreakpointMobile,
initClientBreakpointWebAndDesktop) break CI so they are caught early.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e036e681-ca6f-42dd-a1ad-e9a1d8c5ecd3

📥 Commits

Reviewing files that changed from the base of the PR and between 7667851 and 17472d2.

📒 Files selected for processing (31)
  • integration_test/extensions/default_flows.dart
  • integration_test/robots/home_robot.dart
  • integration_test/robots/setting/settings_privacy_and_security_robot.dart
  • lib/pages/bootstrap/init_client_dialog.dart
  • lib/pages/bootstrap/tom_bootstrap_dialog.dart
  • lib/pages/chat/chat.dart
  • lib/pages/chat/chat_scroll_view.dart
  • lib/pages/chat/send_file_dialog/send_file_dialog.dart
  • lib/pages/chat_adaptive_scaffold/chat_adaptive_scaffold_builder.dart
  • lib/pages/chat_details/chat_details.dart
  • lib/pages/chat_draft/draft_chat.dart
  • lib/pages/chat_list/chat_list_view.dart
  • lib/pages/chat_profile_info/chat_profile_info_details.dart
  • lib/pages/settings_dashboard/settings/settings_view.dart
  • lib/pages/settings_dashboard/settings_profile/settings_profile_view.dart
  • lib/pages/settings_dashboard/settings_security/settings_security_view.dart
  • lib/presentation/mixins/chat_details_tab_mixin.dart
  • lib/presentation/mixins/media_viewer_app_bar_mixin.dart
  • lib/presentation/widget_keys/chat_keys.dart
  • lib/presentation/widget_keys/dialog_keys.dart
  • lib/presentation/widget_keys/link_preview_keys.dart
  • lib/presentation/widget_keys/navigation_keys.dart
  • lib/presentation/widget_keys/settings_keys.dart
  • lib/presentation/widget_keys/widget_keys.dart
  • lib/utils/dialog/twake_dialog.dart
  • lib/widgets/layouts/adaptive_layout/adaptive_scaffold_appbar_style.dart
  • lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold.dart
  • lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold_body_view.dart
  • lib/widgets/layouts/enum/adaptive_destinations_enum.dart
  • lib/widgets/twake_components/twake_preview_link/twake_link_preview.dart
  • lib/widgets/twake_components/twake_preview_link/twake_link_preview_item.dart
💤 Files with no reviewable changes (1)
  • lib/pages/chat_list/chat_list_view.dart
🚧 Files skipped from review as they are similar to previous changes (16)
  • lib/presentation/mixins/media_viewer_app_bar_mixin.dart
  • lib/pages/chat_draft/draft_chat.dart
  • lib/pages/chat_adaptive_scaffold/chat_adaptive_scaffold_builder.dart
  • lib/pages/chat/chat.dart
  • lib/widgets/layouts/adaptive_layout/app_adaptive_scaffold.dart
  • integration_test/robots/home_robot.dart
  • lib/pages/chat/send_file_dialog/send_file_dialog.dart
  • lib/pages/chat/chat_scroll_view.dart
  • lib/widgets/twake_components/twake_preview_link/twake_link_preview.dart
  • lib/pages/bootstrap/tom_bootstrap_dialog.dart
  • lib/pages/chat_details/chat_details.dart
  • lib/presentation/widget_keys/widget_keys.dart
  • lib/pages/settings_dashboard/settings/settings_view.dart
  • integration_test/extensions/default_flows.dart
  • lib/pages/settings_dashboard/settings_security/settings_security_view.dart
  • lib/presentation/widget_keys/navigation_keys.dart

@Crash--
Copy link
Contributor

Crash-- commented Mar 18, 2026

No, I only ran a single random test, because it's E2E and it would take me a really long time to run everything

Do we have a tasks list of the actions needed to be able to run e2e on CI?

@9clg6
Copy link
Collaborator Author

9clg6 commented Mar 18, 2026

No, I only ran a single random test, because it's E2E and it would take me a really long time to run everything

Do we have a tasks list of the actions needed to be able to run e2e on CI?

Not that I know of

@dab246
Copy link
Member

dab246 commented Mar 18, 2026

IMO, Your current implementation has a few structural issues that will become problematic at scale:

  • Repeated logic (key, valueKey) across all enums
  • No namespacing → high risk of key collisions in large apps
  • No centralized control over naming conventions
  • Hard to refactor globally (e.g. changing key format)

Suggestion:

  • Instead of duplicating logic in every enum, define a reusable mixin:
import 'package:flutter/foundation.dart';

mixin NamespacedKey on Enum {
  String get namespace;

  String get fullName => '$namespace.$name';

  Key get key => Key(fullName);

  ValueKey<String> get valueKey => ValueKey(fullName);
}

=> Then refactor enums with namespace

enum ChatKeys with NamespacedKey {
  leaveChatButton,
  actionsMobileAndTablet,
  actionsWebAndDesktop,
  composerTypeAhead,
  mediaPickerTypeAhead,
  draftComposerTypeAhead,
  draftMediaPickerTypeAhead,
  sendFileDialogTypeAhead,
  eventListCenter,
  invitationSelectionMobileAndTablet,
  invitationSelectionWebAndDesktop,
  forwardSelectionMobileAndTablet,
  forwardSelectionWebAndDesktop;

  @override
  String get namespace => 'chat';
}
...

=> Debugging, testing, and tracking are extremely easy.

@9clg6
Copy link
Collaborator Author

9clg6 commented Mar 18, 2026

Thanks for the review! I agree on the duplicated logic point — the key/valueKey getters are copy-pasted across all 5 enums, which is not ideal.

I'll extract a mixin to fix that:

mixin WidgetKeyMixin on Enum {
  Key get key => Key(name);
  ValueKey<String> get valueKey => ValueKey(name);
}

For the namespacing part though, I'm not convinced it's worth it here. The enum member names are already descriptive enough (leaveChatButton, composerTypeAhead, etc.), and the enum type itself (ChatKeys.x vs NavigationKeys.x) already provides namespacing at the Dart level.

I'll push the mixin refactor without the namespace. WDYT?

@dab246
Copy link
Member

dab246 commented Mar 19, 2026

For the namespacing part though, I'm not convinced it's worth it here. The enum member names are already descriptive enough (leaveChatButton, composerTypeAhead, etc.), and the enum type itself (ChatKeys.x vs NavigationKeys.x) already provides namespacing at the Dart level.

I'll push the mixin refactor without the namespace. WDYT?

IMO, Enums only provide namespacing at compile-time, but Flutter Key compares by string at runtime, so keys like "title" can collide across features. find.byKey(Key("title")) may match the wrong widget. Adding a namespace (e.g. chat.title) ensures uniqueness, prevents flaky tests, and improves debugging. Anyway, we can choose either way, but I prefer adding namespacing. :)

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.

4 participants