{"record":{"id":"a8e3db847d82d84f","repo":"immich-app/immich","slug":"user-must-be-logged-in-to-access-archive","errorCode":null,"errorMessage":"User must be logged in to access archive","messagePattern":"User must be logged in to access archive","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"mobile/lib/presentation/pages/archive.page.dart","lineNumber":22,"sourceCode":"import 'package:immich_mobile/generated/translations.g.dart';\nimport 'package:immich_mobile/presentation/widgets/bottom_sheet/archive_bottom_sheet.widget.dart';\nimport 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';\nimport 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';\nimport 'package:immich_mobile/providers/user.provider.dart';\nimport 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart';\n\n@RoutePage()\nclass ArchivePage extends StatelessWidget {\n  const ArchivePage({super.key});\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 archive');\n          }\n\n          final timelineService = ref.watch(timelineFactoryProvider).archive(user.id);\n          ref.onDispose(timelineService.dispose);\n          return timelineService;\n        }),\n      ],\n      child: Timeline(\n        appBar: MesmerizingSliverAppBar(title: context.t.archive, icon: Icons.archive_outlined),\n        bottomSheet: const ArchiveBottomSheet(),\n      ),\n    );\n  }\n}\n","sourceCodeStart":4,"sourceCodeEnd":37,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/mobile/lib/presentation/pages/archive.page.dart#L4-L37","documentation":"This exception is thrown by the archive page's timelineServiceProvider override in Riverpod when the currentUserProvider yields null. The app guards every timeline-backed page with an explicit login check: building the archive timeline service requires a non-null User because the archive timeline is keyed by user.id. If the auth state has not resolved to a logged-in user when the provider is first read, the widget tree build aborts with this error.","triggerScenarios":"Navigating to the archive page while currentUserProvider returns null — e.g. the auth stream has not yet emitted, the session was logged out, or the page is pushed before the app's login gate completes.","commonSituations":"Cold-start deep links straight to /archive before authentication initializes; token expiry causing the auth listener to clear the user while the page remains on the navigation stack; a misconfigured auth bootstrap (e.g. restore-session call skipped) so currentUser is always null.","solutions":["Ensure the app shows a login screen until currentUserProvider emits a non-null user (gate navigation on auth state).","Wrap the archive route in a redirect/guard that redirects unauthenticated users to the login page instead of building the page.","Verify currentUserProvider is wired to the real auth state source (e.g. auth state changes stream) and not left as a stub returning null.","Restore/persist the session on app start so a previously logged-in user is available immediately at first build."],"exampleFix":"// before\nNavigator.push(context, ArchivePage.route());\n// after\nfinal user = ref.read(currentUserProvider);\nif (user == null) {\n  Navigator.push(context, LoginPage.route(returnTo: ArchivePage.route()));\n} else {\n  Navigator.push(context, ArchivePage.route());\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":["Route all timeline pages behind an auth-aware router redirect.","Await session restoration before rendering the home shell.","Watch currentUserProvider in a top-level builder that swaps to a login screen when null.","Test deep links with a signed-out session to catch ungated routes.","Write integration tests asserting no timeline provider is read while logged out."],"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"}