chore: riverpod lints from dcm (#30446)

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
shenlong 2026-08-05 01:26:49 +05:30 committed by GitHub
parent 00e813ba84
commit 8450687db7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 118 additions and 41 deletions

View file

@ -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

View file

@ -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));

View file

@ -102,6 +102,9 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
onPopInvokedWithResult: (didPop, _) async {
if (!didPop) {
await _handlePagePopped();
if (!mounted) {
return;
}
final user = ref.read(currentUserProvider);
if (user == null) {
@ -110,6 +113,10 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
final isBackupEnabled = SettingsRepository.instance.appConfig.backup.enabled;
await ref.read(driftBackupProvider.notifier).getBackupStatus(user.id);
if (!mounted) {
return;
}
final currentTotalAssetCount = ref.read(driftBackupProvider.select((p) => p.totalCount));
final totalChanged = currentTotalAssetCount != _initialTotalAssetCount;
final backupNotifier = ref.read(driftBackupProvider.notifier);

View file

@ -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(

View file

@ -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();
}
}

View file

@ -353,11 +353,11 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
},
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()]));
},
),

View file

@ -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(

View file

@ -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);

View file

@ -76,6 +76,10 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
}
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<RemoteAlbumPage> {
if (confirmed == true) {
try {
if (!context.mounted) {
return;
}
await ref.read(remoteAlbumProvider.notifier).deleteAlbum(_album.id);
if (!context.mounted) {
return;

View file

@ -300,7 +300,7 @@ class _DriftSlideshowPageState extends ConsumerState<DriftSlideshowPage> 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<DriftSlideshowPage> 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) {

View file

@ -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) {

View file

@ -70,6 +70,10 @@ class _ProfilePictureCropPageState extends ConsumerState<ProfilePictureCropPage>
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');

View file

@ -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;

View file

@ -746,7 +746,7 @@ class AddToAlbumHeader extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
Future<void> onCreateAlbum() async {
final albumName = await showDialog<String?>(context: context, builder: (context) => const NewAlbumNameModal());
if (albumName == null) {
if (albumName == null || !context.mounted) {
return;
}

View file

@ -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<String?>(
future: assetMediaRepository.getOriginalFilename(asset.id),
builder: (context, snapshot) {

View file

@ -150,6 +150,10 @@ class _NativeVideoViewerState extends ConsumerState<NativeVideoViewer> 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;

View file

@ -1,3 +1,5 @@
// ignore_for_file: use-ref-and-state-synchronously
import 'dart:async';
import 'package:auto_route/auto_route.dart';

View file

@ -1,3 +1,5 @@
// ignore_for_file: use-ref-and-state-synchronously
import 'dart:async';
import 'package:collection/collection.dart';

View file

@ -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';

View file

@ -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';

View file

@ -1,3 +1,5 @@
// ignore_for_file: use-ref-and-state-synchronously
import 'dart:async';
import 'dart:io';

View file

@ -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';

View file

@ -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));

View file

@ -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) {

View file

@ -45,12 +45,12 @@ class AppBarProfileInfoBox extends HookConsumerWidget {
Future<void> 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(

View file

@ -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(

View file

@ -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,

View file

@ -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<void> 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;
}

View file

@ -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();
}
}());

View file

@ -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), () {

View file

@ -39,11 +39,11 @@ class _FreeUpSpaceSettingsState extends ConsumerState<FreeUpSpaceSettings> {
}
Future<void> _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<FreeUpSpaceSettings> {
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<FreeUpSpaceSettings> {
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<FreeUpSpaceSettings> {
_DeleteConfirmationDialog(assetCount: state.assetsToDelete.length, cutoffDate: state.selectedDate!),
);
if (confirmed != true) {
if (confirmed != true || !context.mounted) {
return;
}

View file

@ -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();
}
}

View file

@ -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();
}

View file

@ -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;
}