Skip to content

Conversation

@jacobsimionato
Copy link
Collaborator

@jacobsimionato jacobsimionato commented Nov 19, 2025

This PR addresses several issues related to surface creation and update notifications in the genui package.

Changes:

  • Corrected the logic in GenUiManager to ensure that SurfaceAdded events are fired only when a new surface is created, and SurfaceUpdated events are fired for all subsequent modifications, including when rendering begins.
  • Updated the genui_manager_test.dart to accurately reflect the corrected event firing logic, ensuring that the tests properly validate the behavior of GenUiManager.
  • Applied automated fixes and formatting across the codebase.

@jacobsimionato
Copy link
Collaborator Author

/gemini review

Copy link
Contributor

@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 correctly refactors the logic for surface creation and update notifications. The distinction between new and existing surfaces is now handled more robustly in the SurfaceUpdate case, and DataModelUpdate now correctly triggers a UI refresh for rendered surfaces. The tests have also been updated to reflect these changes. I've added one comment to improve the robustness of the BeginRendering message handler to align it with the new logic for handling new surfaces.

Comment on lines 186 to 201
final String surfaceId = message.surfaceId;
dataModelForSurface(surfaceId);
final ValueNotifier<UiDefinition?> notifier = getSurfaceNotifier(
message.surfaceId,
surfaceId,
);

// Update the definition with the root component
final UiDefinition uiDefinition =
notifier.value ?? UiDefinition(surfaceId: message.surfaceId);
notifier.value ?? UiDefinition(surfaceId: surfaceId);
final UiDefinition newUiDefinition = uiDefinition.copyWith(
rootComponentId: message.root,
);
notifier.value = newUiDefinition;
genUiLogger.info('Started rendering ${message.surfaceId}');
_surfaceUpdates.add(SurfaceUpdated(message.surfaceId, newUiDefinition));

genUiLogger.info('Start rendering surface $surfaceId');
_surfaceUpdates.add(SurfaceUpdated(surfaceId, newUiDefinition));
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The current implementation for BeginRendering assumes that a SurfaceUpdate message has already been received for the given surfaceId. If BeginRendering is the first message for a new surface, it will be created, but a SurfaceUpdated event will be fired instead of SurfaceAdded.

This is inconsistent with the logic in the SurfaceUpdate case, which correctly handles new surfaces by firing SurfaceAdded.

To make this more robust and handle the edge case where BeginRendering creates the surface, consider checking if the surface is new and firing the appropriate event, similar to how it's done in the SurfaceUpdate case.

        final String surfaceId = message.surfaceId;
        final bool isNewSurface = !_surfaces.containsKey(surfaceId);
        dataModelForSurface(surfaceId);
        final ValueNotifier<UiDefinition?> notifier = getSurfaceNotifier(
          surfaceId,
        );

        // Update the definition with the root component
        final UiDefinition uiDefinition =
            notifier.value ?? UiDefinition(surfaceId: surfaceId);
        final UiDefinition newUiDefinition = uiDefinition.copyWith(
          rootComponentId: message.root,
        );
        notifier.value = newUiDefinition;

        genUiLogger.info('Start rendering surface $surfaceId');
        if (isNewSurface) {
          _surfaceUpdates.add(SurfaceAdded(surfaceId, newUiDefinition));
        } else {
          _surfaceUpdates.add(SurfaceUpdated(surfaceId, newUiDefinition));
        }

Copy link
Contributor

@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 correctly refactors the logic for surface creation and update notifications, ensuring SurfaceAdded is fired for new surfaces and SurfaceUpdated for modifications. The changes in GenUiManager and the corresponding test updates in genui_manager_test.dart are well-aligned with this goal.

I've identified a couple of areas for improvement. One is a potential memory leak and unintended side effect in the DataModelUpdate handler. The other is an inconsistency in the BeginRendering handler, which doesn't correctly fire a SurfaceAdded event for new surfaces. Addressing these points will improve the robustness and correctness of the GenUiManager.

Comment on lines 186 to 201
final String surfaceId = message.surfaceId;
dataModelForSurface(surfaceId);
final ValueNotifier<UiDefinition?> notifier = getSurfaceNotifier(
message.surfaceId,
surfaceId,
);

// Update the definition with the root component
final UiDefinition uiDefinition =
notifier.value ?? UiDefinition(surfaceId: message.surfaceId);
notifier.value ?? UiDefinition(surfaceId: surfaceId);
final UiDefinition newUiDefinition = uiDefinition.copyWith(
rootComponentId: message.root,
);
notifier.value = newUiDefinition;
genUiLogger.info('Started rendering ${message.surfaceId}');
_surfaceUpdates.add(SurfaceUpdated(message.surfaceId, newUiDefinition));

genUiLogger.info('Start rendering surface $surfaceId');
_surfaceUpdates.add(SurfaceUpdated(surfaceId, newUiDefinition));
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The current implementation for BeginRendering always fires a SurfaceUpdated event. This is inconsistent with the logic for SurfaceUpdate and can lead to issues on the client side if BeginRendering is the first message for a given surface. In that case, the client would receive a SurfaceUpdated event for a surface it hasn't seen before, instead of the expected SurfaceAdded.

To fix this, you should check if the surface is new before getting the notifier, and then fire SurfaceAdded or SurfaceUpdated accordingly, just like you've done for the SurfaceUpdate case.

        final String surfaceId = message.surfaceId;
        final bool isNewSurface = !_surfaces.containsKey(surfaceId);
        dataModelForSurface(surfaceId);
        final ValueNotifier<UiDefinition?> notifier = getSurfaceNotifier(
          surfaceId,
        );

        // Update the definition with the root component
        final UiDefinition uiDefinition =
            notifier.value ?? UiDefinition(surfaceId: surfaceId);
        final UiDefinition newUiDefinition = uiDefinition.copyWith(
          rootComponentId: message.root,
        );
        notifier.value = newUiDefinition;

        if (isNewSurface) {
          genUiLogger.info('Adding and rendering new surface $surfaceId');
          _surfaceUpdates.add(SurfaceAdded(surfaceId, newUiDefinition));
        } else {
          genUiLogger.info('Start rendering surface $surfaceId');
          _surfaceUpdates.add(SurfaceUpdated(surfaceId, newUiDefinition));
        }

@jacobsimionato
Copy link
Collaborator Author

/gemini review

Copy link
Contributor

@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 effectively addresses the bugs in surface creation and update notifications by correctly assigning the SurfaceAdded event to the BeginRendering message and ensuring SurfaceUpdated is used for subsequent modifications. The accompanying test updates properly validate this new logic. I've included a couple of suggestions to enhance code conciseness and improve the robustness of one of the tests.

Copy link
Collaborator

@andrewkolos andrewkolos left a comment

Choose a reason for hiding this comment

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

LGTM with nits

jacobsimionato and others added 2 commits November 27, 2025 15:01
Co-authored-by: Andrew Kolos <andrewrkolos@gmail.com>
Co-authored-by: Andrew Kolos <andrewrkolos@gmail.com>
@jacobsimionato jacobsimionato merged commit 8b65981 into flutter:main Nov 27, 2025
30 checks passed
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.

2 participants