diff --git a/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart b/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart index 02c0cf1740f..47cce6525b5 100644 --- a/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart +++ b/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart @@ -314,6 +314,8 @@ class _ExtensionIFrameController extends DisposableController '${showBannerMessageEvent.messageId}', ), screenId: '${showBannerMessageEvent.extensionName}_ext', + dismissOnConnectionChanges: + showBannerMessageEvent.dismissOnConnectionChanges, buildTextSpans: (_) => [ TextSpan( diff --git a/packages/devtools_app/lib/src/framework/observer/memory_observer.dart b/packages/devtools_app/lib/src/framework/observer/memory_observer.dart index fa251a28b8d..148f86686cc 100644 --- a/packages/devtools_app/lib/src/framework/observer/memory_observer.dart +++ b/packages/devtools_app/lib/src/framework/observer/memory_observer.dart @@ -151,6 +151,7 @@ class _MemoryPressureBannerMessage extends banner_messages.BannerWarning { : super( screenId: banner_messages.universalScreenId, key: _messageKey, + dismissOnConnectionChanges: false, buildTextSpans: (context) { final limitAsBytes = convertBytes( MemoryObserver._memoryPressureLimitGb, diff --git a/packages/devtools_app/lib/src/shared/managers/banner_messages.dart b/packages/devtools_app/lib/src/shared/managers/banner_messages.dart index 627c540b8e1..5d468e4e6db 100644 --- a/packages/devtools_app/lib/src/shared/managers/banner_messages.dart +++ b/packages/devtools_app/lib/src/shared/managers/banner_messages.dart @@ -30,7 +30,33 @@ const _cpuSamplingRateDocsUrl = /// every screen from the [BannerMessages] widget. const universalScreenId = 'universal'; -class BannerMessagesController { +class BannerMessagesController extends DisposableController + with AutoDisposeControllerMixin { + BannerMessagesController() { + previouslyConnected = + serviceConnection.serviceManager.connectedState.value.connected; + addAutoDisposeListener(serviceConnection.serviceManager.connectedState, () { + final connected = + serviceConnection.serviceManager.connectedState.value.connected; + if (previouslyConnected != connected) { + for (final messageList in _messages.values) { + for (final message in messageList.value) { + if (message.dismissOnConnectionChanges) { + bannerMessages.removeMessage(message); + } + } + } + } + previouslyConnected = connected; + }); + } + + /// Tracks the previous app connection state for the purpose of dismissing + /// messages upon connection changes. + /// + /// See [BannerMessage.dismissOnConnectionChanges]. + bool previouslyConnected = false; + final _messages = >{}; final _dismissedMessageKeys = {}; @@ -165,6 +191,7 @@ class BannerMessage extends StatelessWidget { required this.buildTextSpans, required this.screenId, required this.messageType, + this.dismissOnConnectionChanges = true, this.buildActions, }); @@ -173,6 +200,9 @@ class BannerMessage extends StatelessWidget { final String screenId; final BannerMessageType messageType; + /// Whether this message should be dismissed on app connection changes. + final bool dismissOnConnectionChanges; + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -284,6 +314,7 @@ class BannerWarning extends BannerMessage { required super.key, required super.buildTextSpans, required super.screenId, + super.dismissOnConnectionChanges = true, super.buildActions, }) : super(messageType: BannerMessageType.warning); } @@ -293,6 +324,7 @@ class BannerInfo extends BannerMessage { required super.key, required super.buildTextSpans, required super.screenId, + super.dismissOnConnectionChanges = true, super.buildActions, }) : super(messageType: BannerMessageType.info); } @@ -322,23 +354,6 @@ class DebugModePerformanceMessage extends BannerWarning { ); } -class ProviderUnknownErrorBanner extends _BannerError { - ProviderUnknownErrorBanner({required super.screenId}) - : super( - key: Key('ProviderUnknownErrorBanner - $screenId'), - buildTextSpans: - (_) => [ - TextSpan( - text: ''' -DevTools failed to connect with package:provider. - -This could be caused by an older version of package:provider; please make sure that you are using version >=5.0.0.''', - style: TextStyle(fontSize: defaultFontSize), - ), - ], - ); -} - class ShaderJankMessage extends _BannerError { ShaderJankMessage({ required super.screenId, @@ -520,7 +535,6 @@ class WelcomeToNewInspectorMessage extends BannerInfo { WelcomeToNewInspectorMessage({required super.screenId}) : super( key: Key('WelcomeToNewInspectorMessage - $screenId'), - buildTextSpans: (context) => [ const TextSpan( diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index aae3a2f89c1..5673a98406e 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md @@ -17,6 +17,7 @@ To learn more about DevTools, check out the - Prevent web apps from remaining paused after triggering a hot-restart from DevTools. - [#9125](https://github.com/flutter/devtools/pull/9125) +- Dismiss stale banner messages when the connected app state changes. - [#9148](https://github.com/flutter/devtools/pull/9148) ## Inspector updates diff --git a/packages/devtools_app/test/framework/observer/memory_observer_test.dart b/packages/devtools_app/test/framework/observer/memory_observer_test.dart index fba17a54b54..0679976c6e5 100644 --- a/packages/devtools_app/test/framework/observer/memory_observer_test.dart +++ b/packages/devtools_app/test/framework/observer/memory_observer_test.dart @@ -46,6 +46,7 @@ void main() { debugMeasureUsageInBytes: testMeasureMemoryUsage, pollingDuration: const Duration(milliseconds: 1), ); + setGlobal(ServiceConnectionManager, FakeServiceConnectionManager()); setGlobal(BannerMessagesController, BannerMessagesController()); offlineDataController = MockOfflineDataController(); offlineDataController.showingOfflineData.value = false; diff --git a/packages/devtools_app/test/screens/memory/framework/memory_screen_test.dart b/packages/devtools_app/test/screens/memory/framework/memory_screen_test.dart index e47552fb7da..79befb2bf5c 100644 --- a/packages/devtools_app/test/screens/memory/framework/memory_screen_test.dart +++ b/packages/devtools_app/test/screens/memory/framework/memory_screen_test.dart @@ -87,10 +87,10 @@ void main() { setGlobal(OfflineDataController, OfflineDataController()); setGlobal(IdeTheme, IdeTheme()); setGlobal(NotificationService, NotificationService()); - setGlobal(BannerMessagesController, BannerMessagesController()); setGlobal(DTDManager, MockDTDManager()); setGlobal(ScriptManager, MockScriptManager()); setUpServiceManagerForMemory(); + setGlobal(BannerMessagesController, BannerMessagesController()); }); testWidgets('builds its tab', (WidgetTester tester) async { diff --git a/packages/devtools_app/test/screens/performance/performance_screen_test.dart b/packages/devtools_app/test/screens/performance/performance_screen_test.dart index 20c02224080..8c93288bb13 100644 --- a/packages/devtools_app/test/screens/performance/performance_screen_test.dart +++ b/packages/devtools_app/test/screens/performance/performance_screen_test.dart @@ -37,6 +37,7 @@ void main() { setGlobal(PreferencesController, PreferencesController()); setGlobal(OfflineDataController, OfflineDataController()); setGlobal(NotificationService, NotificationService()); + setGlobal(ServiceConnectionManager, FakeServiceConnectionManager()); setGlobal(BannerMessagesController, BannerMessagesController()); }); diff --git a/packages/devtools_app/test/shared/managers/banner_messages_test.dart b/packages/devtools_app/test/shared/managers/banner_messages_test.dart index 8bbc62ea0c2..dedf7e206cb 100644 --- a/packages/devtools_app/test/shared/managers/banner_messages_test.dart +++ b/packages/devtools_app/test/shared/managers/banner_messages_test.dart @@ -160,6 +160,32 @@ void main() { expect(find.byKey(k1), findsOneWidget); }, ); + + testWidgets('messages dismiss on connection changes', ( + WidgetTester tester, + ) async { + expect( + fakeServiceConnection.serviceManager.connectedState.value.connected, + true, + ); + await tester.pumpWidget(buildBannerMessages()); + expect(find.byKey(k1), findsNothing); + expect(find.byKey(k2), findsNothing); + + // Add a message that should dismiss on connection changes. + bannerMessages.addMessage(testMessage1); + // Add a message that should not dismiss on connection changes. + bannerMessages.addMessage(testMessageNoDismiss); + await pumpTestFrame(tester); + expect(find.byKey(k1), findsOneWidget); + expect(find.byKey(k2), findsOneWidget); + + // Simulate a connection loss. + await fakeServiceConnection.serviceManager.manuallyDisconnect(); + await pumpTestFrame(tester); + expect(find.byKey(k1), findsNothing); + expect(find.byKey(k2), findsOneWidget); + }); }); } @@ -184,6 +210,14 @@ final testMessage2 = BannerMessage( messageType: BannerMessageType.warning, ); +final testMessageNoDismiss = BannerMessage( + key: k2, + buildTextSpans: (_) => const [TextSpan(text: 'Test Message 2')], + screenId: testMessage2ScreenId, + messageType: BannerMessageType.warning, + dismissOnConnectionChanges: false, +); + final universalMessage = BannerMessage( key: kUniversal, buildTextSpans: (_) => const [TextSpan(text: 'Universal Message')], diff --git a/packages/devtools_app/test/test_infra/scenes/cpu_profiler/default.dart b/packages/devtools_app/test/test_infra/scenes/cpu_profiler/default.dart index 7c8bb9435a4..3bced37f561 100644 --- a/packages/devtools_app/test/test_infra/scenes/cpu_profiler/default.dart +++ b/packages/devtools_app/test/test_infra/scenes/cpu_profiler/default.dart @@ -41,7 +41,6 @@ class CpuProfilerDefaultScene extends Scene { setGlobal(IdeTheme, IdeTheme()); setGlobal(NotificationService, NotificationService()); setGlobal(PreferencesController, PreferencesController()); - setGlobal(BannerMessagesController, BannerMessagesController()); fakeServiceConnection = FakeServiceConnectionManager( service: FakeServiceManager.createFakeService( @@ -59,6 +58,7 @@ class CpuProfilerDefaultScene extends Scene { fakeServiceConnection.errorBadgeManager.errorCountNotifier('profiler'), ).thenReturn(ValueNotifier(0)); setGlobal(ServiceConnectionManager, fakeServiceConnection); + setGlobal(BannerMessagesController, BannerMessagesController()); final mockScriptManager = MockScriptManager(); when( diff --git a/packages/devtools_app/test/test_infra/scenes/memory/default.dart b/packages/devtools_app/test/test_infra/scenes/memory/default.dart index 7562a661d33..2ce1ef5db17 100644 --- a/packages/devtools_app/test/test_infra/scenes/memory/default.dart +++ b/packages/devtools_app/test/test_infra/scenes/memory/default.dart @@ -106,7 +106,6 @@ class MemoryDefaultScene extends Scene { setGlobal(OfflineDataController, OfflineDataController()); setGlobal(IdeTheme, IdeTheme()); setGlobal(NotificationService, NotificationService()); - setGlobal(BannerMessagesController, BannerMessagesController()); setGlobal( PreferencesController, PreferencesController()..memory.showChart.value = false, @@ -138,6 +137,7 @@ class MemoryDefaultScene extends Scene { fakeServiceConnection.serviceManager.vm.operatingSystem, ).thenReturn('ios'); setGlobal(ServiceConnectionManager, fakeServiceConnection); + setGlobal(BannerMessagesController, BannerMessagesController()); setGlobal(OfflineDataController, OfflineDataController()); final showAllFilter = ClassFilter( diff --git a/packages/devtools_extensions/CHANGELOG.md b/packages/devtools_extensions/CHANGELOG.md index b6e27c08e50..bdb1090cd4d 100644 --- a/packages/devtools_extensions/CHANGELOG.md +++ b/packages/devtools_extensions/CHANGELOG.md @@ -5,6 +5,8 @@ found in the LICENSE file or at https://developers.google.com/open-source/licens --> ## 0.3.2 (not released) * Bump `devtools_app_shared` dependency to `0.4.0`. +* Add `ShowBannerMessageExtensionEvent.dismissOnConnectionChanges` field. This +value is set from an optional parameter in the constructor (defaults to `true`). ## 0.3.1 * Bump `vm_service` dependency to `>=13.0.0 <16.0.0`. diff --git a/packages/devtools_extensions/lib/src/api/model.dart b/packages/devtools_extensions/lib/src/api/model.dart index 7280f83e7cb..7dd9c1fc4f1 100644 --- a/packages/devtools_extensions/lib/src/api/model.dart +++ b/packages/devtools_extensions/lib/src/api/model.dart @@ -92,6 +92,7 @@ class ShowBannerMessageExtensionEvent extends DevToolsExtensionEvent { required String message, required String extensionName, bool ignoreIfAlreadyDismissed = true, + bool dismissOnConnectionChanges = true, }) : assert(bannerMessageType == 'warning' || bannerMessageType == 'error'), super( DevToolsExtensionEventType.showBannerMessage, @@ -101,6 +102,7 @@ class ShowBannerMessageExtensionEvent extends DevToolsExtensionEvent { _messageKey: message, _extensionNameKey: extensionName, _ignoreIfAlreadyDismissedKey: ignoreIfAlreadyDismissed, + _dismissOnConnectionChangesKey: dismissOnConnectionChanges, }, ); @@ -113,12 +115,15 @@ class ShowBannerMessageExtensionEvent extends DevToolsExtensionEvent { final extensionName = eventData.checkValid(_extensionNameKey); final ignoreIfAlreadyDismissed = (eventData[_ignoreIfAlreadyDismissedKey] as bool?) ?? true; + final dismissOnConnectionChanges = + (eventData[_dismissOnConnectionChangesKey] as bool?) ?? true; return ShowBannerMessageExtensionEvent( id: id, bannerMessageType: type, message: message, extensionName: extensionName, ignoreIfAlreadyDismissed: ignoreIfAlreadyDismissed, + dismissOnConnectionChanges: dismissOnConnectionChanges, ); } @@ -127,6 +132,7 @@ class ShowBannerMessageExtensionEvent extends DevToolsExtensionEvent { static const _bannerMessageTypeKey = 'bannerMessageType'; static const _extensionNameKey = 'extensionName'; static const _ignoreIfAlreadyDismissedKey = 'ignoreIfAlreadyDismissed'; + static const _dismissOnConnectionChangesKey = 'dismissOnConnectionChanges'; String get messageId => data![_idKey] as String; String get bannerMessageType => data![_bannerMessageTypeKey] as String; @@ -134,6 +140,10 @@ class ShowBannerMessageExtensionEvent extends DevToolsExtensionEvent { String get extensionName => data![_extensionNameKey] as String; bool get ignoreIfAlreadyDismissed => (data![_ignoreIfAlreadyDismissedKey] as bool?) ?? true; + + /// Whether this message should be dismissed on app connection changes. + bool get dismissOnConnectionChanges => + (data![_dismissOnConnectionChangesKey] as bool?) ?? true; } /// An extension event of type [DevToolsExtensionEventType.copyToClipboard]