{"record":{"id":"15fbead49322e4aa","repo":"immich-app/immich","slug":"failed-to-load-animated-codec-for-local-asset-key-id","errorCode":null,"errorMessage":"Failed to load animated codec for local asset ${key.id}","messagePattern":"Failed to load animated codec for local asset (.+?)","errorType":"exception","errorClass":"StateError","httpStatus":null,"severity":"error","filePath":"mobile/lib/presentation/widgets/images/local_image_provider.dart","lineNumber":193,"sourceCode":"    final previewRequest = request = LocalImageRequest(\n      localId: key.id,\n      size: _previewTarget(devicePixelRatio, false),\n      assetType: key.assetType,\n    );\n    yield* loadRequest(previewRequest, decode, isFinal: false);\n\n    if (isCancelled) {\n      return;\n    }\n\n    // always try original for animated, since previews don't support animation\n    final originalRequest = request = LocalImageRequest(localId: key.id, size: Size.zero, assetType: key.assetType);\n    final codec = await loadCodecRequest(originalRequest, isFinal: true);\n    if (codec == null) {\n      if (isCancelled) {\n        return;\n      }\n      throw StateError('Failed to load animated codec for local asset ${key.id}');\n    }\n    yield codec;\n  }\n\n  @override\n  bool operator ==(Object other) {\n    if (identical(this, other)) {\n      return true;\n    }\n    if (other is LocalFullImageProvider) {\n      return id == other.id &&\n          size == other.size &&\n          isAnimated == other.isAnimated &&\n          width == other.width &&\n          height == other.height &&\n          checksum == other.checksum;\n    }\n    return false;","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/mobile/lib/presentation/widgets/images/local_image_provider.dart#L175-L211","documentation":"local_image_provider's animated variant loads a codec for a local asset; when loadCodecRequest returns null (no decodable animated frame could be produced for the asset id), a StateError is thrown because an animated codec is required to emit.","triggerScenarios":"ImageStreamDecorator-style request for a local asset whose animated codec cannot be loaded: unsupported/unknown animated format, asset file unreadable, or decoder returned no frames for key.id.","commonSituations":"Displaying a corrupted or exotic animated asset (rare GIF/HEVC variants); asset deleted or moved after it was picked; plugin/decoder limitation on specific formats.","solutions":["Fall back to the static (non-animated) decode path when the animated codec is null","Pre-check the asset's mime/format and route unsupported animated formats to the static provider","Skip or placeholder the asset instead of throwing when it is unreadable"],"exampleFix":"// before\nfinal codec = await loadCodecRequest(originalRequest, isFinal: true);\nif (codec == null) throw StateError('Failed to load animated codec ...');\n// after\nfinal codec = await loadCodecRequest(originalRequest, isFinal: true) ??\n    await loadStaticCodec(originalRequest); // graceful fallback","handlingStrategy":"fallback","validationCode":"// Dart: confirm the asset is still present and decodable\nfinal exists = await fileExistsForAsset(key.id);\nif (!exists) return placeholderCodec();","typeGuard":null,"tryCatchPattern":"try {\n  yield* animatedFrames(key);\n} on StateError catch (e) {\n  log('animated decode failed: $e');\n  yield* staticFrames(key); // non-animated fallback\n}","preventionTips":["Verify asset existence before decode","Route unsupported animated formats to the static provider","Handle deletion/move of assets during image streaming","Keep image decoder plugins up to date"],"tags":["dart","flutter","image-codec","animated"],"backgroundTag":"unsupported-operation","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"}