Skip to content

perf(splash): optimize reactive task state management and reduce unnecessary rebuilds#16

Merged
pckimlong merged 1 commit into
devfrom
enhancement
Dec 6, 2025
Merged

perf(splash): optimize reactive task state management and reduce unnecessary rebuilds#16
pckimlong merged 1 commit into
devfrom
enhancement

Conversation

@pckimlong
Copy link
Copy Markdown
Owner

Replace Set/Map-based state tracking with fixed-size lists for O(1) access. Add counters to cache loading state instead of iterating collections. Minimize setState calls by checking if state actually changed before updating. Cache SplashTaskError string representation. Add early returns in providers when no tasks exist. Use eagerError in Future.wait for faster failure detection. Make oneTimeTasks and reactiveTasks

…cessary rebuilds

Replace Set/Map-based state tracking with fixed-size lists for O(1) access. Add counters to cache loading state instead of iterating collections. Minimize setState calls by checking if state actually changed before updating. Cache SplashTaskError string representation. Add early returns in providers when no tasks exist. Use eagerError in Future.wait for faster failure detection. Make oneTimeTasks and reactiveTasks
@pckimlong pckimlong merged commit d64384a into dev Dec 6, 2025
1 of 2 checks passed
@pckimlong pckimlong deleted the enhancement branch December 6, 2025 06:59
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @pckimlong, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers significant performance enhancements and stability improvements to the splash screen task management system. It refactors the internal state tracking to use more efficient data structures, reduces redundant UI rebuilds, and optimizes error handling and task execution logic. These changes aim to provide a smoother, faster, and more reliable splash experience for users.

Highlights

  • Performance Optimization: Replaced Set/Map-based state tracking with fixed-size lists for O(1) access and introduced counters to cache loading states, significantly reducing iteration overhead and improving performance.
  • Reduced Rebuilds: Implemented conditional setState calls in SplashBuilder to only trigger UI updates when the state genuinely changes, minimizing unnecessary widget rebuilds.
  • Improved Error Handling: Utilized eagerError: true in Future.wait for one-time tasks to enable faster failure detection. The SplashTaskError.toString() method now caches its string representation and limits stack trace output for conciseness.
  • Enhanced Task Management: Added early return checks in splash task providers when no tasks are configured or indices are out of bounds, preventing unnecessary processing. oneTimeTasks and reactiveTasks in SplashConfig are now immutable.
  • Dependency Updates: Updated flutter_riverpod and riverpod dependencies to version 3.0.3 in the example project.
  • Comprehensive Testing: Expanded test coverage with new test groups and numerous test cases for SplashConfig, SplashTaskError, oneTimeSplashTasksProvider, reactiveSplashTasksProvider, SplashBuilder, and the task() helper, ensuring robustness and correctness of the changes.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces significant performance optimizations to the splash screen task management, primarily by replacing map/set-based state tracking with more efficient fixed-size lists and counters, and by reducing unnecessary widget rebuilds. The changes are well-reasoned and align with the goals outlined in the description. Caching the string representation of SplashTaskError and using eagerError in Future.wait are also smart improvements. The addition of a comprehensive test suite is particularly commendable and greatly increases confidence in the correctness of these optimizations. My feedback includes a few minor suggestions to improve code clarity and maintainability.

Comment on lines +78 to +90
// Only call setState if state actually changed
if ((shouldMarkChanged && !wasChanged) || wasActive != isNowActive) {
setState(() {
if (shouldMarkChanged && !wasChanged) {
_changedWatchValues[taskIndex] = true;
_changedWatchCount++;
}
if (wasActive != isNowActive) {
_activeWatchValues[taskIndex] = isNowActive;
_activeWatchCount += isNowActive ? 1 : -1;
}
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To improve readability and avoid redundant logic, the conditions for updating the state can be evaluated once and stored in local boolean variables. This makes the code cleaner and easier to follow, as the conditions are not re-evaluated inside the setState block.

        // Only call setState if state actually changed
        final needsChangeUpdate = shouldMarkChanged && !wasChanged;
        final needsActiveUpdate = wasActive != isNowActive;

        if (needsChangeUpdate || needsActiveUpdate) {
          setState(() {
            if (needsChangeUpdate) {
              _changedWatchValues[taskIndex] = true;
              _changedWatchCount++;
            }
            if (needsActiveUpdate) {
              _activeWatchValues[taskIndex] = isNowActive;
              _activeWatchCount += isNowActive ? 1 : -1;
            }
          });
        }

Comment on lines +105 to +117
// Only call setState if state actually changed
if (wasActive != shouldBeActive || shouldClearChanged) {
setState(() {
if (wasActive != shouldBeActive) {
_activeExecuteTasks[taskIndex] = shouldBeActive;
_activeExecuteCount += shouldBeActive ? 1 : -1;
}
if (shouldClearChanged) {
_changedWatchValues[taskIndex] = false;
_changedWatchCount--;
}
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Similar to the watch provider listener, the condition wasActive != shouldBeActive is checked twice. Storing this in a local boolean variable would make the code more readable and prevent re-evaluating the same condition.

        // Only call setState if state actually changed
        final needsActiveUpdate = wasActive != shouldBeActive;
        if (needsActiveUpdate || shouldClearChanged) {
          setState(() {
            if (needsActiveUpdate) {
              _activeExecuteTasks[taskIndex] = shouldBeActive;
              _activeExecuteCount += shouldBeActive ? 1 : -1;
            }
            if (shouldClearChanged) {
              _changedWatchValues[taskIndex] = false;
              _changedWatchCount--;
            }
          });
        }

Comment thread lib/src/splash_task.dart
if (tasks.isNotEmpty) {
if (config.runOneTimeTaskInParallel) {
await Future.wait([for (final task in tasks) task(ref)]);
// Use more efficient Future.wait with growable: false
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The comment mentions using growable: false, but a list literal [...] creates a growable list in Dart. While the performance impact is negligible, the comment is inaccurate. It would be clearer to update the comment to reflect the primary optimization here, which is the use of eagerError.

Suggested change
// Use more efficient Future.wait with growable: false
// Use Future.wait with eagerError for faster failure detection

Comment thread lib/src/splash_task.dart
Comment on lines +109 to +112
final stackLines = stack.toString().split('\n');
final limitedStack = stackLines.length > 5
? stackLines.sublist(0, 5).join('\n')
: stackLines.join('\n');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The logic to limit the stack trace to the first 5 lines can be simplified. The previous implementation using .take(5) is more concise and idiomatic in Dart for this purpose. It achieves the same result with better readability.

Suggested change
final stackLines = stack.toString().split('\n');
final limitedStack = stackLines.length > 5
? stackLines.sublist(0, 5).join('\n')
: stackLines.join('\n');
final limitedStack = stack.toString().split('\n').take(5).join('\n');

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.

1 participant