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

Thrown by the timelineServiceProvider override in the map bottom sheet when currentUserProvider is null. Building the map timeline (users list, TimelineMapOptions stream) requires a user id, so an unauthenticated session makes the provider throw during build. It indicates the map sheet was opened without a logged-in user.

Solutions

  1. Only open the map sheet when a user is authenticated (check currentUserProvider first).
  2. Await the auth AsyncValue before building the sheet's provider overrides.
  3. Re-authenticate on this error and reopen the sheet.
  4. Render a login prompt / empty state instead of throwing when user is null.

Example fix

// before
final user = ref.watch(currentUserProvider);
if (user == null) {
  throw Exception('User must be logged in to access archive');
}
// after
final user = ref.watch(currentUserProvider);
if (user == null) {
  return const SizedBox.shrink(); // or redirect to login
}
Defensive patterns

Strategy: try-catch

Validate before calling

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

Type guard

bool isAuthenticated(User? u) => u != null && u.id.isNotEmpty;

Try / catch

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

Prevention

When it happens

Trigger: Opening the map bottom sheet while currentUserProvider is null — before login finishes, after logout/token expiry, or when auth state restoration hasn't completed when the sheet builds.

Common situations: Session expired in background then user taps the map button; deep link opening the map pre-login; stale credentials after a server-side logout.

Related errors


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

Appendix: source

Thrown at mobile/lib/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart:49

      backgroundColor: context.themeData.colorScheme.surface,
      slivers: const [
        SliverFillRemaining(hasScrollBody: false, child: SizedBox(height: 0, child: _ScopedMapTimeline())),
      ],
    );
  }
}

class _ScopedMapTimeline extends StatelessWidget {
  const _ScopedMapTimeline();

  @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 withPartners = ref.watch(mapStateProvider.select((s) => s.withPartners));
          final users = withPartners ? ref.watch(timelineUsersProvider).valueOrNull ?? [user.id] : [user.id];

          final optionsController = StreamController<TimelineMapOptions>.broadcast();
          ref.onDispose(optionsController.close);

          var currentOptions = ref.read(mapStateProvider).toOptions();

          ref.listen(mapStateProvider.select((state) => state.toOptions()), (_, newOptions) {
            currentOptions = newOptions;
            optionsController.add(newOptions);
          });

          final timelineService = ref
              .watch(timelineFactoryProvider)
              .geographicMap(users, () => currentOptions, optionsController.stream);

View on GitHub (pinned to e55ac299a4)