immich-app/immich · error · Exception

User must be logged in to access archive

Error message

User must be logged in to access archive

What it means

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.

Solutions

  1. Ensure the app shows a login screen until currentUserProvider emits a non-null user (gate navigation on auth state).
  2. Wrap the archive route in a redirect/guard that redirects unauthenticated users to the login page instead of building the page.
  3. Verify currentUserProvider is wired to the real auth state source (e.g. auth state changes stream) and not left as a stub returning null.
  4. Restore/persist the session on app start so a previously logged-in user is available immediately at first build.

Example fix

// before
Navigator.push(context, ArchivePage.route());
// after
final user = ref.read(currentUserProvider);
if (user == null) {
  Navigator.push(context, LoginPage.route(returnTo: ArchivePage.route()));
} else {
  Navigator.push(context, ArchivePage.route());
}
Defensive patterns

Strategy: validation

Validate before calling

final user = ref.read(currentUserProvider);
if (user == null) {
  context.go('/login');
  return;
}

Type guard

bool isSignedIn(User? user) => user != null && user.id.isNotEmpty;

Try / catch

try {
  final service = ref.read(timelineServiceProvider);
} catch (e) {
  if (e.toString().contains('User must be logged in')) {
    context.go('/login');
  } else {
    rethrow;
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/a8e3db847d82d84f. Report an issue: GitHub.

Appendix: source

Thrown at mobile/lib/presentation/pages/archive.page.dart:22

import 'package:immich_mobile/generated/translations.g.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/archive_bottom_sheet.widget.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart';

@RoutePage()
class ArchivePage extends StatelessWidget {
  const ArchivePage({super.key});

  @override
  Widget build(BuildContext context) {
    return ProviderScope(
      overrides: [
        timelineServiceProvider.overrideWith((ref) {
          final user = ref.watch(currentUserProvider);
          if (user == null) {
            throw Exception('User must be logged in to access archive');
          }

          final timelineService = ref.watch(timelineFactoryProvider).archive(user.id);
          ref.onDispose(timelineService.dispose);
          return timelineService;
        }),
      ],
      child: Timeline(
        appBar: MesmerizingSliverAppBar(title: context.t.archive, icon: Icons.archive_outlined),
        bottomSheet: const ArchiveBottomSheet(),
      ),
    );
  }
}

View on GitHub (pinned to e55ac299a4)