Skip to content

perf(home): widget rebuild optimization and PasswordBloc memory leak fix#46

Merged
dhruvanbhalara merged 3 commits into
mainfrom
perf/widget-rebuild-optimization
Jun 3, 2026
Merged

perf(home): widget rebuild optimization and PasswordBloc memory leak fix#46
dhruvanbhalara merged 3 commits into
mainfrom
perf/widget-rebuild-optimization

Conversation

@dhruvanbhalara

@dhruvanbhalara dhruvanbhalara commented May 31, 2026

Copy link
Copy Markdown
Owner

Description

Three performance issues were found via flutter_agent_lens MCP widget rebuild tracking and memory auditing:

  • FadeTransition and SlideTransition in AppAnimatedListItem triggered full subtree rebuilds on every animation frame, reaching roughly 90 rebuilds per second during list entrance animations.
  • BlocBuilder<PasswordBloc> had no buildWhen guard, so every background data refresh emitted a PasswordLoading state that destroyed and recreated the entire SliverList.
  • A stale broadcast subscription closure held a reference to a disposed PasswordBloc instance after hot restart, preventing garbage collection.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • UI/UX refinement

Changes

app_animated_list_item.dart

Replaced FadeTransition and SlideTransition with AnimatedBuilder using a pre-built child parameter. The builder closure now only rebuilds the thin Opacity and FractionalTranslation wrappers on each animation frame rather than the full child subtree. Rebuild count dropped from 450 to 12 per 5-second window (97% reduction).

home_screen.dart

Added buildWhen to BlocBuilder<PasswordBloc>. Rebuilds are skipped when transitioning from PasswordLoaded to PasswordLoading, keeping the list stable during background refreshes instead of destroying and rebuilding the entire SliverList.

password_bloc.dart

Added an isClosed guard in the _dataChangeSubscription listener to prevent stale events on a disposed bloc. Set _dataChangeSubscription = null after cancel() in close() to release the closure reference and allow GC to collect the old instance.

Testing instructions

  1. Run the app: flutter run --debug
  2. Navigate to the Home screen with a list of passwords.
  3. Connect flutter_agent_lens and run get_widget_rebuild_counts for 5 seconds.
  4. Confirm the top rebuilder is AnimatedScale from AppCard tap animations at around 12 rebuilds. No animation cascade should appear.
  5. Navigate away and back. The list should stay stable and not re-trigger entrance animations on background reload.

Rebuild metrics (flutter_agent_lens)

Metric Before After
Top rebuild count (5s) 450 12
Raw events 120 1
Reduction 97%

Checklist

  • I have followed the project's CONTRIBUTING.md guidelines.
  • My code follows the style guidelines of this project.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • My changes generate no new warnings.
  • I have added tests that prove my fix is effective.
  • New and existing unit tests pass locally with my changes.
  • Any dependent changes have been merged and published in downstream modules.

Code coverage

Metric Value
Total lines 5221
Lines hit 4244
Overall coverage 81.29%

Generated by PassVault CI

… to eliminate animation-driven subtree rebuilds

Previously, AppAnimatedListItem used FadeTransition and SlideTransition directly,
which are AnimatedWidget subclasses that call setState on every animation frame.
This caused the entire child subtree (GroupedPasswordListTile, AppCard, etc.) to
rebuild at ~90 times/sec during list entrance animations.

Replace both with a single AnimatedBuilder that receives the child as a pre-built
constant parameter. The builder closure only re-runs Opacity + FractionalTranslation
wrappers, leaving the child widget untouched during animation ticks.

Impact: Widget rebuild count for list items dropped from 450 rebuilds/5s → 12/5s
(97% reduction), verified via flutter_agent_lens MCP widget rebuild tracking.
…on background reload

Without buildWhen, the BlocBuilder<PasswordBloc> on HomeScreen rebuilds the
entire SliverList every time PasswordBloc emits PasswordLoading — which happens
on every background data-change (triggered by repository stream events after
add/update/delete in other screens).

This destroyed all GroupedPasswordListTile widgets and triggered a new round of
AppAnimatedListItem entrance animations on every cross-screen navigation.

Add buildWhen guard: skip rebuild when transitioning from PasswordLoaded to
PasswordLoading (background refresh), so the list remains stable while data
reloads silently in the background. The list only re-renders when new data
(PasswordLoaded) arrives with updated content.
…ordBloc

Two issues fixed in PasswordBloc.close() and the stream subscription callback:

1. Stale event after dispose: When PasswordBloc is closed (e.g. hot restart in
   debug mode), the _BroadcastSubscription from _repository.dataChanges could
   still fire and call add(LoadPasswords()) on the closed bloc, causing an
   UnhandledStreamError or state emission on a disposed stream.
   Fix: Add isClosed guard inside the listener.

2. Memory leak via closure retention: _dataChangeSubscription captures 'this'
   (PasswordBloc) in its closure. Calling .cancel() removes the subscription but
   the closure object itself still holds a reference. Setting the field to null
   after cancel() allows the closure to be garbage collected, preventing the old
   PasswordBloc instance from being retained by the broadcast stream's subscriber
   list.

   Verified via flutter_agent_lens MCP memory audit: leak path via
   _BroadcastSubscription → _Closure → PasswordBloc (mounted=false).
@dhruvanbhalara dhruvanbhalara added the enhancement New feature or request label May 31, 2026
@dhruvanbhalara dhruvanbhalara changed the title perf(home): widget rebuild optimization & PasswordBloc memory leak fix perf(home): widget rebuild optimization and PasswordBloc memory leak fix May 31, 2026
@dhruvanbhalara dhruvanbhalara self-assigned this May 31, 2026
@dhruvanbhalara dhruvanbhalara merged commit be9f831 into main Jun 3, 2026
3 checks passed
@dhruvanbhalara dhruvanbhalara deleted the perf/widget-rebuild-optimization branch June 3, 2026 05:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant