From 8450687db7b894c80e7199e84c07598cae0cb97b Mon Sep 17 00:00:00 2001 From: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Date: Wed, 5 Aug 2026 01:26:49 +0530 Subject: [PATCH] chore: riverpod lints from dcm (#30446) Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> --- mobile/analysis_options.yaml | 6 ++++++ .../lib/pages/backup/drift_backup.page.dart | 13 ++++++++++-- .../drift_backup_album_selection.page.dart | 7 +++++++ mobile/lib/pages/common/download_panel.dart | 2 +- .../pages/common/headers_settings.page.dart | 3 ++- .../lib/pages/common/splash_screen.page.dart | 2 +- .../pages/download_info.page.dart | 2 +- .../pages/drift_album_options.page.dart | 8 ++++++-- .../pages/drift_remote_album.page.dart | 8 ++++++++ .../pages/drift_slideshow.page.dart | 4 ++-- .../pages/drift_user_selection.page.dart | 2 +- .../profile/profile_picture_crop.page.dart | 4 ++++ .../pages/search/drift_search.page.dart | 4 ++-- .../widgets/album/album_selector.widget.dart | 2 +- .../technical_details.widget.dart | 2 +- .../asset_viewer/video_viewer.widget.dart | 4 ++++ .../infrastructure/action.provider.dart | 2 ++ .../infrastructure/remote_album.provider.dart | 2 ++ .../infrastructure/setting.provider.dart | 2 ++ .../infrastructure/tag.provider.dart | 2 ++ mobile/lib/providers/permission.provider.dart | 2 ++ .../timeline/multiselect.provider.dart | 2 ++ .../backup/drift_album_info_list_tile.dart | 2 +- .../common/app_bar_dialog/app_bar_dialog.dart | 3 +++ .../app_bar_dialog/app_bar_profile_info.dart | 10 +++++----- .../widgets/common/immich_sliver_app_bar.dart | 8 ++++---- .../widgets/forms/change_password_form.dart | 8 ++++++-- .../lib/widgets/forms/login/login_form.dart | 20 ++++++++++++++----- .../widgets/settings/advanced_settings.dart | 2 +- .../drift_backup_settings.dart | 5 +++-- .../settings/free_up_space_settings.dart | 10 +++++++--- .../external_network_preference.dart | 2 +- .../settings/notification_setting.dart | 2 +- .../widgets/shared_link/shared_link_item.dart | 2 +- 34 files changed, 118 insertions(+), 41 deletions(-) diff --git a/mobile/analysis_options.yaml b/mobile/analysis_options.yaml index e828760789..4894e84403 100644 --- a/mobile/analysis_options.yaml +++ b/mobile/analysis_options.yaml @@ -110,3 +110,9 @@ dart_code_metrics: - prefer-const-border-radius - prefer-declaring-const-constructor - prefer-switch-expression + + # Riverpod + - avoid-ref-watch-outside-build + - prefer-immutable-provider-arguments + - use-ref-and-state-synchronously + - use-ref-read-synchronously diff --git a/mobile/lib/pages/backup/drift_backup.page.dart b/mobile/lib/pages/backup/drift_backup.page.dart index cc618dbe60..8a23bc285f 100644 --- a/mobile/lib/pages/backup/drift_backup.page.dart +++ b/mobile/lib/pages/backup/drift_backup.page.dart @@ -321,7 +321,7 @@ class _BackupAlbumSelectionCard extends ConsumerWidget { Widget buildSelectedAlbumName() { String text = "backup_controller_page_backup_selected".tr(); final albums = ref - .watch(backupAlbumProvider) + .read(backupAlbumProvider) .where((album) => album.backupSelection == BackupSelection.selected) .toList(); @@ -355,7 +355,7 @@ class _BackupAlbumSelectionCard extends ConsumerWidget { Widget buildExcludedAlbumName() { String text = "backup_controller_page_excluded".tr(); final albums = ref - .watch(backupAlbumProvider) + .read(backupAlbumProvider) .where((album) => album.backupSelection == BackupSelection.excluded) .toList(); @@ -403,6 +403,10 @@ class _BackupAlbumSelectionCard extends ConsumerWidget { trailing: ElevatedButton( onPressed: () async { await context.pushRoute(const DriftBackupAlbumSelectionRoute()); + if (!context.mounted) { + return; + } + final currentUser = ref.read(currentUserProvider); if (currentUser == null) { return; @@ -562,6 +566,11 @@ class _PreparingStatusState extends ConsumerState { final currentUser = ref.read(currentUserProvider); if (currentUser != null && mounted) { await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id); + if (!context.mounted) { + timer.cancel(); + _pollingTimer = null; + return; + } // Stop polling if processing count reaches 0 final updatedProcessingCount = ref.read(driftBackupProvider.select((p) => p.processingCount)); diff --git a/mobile/lib/pages/backup/drift_backup_album_selection.page.dart b/mobile/lib/pages/backup/drift_backup_album_selection.page.dart index 7667dbc3f1..052eb2c142 100644 --- a/mobile/lib/pages/backup/drift_backup_album_selection.page.dart +++ b/mobile/lib/pages/backup/drift_backup_album_selection.page.dart @@ -102,6 +102,9 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState p.totalCount)); final totalChanged = currentTotalAssetCount != _initialTotalAssetCount; final backupNotifier = ref.read(driftBackupProvider.notifier); diff --git a/mobile/lib/pages/common/download_panel.dart b/mobile/lib/pages/common/download_panel.dart index 267d32fc3d..2015948fad 100644 --- a/mobile/lib/pages/common/download_panel.dart +++ b/mobile/lib/pages/common/download_panel.dart @@ -17,7 +17,7 @@ class DownloadPanel extends ConsumerWidget { final tasks = ref.watch(downloadStateProvider.select((state) => state.taskProgress)).entries.toList(); void onCancelDownload(String id) { - unawaited(ref.watch(downloadStateProvider.notifier).cancelDownload(id)); + unawaited(ref.read(downloadStateProvider.notifier).cancelDownload(id)); } return Positioned( diff --git a/mobile/lib/pages/common/headers_settings.page.dart b/mobile/lib/pages/common/headers_settings.page.dart index f0b3f4b67f..aab1dcd751 100644 --- a/mobile/lib/pages/common/headers_settings.page.dart +++ b/mobile/lib/pages/common/headers_settings.page.dart @@ -93,8 +93,9 @@ class HeaderSettingsPage extends HookConsumerWidget { headersMap[key] = value; } + final apiService = ref.read(apiServiceProvider); await ref.read(settingsProvider).write(.networkCustomHeaders, headersMap); - await ref.read(apiServiceProvider).updateHeaders(); + await apiService.updateHeaders(); } } diff --git a/mobile/lib/pages/common/splash_screen.page.dart b/mobile/lib/pages/common/splash_screen.page.dart index 2c39883b7e..65a78aed8a 100644 --- a/mobile/lib/pages/common/splash_screen.page.dart +++ b/mobile/lib/pages/common/splash_screen.page.dart @@ -353,11 +353,11 @@ class SplashScreenPageState extends ConsumerState { }, onError: (exception) { log.severe('Failed to update auth info with access token: $accessToken'); - unawaited(ref.read(authProvider.notifier).logout()); if (!mounted) { return; } + unawaited(ref.read(authProvider.notifier).logout()); unawaited(context.router.replaceAll([const LoginRoute()])); }, ), diff --git a/mobile/lib/presentation/pages/download_info.page.dart b/mobile/lib/presentation/pages/download_info.page.dart index c2c63c7860..8073d3098c 100644 --- a/mobile/lib/presentation/pages/download_info.page.dart +++ b/mobile/lib/presentation/pages/download_info.page.dart @@ -17,7 +17,7 @@ class DownloadInfoPage extends ConsumerWidget { final tasks = ref.watch(downloadStateProvider.select((state) => state.taskProgress)).entries.toList(); void onCancelDownload(String id) { - unawaited(ref.watch(downloadStateProvider.notifier).cancelDownload(id)); + unawaited(ref.read(downloadStateProvider.notifier).cancelDownload(id)); } return Scaffold( diff --git a/mobile/lib/presentation/pages/drift_album_options.page.dart b/mobile/lib/presentation/pages/drift_album_options.page.dart index 9c5161fa8a..7b4c48a6b0 100644 --- a/mobile/lib/presentation/pages/drift_album_options.page.dart +++ b/mobile/lib/presentation/pages/drift_album_options.page.dart @@ -75,6 +75,10 @@ class DriftAlbumOptionsPage extends HookConsumerWidget { } try { + if (!context.mounted) { + return; + } + await ref.read(remoteAlbumProvider.notifier).addUsers(album.id, newUsers); ref.invalidate(remoteAlbumSharedUsersProvider(album.id)); if (!context.mounted) { @@ -137,7 +141,7 @@ class DriftAlbumOptionsPage extends HookConsumerWidget { Widget buildOwnerInfo() { if (isOwner) { - final owner = ref.watch(currentUserProvider); + final owner = ref.read(currentUserProvider); return ListTile( leading: owner != null ? UserCircleAvatar(user: owner) : const SizedBox(), title: Text(album.ownerName, style: const TextStyle(fontWeight: FontWeight.w500)), @@ -145,7 +149,7 @@ class DriftAlbumOptionsPage extends HookConsumerWidget { trailing: Text("owner", style: context.textTheme.labelLarge).t(context: context), ); } else { - final usersProvider = ref.watch(driftUsersProvider); + final usersProvider = ref.read(driftUsersProvider); return usersProvider.maybeWhen( data: (users) { final user = users.firstWhereOrNull((u) => u.id == album.ownerId); diff --git a/mobile/lib/presentation/pages/drift_remote_album.page.dart b/mobile/lib/presentation/pages/drift_remote_album.page.dart index 5d018561cc..1c05538bac 100644 --- a/mobile/lib/presentation/pages/drift_remote_album.page.dart +++ b/mobile/lib/presentation/pages/drift_remote_album.page.dart @@ -76,6 +76,10 @@ class _RemoteAlbumPageState extends ConsumerState { } try { + if (!context.mounted) { + return; + } + await ref.read(remoteAlbumProvider.notifier).addUsers(_album.id, newUsers); ref.invalidate(remoteAlbumSharedUsersProvider(_album.id)); if (!context.mounted) { @@ -133,6 +137,10 @@ class _RemoteAlbumPageState extends ConsumerState { if (confirmed == true) { try { + if (!context.mounted) { + return; + } + await ref.read(remoteAlbumProvider.notifier).deleteAlbum(_album.id); if (!context.mounted) { return; diff --git a/mobile/lib/presentation/pages/drift_slideshow.page.dart b/mobile/lib/presentation/pages/drift_slideshow.page.dart index 81c09bea67..e6c78796d3 100644 --- a/mobile/lib/presentation/pages/drift_slideshow.page.dart +++ b/mobile/lib/presentation/pages/drift_slideshow.page.dart @@ -300,7 +300,7 @@ class _DriftSlideshowPageState extends ConsumerState with Si borderRadius: BorderRadius.zero, minHeight: 5, value: - ref.watch(videoPlayerProvider(asset.heroTag).select((s) => s.position)).inMilliseconds / + ref.read(videoPlayerProvider(asset.heroTag).select((s) => s.position)).inMilliseconds / asset.duration.inMilliseconds, ); } @@ -374,7 +374,7 @@ class _DriftSlideshowPageState extends ConsumerState with Si builder: (context, value, _) => buildPhotoView(scale * (1.0 + value * _kenBurnsZoom)), ); } else { - final status = ref.watch(videoPlayerProvider(asset.heroTag).select((s) => s.status)); + final status = ref.read(videoPlayerProvider(asset.heroTag).select((s) => s.status)); final position = ref.read(videoPlayerProvider(asset.heroTag)).position; if (status == VideoPlaybackStatus.completed && isCurrent && position.inMicroseconds > 0) { diff --git a/mobile/lib/presentation/pages/drift_user_selection.page.dart b/mobile/lib/presentation/pages/drift_user_selection.page.dart index 19a450b435..a145b96a70 100644 --- a/mobile/lib/presentation/pages/drift_user_selection.page.dart +++ b/mobile/lib/presentation/pages/drift_user_selection.page.dart @@ -137,7 +137,7 @@ class DriftUserSelectionPage extends HookConsumerWidget { body: suggestedShareUsers.widgetWhen( onData: (users) { // Get shared users for this album from the database - final sharedUsers = ref.watch(remoteAlbumSharedUsersProvider(album.id)); + final sharedUsers = ref.read(remoteAlbumSharedUsersProvider(album.id)); return sharedUsers.when( data: (albumSharedUsers) { diff --git a/mobile/lib/presentation/pages/profile/profile_picture_crop.page.dart b/mobile/lib/presentation/pages/profile/profile_picture_crop.page.dart index a987c9ca29..523781b274 100644 --- a/mobile/lib/presentation/pages/profile/profile_picture_crop.page.dart +++ b/mobile/lib/presentation/pages/profile/profile_picture_crop.page.dart @@ -70,6 +70,10 @@ class _ProfilePictureCropPageState extends ConsumerState final croppedImage = await _cropController.croppedImage(); final pngBytes = await imageToUint8List(croppedImage); final xFile = XFile.fromData(pngBytes, mimeType: 'image/png'); + if (!context.mounted) { + return; + } + final success = await ref .read(uploadProfileImageProvider.notifier) .upload(xFile, fileName: 'profile-picture.png'); diff --git a/mobile/lib/presentation/pages/search/drift_search.page.dart b/mobile/lib/presentation/pages/search/drift_search.page.dart index 39588cf051..12935b4f23 100644 --- a/mobile/lib/presentation/pages/search/drift_search.page.dart +++ b/mobile/lib/presentation/pages/search/drift_search.page.dart @@ -749,7 +749,7 @@ class _SearchResultGrid extends ConsumerWidget { } Widget? _bottomWidget(BuildContext context, WidgetRef ref) { - final isLoading = ref.watch(paginatedSearchProvider.select((s) => s.isLoading)); + final isLoading = ref.read(paginatedSearchProvider.select((s) => s.isLoading)); if (isLoading) { return const SliverFillRemaining( @@ -761,7 +761,7 @@ class _SearchResultGrid extends ConsumerWidget { ); } - final hasMore = ref.watch(paginatedSearchProvider.select((s) => s.nextPage != null)); + final hasMore = ref.read(paginatedSearchProvider.select((s) => s.nextPage != null)); if (hasMore) { return null; diff --git a/mobile/lib/presentation/widgets/album/album_selector.widget.dart b/mobile/lib/presentation/widgets/album/album_selector.widget.dart index f30796e406..4187a0a495 100644 --- a/mobile/lib/presentation/widgets/album/album_selector.widget.dart +++ b/mobile/lib/presentation/widgets/album/album_selector.widget.dart @@ -746,7 +746,7 @@ class AddToAlbumHeader extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { Future onCreateAlbum() async { final albumName = await showDialog(context: context, builder: (context) => const NewAlbumNameModal()); - if (albumName == null) { + if (albumName == null || !context.mounted) { return; } diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_details/technical_details.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_details/technical_details.widget.dart index e97fa4889b..e92b671562 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_details/technical_details.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_details/technical_details.widget.dart @@ -72,7 +72,7 @@ class TechnicalDetails extends ConsumerWidget { final subtitleStyle = context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary); if (asset is LocalAsset) { - final assetMediaRepository = ref.watch(assetMediaRepositoryProvider); + final assetMediaRepository = ref.read(assetMediaRepositoryProvider); return FutureBuilder( future: assetMediaRepository.getOriginalFilename(asset.id), builder: (context, snapshot) { diff --git a/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart index 63b5663d9d..8b3d8a9978 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart @@ -150,6 +150,10 @@ class _NativeVideoViewerState extends ConsumerState with Widg final remoteAsset = videoAsset as RemoteAsset; final serverEndpoint = Store.get(StoreKey.serverEndpoint); + if (!context.mounted) { + return null; + } + final isOriginalVideo = ref.read(appConfigProvider).viewer.loadOriginalVideo; final String postfixUrl = isOriginalVideo ? 'original' : 'video/playback'; final String assetId = remoteAsset.livePhotoVideoId ?? remoteAsset.id; diff --git a/mobile/lib/providers/infrastructure/action.provider.dart b/mobile/lib/providers/infrastructure/action.provider.dart index 919836737f..3fb9f8acbc 100644 --- a/mobile/lib/providers/infrastructure/action.provider.dart +++ b/mobile/lib/providers/infrastructure/action.provider.dart @@ -1,3 +1,5 @@ +// ignore_for_file: use-ref-and-state-synchronously + import 'dart:async'; import 'package:auto_route/auto_route.dart'; diff --git a/mobile/lib/providers/infrastructure/remote_album.provider.dart b/mobile/lib/providers/infrastructure/remote_album.provider.dart index b392f5f401..4457b9f831 100644 --- a/mobile/lib/providers/infrastructure/remote_album.provider.dart +++ b/mobile/lib/providers/infrastructure/remote_album.provider.dart @@ -1,3 +1,5 @@ +// ignore_for_file: use-ref-and-state-synchronously + import 'dart:async'; import 'package:collection/collection.dart'; diff --git a/mobile/lib/providers/infrastructure/setting.provider.dart b/mobile/lib/providers/infrastructure/setting.provider.dart index 7d8be72cd0..9e33017cab 100644 --- a/mobile/lib/providers/infrastructure/setting.provider.dart +++ b/mobile/lib/providers/infrastructure/setting.provider.dart @@ -1,3 +1,5 @@ +// ignore_for_file: use-ref-and-state-synchronously + import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/domain/services/setting.service.dart'; diff --git a/mobile/lib/providers/infrastructure/tag.provider.dart b/mobile/lib/providers/infrastructure/tag.provider.dart index 2d527768c5..152582aa93 100644 --- a/mobile/lib/providers/infrastructure/tag.provider.dart +++ b/mobile/lib/providers/infrastructure/tag.provider.dart @@ -1,3 +1,5 @@ +// ignore_for_file: use-ref-and-state-synchronously + import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/tag.model.dart'; import 'package:immich_mobile/domain/services/tag.service.dart'; diff --git a/mobile/lib/providers/permission.provider.dart b/mobile/lib/providers/permission.provider.dart index dc82285122..bd4dc7b61e 100644 --- a/mobile/lib/providers/permission.provider.dart +++ b/mobile/lib/providers/permission.provider.dart @@ -1,3 +1,5 @@ +// ignore_for_file: use-ref-and-state-synchronously + import 'dart:async'; import 'dart:io'; diff --git a/mobile/lib/providers/timeline/multiselect.provider.dart b/mobile/lib/providers/timeline/multiselect.provider.dart index 64edd4cd3f..36de8d5e4f 100644 --- a/mobile/lib/providers/timeline/multiselect.provider.dart +++ b/mobile/lib/providers/timeline/multiselect.provider.dart @@ -1,3 +1,5 @@ +// ignore_for_file: use-ref-and-state-synchronously + import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; diff --git a/mobile/lib/widgets/backup/drift_album_info_list_tile.dart b/mobile/lib/widgets/backup/drift_album_info_list_tile.dart index 999b64e9de..41a215fb99 100644 --- a/mobile/lib/widgets/backup/drift_album_info_list_tile.dart +++ b/mobile/lib/widgets/backup/drift_album_info_list_tile.dart @@ -53,7 +53,7 @@ class DriftAlbumInfoListTile extends HookConsumerWidget { return GestureDetector( onDoubleTap: () { - ref.watch(hapticFeedbackProvider.notifier).selectionClick(); + ref.read(hapticFeedbackProvider.notifier).selectionClick(); if (isExcluded) { unawaited(ref.read(backupAlbumProvider.notifier).deselectAlbum(album)); diff --git a/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart b/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart index 53c1eb1af9..b390d2a423 100644 --- a/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart +++ b/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart @@ -124,6 +124,9 @@ class ImmichAppBarDialog extends HookConsumerWidget { onOk: () async { isLoggingOut.value = true; await ref.read(authProvider.notifier).logout().whenComplete(() => isLoggingOut.value = false); + if (!context.mounted) { + return; + } ref.read(websocketProvider.notifier).disconnect(); if (!context.mounted) { diff --git a/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart b/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart index c963564c69..51cdd7c5a3 100644 --- a/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart +++ b/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart @@ -45,12 +45,12 @@ class AppBarProfileInfoBox extends HookConsumerWidget { Future pickUserProfileImage() async { final XFile? image = await ImagePicker().pickImage(source: ImageSource.gallery, maxHeight: 1024, maxWidth: 1024); - if (image != null) { - final success = await ref.watch(uploadProfileImageProvider.notifier).upload(image); + if (image != null && context.mounted) { + final success = await ref.read(uploadProfileImageProvider.notifier).upload(image); - if (success) { + if (success && context.mounted) { final profileImagePath = ref.read(uploadProfileImageProvider).profileImagePath; - ref.watch(authProvider.notifier).updateUserProfileImagePath(profileImagePath); + ref.read(authProvider.notifier).updateUserProfileImagePath(profileImagePath); if (user != null) { unawaited(ref.read(currentUserProvider.notifier).refresh()); } @@ -61,7 +61,7 @@ class AppBarProfileInfoBox extends HookConsumerWidget { } void toggleReadonlyMode() { - final isReadonlyModeEnabled = ref.watch(readonlyModeProvider); + final isReadonlyModeEnabled = ref.read(readonlyModeProvider); ref.read(readonlyModeProvider.notifier).toggleReadonlyMode(); context.scaffoldMessenger.showSnackBar( diff --git a/mobile/lib/widgets/common/immich_sliver_app_bar.dart b/mobile/lib/widgets/common/immich_sliver_app_bar.dart index c5bce92cff..54495365db 100644 --- a/mobile/lib/widgets/common/immich_sliver_app_bar.dart +++ b/mobile/lib/widgets/common/immich_sliver_app_bar.dart @@ -113,7 +113,7 @@ class _ProfileIndicator extends ConsumerWidget { final isIpad = defaultTargetPlatform == TargetPlatform.iOS && !context.isMobile; void toggleReadonlyMode() { - final isReadonlyModeEnabled = ref.watch(readonlyModeProvider); + final isReadonlyModeEnabled = ref.read(readonlyModeProvider); ref.read(readonlyModeProvider.notifier).toggleReadonlyMode(); context.scaffoldMessenger.showSnackBar( @@ -193,11 +193,11 @@ class _BackupIndicator extends ConsumerWidget { } Widget? _getBackupBadgeIcon(BuildContext context, WidgetRef ref) { - final backupEnabled = ref.watch(appConfigProvider.select((c) => c.backup.enabled)); - final hasError = ref.watch(driftBackupProvider.select((state) => state.error != BackupError.none)); + final backupEnabled = ref.read(appConfigProvider.select((c) => c.backup.enabled)); + final hasError = ref.read(driftBackupProvider.select((state) => state.error != BackupError.none)); final isDarkTheme = context.isDarkTheme; final iconColor = isDarkTheme ? Colors.white : Colors.black; - final isUploading = ref.watch(driftBackupProvider.select((state) => state.uploadItems.isNotEmpty)); + final isUploading = ref.read(driftBackupProvider.select((state) => state.uploadItems.isNotEmpty)); if (!backupEnabled) { return _BadgeLabel( diff --git a/mobile/lib/widgets/forms/change_password_form.dart b/mobile/lib/widgets/forms/change_password_form.dart index dd307f4ede..b7a990e99d 100644 --- a/mobile/lib/widgets/forms/change_password_form.dart +++ b/mobile/lib/widgets/forms/change_password_form.dart @@ -69,12 +69,16 @@ class ChangePasswordForm extends HookConsumerWidget { return; } - await ref.read(authProvider.notifier).logout(); - ref.read(websocketProvider.notifier).disconnect(); if (!context.mounted) { return; } + await ref.read(authProvider.notifier).logout(); + if (!context.mounted) { + return; + } + + ref.read(websocketProvider.notifier).disconnect(); AutoRouter.of(context).back(); ImmichToast.show( context: context, diff --git a/mobile/lib/widgets/forms/login/login_form.dart b/mobile/lib/widgets/forms/login/login_form.dart index 18963f0cfd..e4c7516c43 100644 --- a/mobile/lib/widgets/forms/login/login_form.dart +++ b/mobile/lib/widgets/forms/login/login_form.dart @@ -101,9 +101,15 @@ class LoginForm extends HookConsumerWidget { try { final endpoint = await ref.read(authProvider.notifier).validateServerUrl(serverUrl); + if (!context.mounted) { + return; + } // Fetch and load server config and features await ref.read(serverInfoProvider.notifier).getServerInfo(); + if (!context.mounted) { + return; + } final serverInfo = ref.read(serverInfoProvider); final features = serverInfo.serverFeatures; @@ -264,6 +270,10 @@ class LoginForm extends HookConsumerWidget { await getManageMediaPermission(); } unawaited(handleSyncFlow()); + if (!context.mounted) { + return; + } + ref.read(websocketProvider.notifier).connect(); unawaited(ref.read(featureMessageServiceProvider).markSeen()); if (!context.mounted) { @@ -312,7 +322,7 @@ class LoginForm extends HookConsumerWidget { } Future oAuthLogin() async { - final oAuthService = ref.watch(oAuthServiceProvider); + final oAuthService = ref.read(oAuthServiceProvider); String? oAuthServerUrl; final state = generateRandomString(32); @@ -349,27 +359,27 @@ class LoginForm extends HookConsumerWidget { try { final loginResponseDto = await oAuthService.oAuthLogin(oAuthServerUrl, state, codeVerifier); - if (loginResponseDto == null) { + if (loginResponseDto == null || !context.mounted) { return; } log.info("Finished OAuth login with response: ${loginResponseDto.userEmail}"); final isSuccess = await ref - .watch(authProvider.notifier) + .read(authProvider.notifier) .saveAuthInfo(accessToken: loginResponseDto.accessToken); - if (isSuccess) { + if (isSuccess && context.mounted) { await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission(); if (isSyncRemoteDeletionsMode()) { await getManageMediaPermission(); } unawaited(handleSyncFlow()); - unawaited(ref.read(featureMessageServiceProvider).markSeen()); if (!context.mounted) { return; } + unawaited(ref.read(featureMessageServiceProvider).markSeen()); unawaited(context.router.replaceAll([const TabShellRoute()])); return; } diff --git a/mobile/lib/widgets/settings/advanced_settings.dart b/mobile/lib/widgets/settings/advanced_settings.dart index bf8673ccc5..56381164ae 100644 --- a/mobile/lib/widgets/settings/advanced_settings.dart +++ b/mobile/lib/widgets/settings/advanced_settings.dart @@ -60,7 +60,7 @@ class AdvancedSettings extends HookConsumerWidget { useEffect(() { unawaited(() async { isManageMediaSupported.value = await checkAndroidVersion(); - if (isManageMediaSupported.value) { + if (isManageMediaSupported.value && context.mounted) { manageMediaAndroidPermission.value = await ref.read(permissionRepositoryProvider).hasManageMediaPermission(); } }()); diff --git a/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart b/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart index f0f6435f90..10b20c31b0 100644 --- a/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart +++ b/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart @@ -68,8 +68,9 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton> }); try { - await ref.read(backgroundSyncProvider).syncLinkedAlbum(); - await ref.read(backgroundSyncProvider).syncRemote(); + final backgroundSync = ref.read(backgroundSyncProvider); + await backgroundSync.syncLinkedAlbum(); + await backgroundSync.syncRemote(); } catch (_) { } finally { Future.delayed(const Duration(seconds: 1), () { diff --git a/mobile/lib/widgets/settings/free_up_space_settings.dart b/mobile/lib/widgets/settings/free_up_space_settings.dart index 72e85ac821..8d4785d664 100644 --- a/mobile/lib/widgets/settings/free_up_space_settings.dart +++ b/mobile/lib/widgets/settings/free_up_space_settings.dart @@ -39,11 +39,11 @@ class _FreeUpSpaceSettingsState extends ConsumerState { } Future _initializeAlbumDefaults() async { + final notifier = ref.read(cleanupProvider.notifier); final albums = await ref.read(localAlbumProvider.future); final existingAlbumIds = albums.map((a) => a.id).toSet(); final albumsWithNames = albums.map((a) => (a.id, a.name)).toList(); - final notifier = ref.read(cleanupProvider.notifier); notifier.applyDefaultAlbumSelections(albumsWithNames); notifier.cleanupStaleAlbumIds(existingAlbumIds); } @@ -105,7 +105,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState { lastDate: DateTime.now(), ); - if (picked != null) { + if (picked != null && context.mounted) { ref.read(cleanupProvider.notifier).setSelectedDate(picked); setState(() => _hasScanned = false); } @@ -122,6 +122,10 @@ class _FreeUpSpaceSettingsState extends ConsumerState { ref.read(hapticFeedbackProvider.notifier).mediumImpact(); await ref.read(cleanupProvider.notifier).scanAssets(); + if (!mounted) { + return; + } + final state = ref.read(cleanupProvider); setState(() { @@ -146,7 +150,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState { _DeleteConfirmationDialog(assetCount: state.assetsToDelete.length, cutoffDate: state.selectedDate!), ); - if (confirmed != true) { + if (confirmed != true || !context.mounted) { return; } diff --git a/mobile/lib/widgets/settings/networking_settings/external_network_preference.dart b/mobile/lib/widgets/settings/networking_settings/external_network_preference.dart index 68278e0e1d..81ffb4b19b 100644 --- a/mobile/lib/widgets/settings/networking_settings/external_network_preference.dart +++ b/mobile/lib/widgets/settings/networking_settings/external_network_preference.dart @@ -36,7 +36,7 @@ class ExternalNetworkPreference extends HookConsumerWidget { entries.value[index] = entries.value[index].copyWith(url: url, status: status); await saveEndpointList(); - if (status == AuxCheckStatus.valid) { + if (status == AuxCheckStatus.valid && context.mounted) { await ref.read(apiServiceProvider).updateHeaders(); } } diff --git a/mobile/lib/widgets/settings/notification_setting.dart b/mobile/lib/widgets/settings/notification_setting.dart index 8c858a231b..0bab4e051a 100644 --- a/mobile/lib/widgets/settings/notification_setting.dart +++ b/mobile/lib/widgets/settings/notification_setting.dart @@ -47,7 +47,7 @@ class NotificationSetting extends HookConsumerWidget { subtileText: 'notification_permission_list_tile_content'.tr(), buttonText: 'notification_permission_list_tile_enable_button'.tr(), onButtonTap: () => - ref.watch(notificationPermissionProvider.notifier).requestNotificationPermission().then((permission) { + ref.read(notificationPermissionProvider.notifier).requestNotificationPermission().then((permission) { if (permission == PermissionStatus.permanentlyDenied) { showPermissionsDialog(); } diff --git a/mobile/lib/widgets/shared_link/shared_link_item.dart b/mobile/lib/widgets/shared_link/shared_link_item.dart index d419d6ead0..d98925bdf8 100644 --- a/mobile/lib/widgets/shared_link/shared_link_item.dart +++ b/mobile/lib/widgets/shared_link/shared_link_item.dart @@ -179,7 +179,7 @@ class SharedLinkItem extends ConsumerWidget { ), ); - if (confirmed == true) { + if (confirmed == true && context.mounted) { await ref.read(sharedLinksStateProvider.notifier).deleteLink(sharedLink.id); return true; }