From 94927aec11e91b631c65576f9b59907e29712755 Mon Sep 17 00:00:00 2001 From: Gaurav Saini Date: Tue, 24 Mar 2026 00:14:10 +1100 Subject: [PATCH] fix: reduce CPU usage on Manage page by avoiding per-snap SnapModel (#1998) The ManageAppActions widget was creating a full SnapModel for every installed snap tile, causing high CPU usage with 100+ snaps. Each SnapModel independently fetches data from snapd. This fix: - Uses Snap data from ManageSnapData directly for display decisions - Checks currentlyInstallingModelProvider for active changes instead of per-snap SnapModel - Only creates/reads SnapModel when an action button is actually pressed - Uses snap.apps.isNotEmpty for launchability instead of LaunchProvider --- .../lib/manage/manage_app_actions.dart | 64 +++++++------------ 1 file changed, 23 insertions(+), 41 deletions(-) diff --git a/packages/app_center/lib/manage/manage_app_actions.dart b/packages/app_center/lib/manage/manage_app_actions.dart index e28deff44..304677f50 100755 --- a/packages/app_center/lib/manage/manage_app_actions.dart +++ b/packages/app_center/lib/manage/manage_app_actions.dart @@ -46,10 +46,9 @@ class ManageAppActions extends ConsumerWidget { ); } - /// Builds snap action buttons using the per-snap [SnapModel]. Shows a loading - /// indicator while the snap model loads, an active change status when a snapd - /// operation is in progress, or the appropriate action buttons (update, open, - /// remove) otherwise. + /// Builds snap action buttons. Uses data from [snap] directly for display + /// decisions to avoid creating per-snap [SnapModel] instances on each tile. + /// Only creates a [SnapModel] when an action is actually performed. Widget _buildSnapActions( BuildContext context, WidgetRef ref, @@ -57,30 +56,21 @@ class ManageAppActions extends ConsumerWidget { Snap snap, String? updateVersion, ) { - final snapModel = ref.watch(snapModelProvider(snap.name)); - if (!snapModel.hasValue) { - return const Center( - child: SizedBox.square( - dimension: kLoaderMediumHeight, - child: YaruCircularProgressIndicator(), - ), - ); - } - final snapData = snapModel.value!; - final shouldQuitToUpdate = snapData.localSnap?.refreshInhibit != null; - final snapViewModel = ref.watch(snapModelProvider(snap.name).notifier); - final snapLauncher = snapData.localSnap == null - ? null - : ref.watch(launchProvider(snapData.localSnap!)); - final canOpen = snapLauncher?.isLaunchable ?? false; - final hasActiveChange = snapData.activeChangeId != null; + final currentlyInstalling = ref.watch(currentlyInstallingModelProvider); + final activeChangeData = currentlyInstalling[snap.name]; + final hasActiveChange = activeChangeData?.activeChangeId != null; + if (hasActiveChange) { return ActiveChangeStatus( snapName: snap.name, - activeChangeId: snapData.activeChangeId!, + activeChangeId: activeChangeData!.activeChangeId!, ); } + final shouldQuitToUpdate = snap.refreshInhibit != null; + final canOpen = snap.apps.isNotEmpty; + final hasUpdate = updateVersion != null; + return Row( mainAxisSize: MainAxisSize.min, children: [ @@ -90,34 +80,26 @@ class ManageAppActions extends ConsumerWidget { ], if (showOnlyUpdate) OutlinedButton( - onPressed: SnapAction.update.callback( - snapData, - snapViewModel, - snapLauncher, - context, - ), + onPressed: () => ref + .read(snapModelProvider(snap.name).notifier) + .refresh() + .then((_) {}), child: Text(SnapAction.update.label(l10n)), ), - if (!showOnlyUpdate && snapData.isInstalled) ...[ + if (!showOnlyUpdate) ...[ OutlinedButton( onPressed: canOpen - ? SnapAction.open.callback( - snapData, - snapViewModel, - snapLauncher, - context, - ) + ? () { + final launcher = ref.read(launchProvider(snap)); + if (launcher.isLaunchable) launcher.open(); + } : null, child: Text(SnapAction.open.label(l10n)), ), const SizedBox(width: kSpacing), OutlinedButton( - onPressed: SnapAction.remove.callback( - snapData, - snapViewModel, - snapLauncher, - context, - ), + onPressed: () => + ref.read(snapModelProvider(snap.name).notifier).remove(), child: Text(SnapAction.remove.label(l10n)), ), ],