immich-app/immich · error · Exception

User must be logged in to video

Error message

User must be logged in to video

What it means

Thrown by the timelineServiceProvider override in the Video page when currentUserProvider is null. The video timeline needs a user id (timelineFactoryProvider.video(user.id)), so an unauthenticated session makes the provider throw during initialization. It means the Video page was built without a logged-in user.

Solutions

  1. Ensure authentication before navigating to the video page.
  2. Make the page await the resolved auth AsyncValue instead of reading a possibly-null user.
  3. Re-authenticate (token refresh or login) on this error and retry navigation.
  4. Return a login prompt widget from the override when the user is null.

Example fix

// before
final user = ref.watch(currentUserProvider);
if (user == null) {
  throw Exception('User must be logged in to video');
}
// 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);
  // play video timeline
} catch (e) {
  if (e.toString().contains('User must be logged in')) {
    context.go('/login');
  } else {
    rethrow;
  }
}

Prevention

When it happens

Trigger: Opening the video page before login completes, after logout/token expiry, or when auth state restoration races with page navigation so currentUserProvider reads null.

Common situations: Expired session token; deep link to a video opened pre-login; app resumed from background after the session was invalidated.

Related errors


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

Appendix: source

Thrown at mobile/lib/presentation/pages/video.page.dart:21

import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/generated/translations.g.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 VideoPage extends StatelessWidget {
  const VideoPage({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 video');
          }

          final timelineService = ref.watch(timelineFactoryProvider).video(user.id);
          ref.onDispose(timelineService.dispose);
          return timelineService;
        }),
      ],
      child: Timeline(appBar: MesmerizingSliverAppBar(title: context.t.videos)),
    );
  }
}

View on GitHub (pinned to e55ac299a4)