{"record":{"id":"107ccac22d64a0e0","repo":"immich-app/immich","slug":"user-must-be-logged-in-to-access-locked-folder","errorCode":null,"errorMessage":"User must be logged in to access locked folder","messagePattern":"User must be logged in to access locked folder","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"mobile/lib/presentation/pages/locked_folder.page.dart","lineNumber":60,"sourceCode":"    }\n    if (state == AppLifecycleState.paused) {\n      unawaited(ref.read(authProvider.notifier).lockPinCode());\n      unawaited(context.navigateTo(const TabShellRoute()));\n      return;\n    }\n    setState(() {\n      _showOverlay = state != AppLifecycleState.resumed;\n    });\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return ProviderScope(\n      overrides: [\n        timelineServiceProvider.overrideWith((ref) {\n          final user = ref.watch(currentUserProvider);\n          if (user == null) {\n            throw Exception('User must be logged in to access locked folder');\n          }\n\n          final timelineService = ref.watch(timelineFactoryProvider).lockedFolder(user.id);\n          ref.onDispose(timelineService.dispose);\n          return timelineService;\n        }),\n      ],\n      child: _showOverlay\n          ? const SizedBox()\n          : PopScope(\n              onPopInvokedWithResult: (didPop, _) => didPop ? ref.read(authProvider.notifier).lockPinCode() : null,\n              child: Timeline(\n                appBar: MesmerizingSliverAppBar(title: context.t.locked_folder),\n                bottomSheet: const LockedFolderBottomSheet(),\n              ),\n            ),\n    );\n  }","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/mobile/lib/presentation/pages/locked_folder.page.dart#L42-L78","documentation":"This exception is thrown by the locked folder page's timelineServiceProvider override when currentUserProvider is null. The locked-folder timeline is user-scoped (timelineFactoryProvider.lockedFolder(user.id)), so it cannot be constructed without an authenticated user. The throw indicates the page was built outside of a valid login session.","triggerScenarios":"Building the locked folder page while currentUserProvider returns null — auth not yet initialized, session expired, or the route was pushed without a login gate.","commonSituations":"Opening the locked folder via deep link before sign-in completes; biometric/PIN unlock flow that re-authenticates the folder but the underlying account session already expired; auth stream error leaving currentUser permanently null.","solutions":["Add an auth guard so the locked folder route is unreachable while currentUser is null.","Re-check the account session before unlocking the folder (e.g. reauthentication should also refresh currentUserProvider).","Verify auth bootstrap/restoration completes before restoring navigation state to this page.","Log the user out fully and redirect to login when the session is detected as expired."],"exampleFix":"// before\nGoRouter(routes: [\n  GoRoute(path: '/locked-folder', builder: (_, __) => const LockedFolderPage()),\n])\n// after\nGoRouter(routes: [\n  GoRoute(path: '/locked-folder', redirect: (context, state) {\n    final user = ref.read(currentUserProvider);\n    return user == null ? '/login' : null;\n  }, builder: (_, __) => const LockedFolderPage()),\n])","handlingStrategy":"validation","validationCode":"final user = ref.read(currentUserProvider);\nif (user == null) {\n  context.go('/login');\n  return;\n}","typeGuard":"bool isSignedIn(User? user) => user != null && user.id.isNotEmpty;","tryCatchPattern":"try {\n  final service = ref.read(timelineServiceProvider);\n} catch (e) {\n  if (e.toString().contains('User must be logged in')) {\n    context.go('/login');\n  } else {\n    rethrow;\n  }\n}","preventionTips":["Require a valid account session before the folder unlock flow (unlock should reconfirm, not replace, sign-in).","Guard the locked-folder route with a redirect to login when currentUser is null.","Restore navigation state only after auth bootstrap completes.","Handle session expiry events by navigating to login immediately.","Add a signed-out deep-link test for this page."],"tags":["flutter","riverpod","authentication","null-user"],"backgroundTag":"authentication-required","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}