-
Notifications
You must be signed in to change notification settings - Fork 80
Fix surface creation and update notification bugs #556
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -158,14 +158,12 @@ class GenUiManager implements GenUiHost { | |||||||||||||||||||||||||||
| void handleMessage(A2uiMessage message) { | ||||||||||||||||||||||||||||
| switch (message) { | ||||||||||||||||||||||||||||
| case SurfaceUpdate(): | ||||||||||||||||||||||||||||
| // No need for SurfaceAdded here because A2uiMessage will never generate | ||||||||||||||||||||||||||||
| // those. We decide here if the surface is new or not, and generate a | ||||||||||||||||||||||||||||
| // SurfaceAdded event if so. | ||||||||||||||||||||||||||||
| final String surfaceId = message.surfaceId; | ||||||||||||||||||||||||||||
| final ValueNotifier<UiDefinition?> notifier = getSurfaceNotifier( | ||||||||||||||||||||||||||||
| surfaceId, | ||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||
| final isNew = notifier.value == null; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Caching logic remains the same | ||||||||||||||||||||||||||||
| UiDefinition uiDefinition = | ||||||||||||||||||||||||||||
| notifier.value ?? UiDefinition(surfaceId: surfaceId); | ||||||||||||||||||||||||||||
| final Map<String, Component> newComponents = Map.of( | ||||||||||||||||||||||||||||
|
|
@@ -176,26 +174,34 @@ class GenUiManager implements GenUiHost { | |||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| uiDefinition = uiDefinition.copyWith(components: newComponents); | ||||||||||||||||||||||||||||
| notifier.value = uiDefinition; | ||||||||||||||||||||||||||||
| if (isNew) { | ||||||||||||||||||||||||||||
| genUiLogger.info('Adding surface $surfaceId'); | ||||||||||||||||||||||||||||
| _surfaceUpdates.add(SurfaceAdded(surfaceId, uiDefinition)); | ||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Notify UI ONLY if rendering has begun (i.e., rootComponentId is set) | ||||||||||||||||||||||||||||
| if (uiDefinition.rootComponentId != null) { | ||||||||||||||||||||||||||||
| genUiLogger.info('Updating surface $surfaceId'); | ||||||||||||||||||||||||||||
| _surfaceUpdates.add(SurfaceUpdated(surfaceId, uiDefinition)); | ||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||
| genUiLogger.info( | ||||||||||||||||||||||||||||
| 'Caching components for surface $surfaceId (pre-rendering)', | ||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| case BeginRendering(): | ||||||||||||||||||||||||||||
| dataModelForSurface(message.surfaceId); | ||||||||||||||||||||||||||||
| 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)); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // ALWAYS fire SurfaceAdded, as this is the signal to start rendering. | ||||||||||||||||||||||||||||
| genUiLogger.info('Creating and rendering surface $surfaceId'); | ||||||||||||||||||||||||||||
| _surfaceUpdates.add(SurfaceAdded(surfaceId, newUiDefinition)); | ||||||||||||||||||||||||||||
| case DataModelUpdate(): | ||||||||||||||||||||||||||||
| final String path = message.path ?? '/'; | ||||||||||||||||||||||||||||
| genUiLogger.info( | ||||||||||||||||||||||||||||
|
|
@@ -205,6 +211,15 @@ class GenUiManager implements GenUiHost { | |||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||
| final DataModel dataModel = dataModelForSurface(message.surfaceId); | ||||||||||||||||||||||||||||
| dataModel.update(DataPath(path), message.contents); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Notify UI of an update if the surface is already rendering | ||||||||||||||||||||||||||||
| final ValueNotifier<UiDefinition?> notifier = getSurfaceNotifier( | ||||||||||||||||||||||||||||
| message.surfaceId, | ||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||
| final UiDefinition? uiDefinition = notifier.value; | ||||||||||||||||||||||||||||
| if (uiDefinition != null && uiDefinition.rootComponentId != null) { | ||||||||||||||||||||||||||||
| _surfaceUpdates.add(SurfaceUpdated(message.surfaceId, uiDefinition)); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+215
to
+222
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This block of code can be made more concise. You can chain the call to
Suggested change
|
||||||||||||||||||||||||||||
| case SurfaceDeletion(): | ||||||||||||||||||||||||||||
| final String surfaceId = message.surfaceId; | ||||||||||||||||||||||||||||
| if (_surfaces.containsKey(surfaceId)) { | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -41,24 +41,19 @@ void main() { | |||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| final Future<GenUiUpdate> futureAdded = manager.surfaceUpdates.first; | ||||||||||||||||||||||||||||||||
| manager.handleMessage( | ||||||||||||||||||||||||||||||||
| SurfaceUpdate(surfaceId: surfaceId, components: components), | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
| final GenUiUpdate addedUpdate = await futureAdded; | ||||||||||||||||||||||||||||||||
| expect(addedUpdate, isA<SurfaceAdded>()); | ||||||||||||||||||||||||||||||||
| expect(addedUpdate.surfaceId, surfaceId); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| final Future<GenUiUpdate> futureUpdated = manager.surfaceUpdates.first; | ||||||||||||||||||||||||||||||||
| final Future<GenUiUpdate> futureUpdate = manager.surfaceUpdates.first; | ||||||||||||||||||||||||||||||||
| manager.handleMessage( | ||||||||||||||||||||||||||||||||
| const BeginRendering(surfaceId: surfaceId, root: 'root'), | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
| final GenUiUpdate updatedUpdate = await futureUpdated; | ||||||||||||||||||||||||||||||||
| final GenUiUpdate update = await futureUpdate; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| expect(updatedUpdate, isA<SurfaceUpdated>()); | ||||||||||||||||||||||||||||||||
| expect(updatedUpdate.surfaceId, surfaceId); | ||||||||||||||||||||||||||||||||
| final UiDefinition definition = | ||||||||||||||||||||||||||||||||
| (updatedUpdate as SurfaceUpdated).definition; | ||||||||||||||||||||||||||||||||
| expect(update, isA<SurfaceAdded>()); | ||||||||||||||||||||||||||||||||
| expect(update.surfaceId, surfaceId); | ||||||||||||||||||||||||||||||||
| final UiDefinition definition = (update as SurfaceAdded).definition; | ||||||||||||||||||||||||||||||||
| expect(definition, isNotNull); | ||||||||||||||||||||||||||||||||
| expect(definition.rootComponentId, 'root'); | ||||||||||||||||||||||||||||||||
| expect(manager.surfaces[surfaceId]!.value, isNotNull); | ||||||||||||||||||||||||||||||||
|
|
@@ -90,18 +85,17 @@ void main() { | |||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| final Future<GenUiUpdate> futureUpdate = manager.surfaceUpdates.first; | ||||||||||||||||||||||||||||||||
| expectLater( | ||||||||||||||||||||||||||||||||
| manager.surfaceUpdates, | ||||||||||||||||||||||||||||||||
| emitsInOrder([isA<SurfaceAdded>(), isA<SurfaceUpdated>()]), | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
|
Comment on lines
+88
to
+91
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test correctly verifies the order of events, but it no longer checks the content of the
Suggested change
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| manager.handleMessage( | ||||||||||||||||||||||||||||||||
| const BeginRendering(surfaceId: surfaceId, root: 'root'), | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
| manager.handleMessage( | ||||||||||||||||||||||||||||||||
| SurfaceUpdate(surfaceId: surfaceId, components: newComponents), | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
| final GenUiUpdate update = await futureUpdate; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| expect(update, isA<SurfaceUpdated>()); | ||||||||||||||||||||||||||||||||
| expect(update.surfaceId, surfaceId); | ||||||||||||||||||||||||||||||||
| final UiDefinition updatedDefinition = | ||||||||||||||||||||||||||||||||
| (update as SurfaceUpdated).definition; | ||||||||||||||||||||||||||||||||
| expect(updatedDefinition.components['root'], newComponents[0]); | ||||||||||||||||||||||||||||||||
| expect(manager.surfaces[surfaceId]!.value, updatedDefinition); | ||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling
getSurfaceNotifierhere has an unintended side effect: if aDataModelUpdatemessage is received for a surface that does not exist yet, a newValueNotifieris created and added to the_surfacesmap. This could lead to a memory leak if the surface is never formally created with aSurfaceUpdatemessage.To avoid this, you can directly access the
_surfacesmap to check for existence and retrieve the notifier without causing this side effect.