[web] HttpGet: read/decode failures are not reported to the image stream (placeholder forever, pending ImageCache entry)
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 45/100
Direzione di ricerca
Start in cached_network_image_web/lib/cached_network_image_web.dart, in _loadAsyncHttpGet, and review how file reads, decoding, stream errors, and controller closure are handled. The reproduction test in the issue shows the expected behavior: read or decode failures reach the image stream, evict the pending image, close both controllers, and leave no uncaught error; removing the broken cache entry is described as ideal.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
🐛 Bug Report
On web with ImageRenderMethodForWeb.HttpGet, a failure while reading the cached file or decoding its bytes is never reported to the image stream.
In cached_network_image_web/lib/cached_network_image_web.dart, _loadAsyncHttpGet handles a FileInfo like this:
event.file.readAsBytes().then((value) => decode(value)).then((data) {
streamController.add(data);
...
});
Neither future has an error handler. If the server returns 200 with bytes that are not a valid image (or the read fails), the exception goes to the zone as an uncaught error. The onError of the getFileStream subscription only covers errors from the download stream, so:
streamControllernever gets the error, soerrorWidget/errorListenerare not called and the placeholder stays foreverevictImageis not called, so the pending entry stays inImageCache; showing the same URL again reuses the pending completer and never retries- the broken file stays in the cache manager, so a later load reads the same bytes again
streamControllerandchunkEventsare never closed
With HtmlImage the same broken response is reported as an error, so switching to HttpGet changes how a bad image fails.
A related detail in the same function: when the cached FileInfo finishes decoding before the download stream calls onDone, the state goes waitingForData → closing after the decode has already completed, and nothing closes streamController / chunkEvents.
Expected behavior
A read or decode failure for a FileInfo is delivered to the returned stream as an error (so MultiImageStreamCompleter reports it), evictImage is called, and both controllers are closed. Ideally the broken cache entry is removed too, so the next attempt downloads again.
Reproduction steps
This VM test feeds the web ImageLoader a FileInfo with non-image bytes and a decoder that throws, as the real decoder does for such bytes. It fails on cached_network_image_web 2.0.3: the output is uncaught=[Bad state: invalid image bytes] streamErrors=[] evicted=0 done=false.
import 'dart:async';
import 'dart:ui' as ui;
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart'
show ImageRenderMethodForWeb;
import 'package:cached_network_image_web/cached_network_image_web.dart';
import 'package:file/memory.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_test/flutter_test.dart';
class FakeCacheManager implements BaseCacheManager {
final events = StreamController<FileResponse>();
@override
Stream<FileResponse> getFileStream(String url,
{String? key, Map<String, String>? headers, bool withProgress = false}) =>
events.stream;
@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
test('HttpGet: a decode failure should reach the image stream', () async {
final uncaught = <Object>[];
final streamErrors = <Object>[];
var evicted = 0;
var done = false;
await runZonedGuarded(() async {
final cache = FakeCacheManager();
final file = MemoryFileSystem().file('/broken.webp')
..writeAsBytesSync([0, 1, 2, 3]); // not an image
final stream = ImageLoader().loadImageAsync(
'https://example.com/broken.webp',
null,
StreamController<ImageChunkEvent>(),
(buffer, {getTargetSize}) async {
buffer.dispose();
throw StateError('invalid image bytes'); // what a real decoder does
},
cache,
null,
null,
null,
ImageRenderMethodForWeb.HttpGet,
() => evicted++,
);
stream.listen((_) {}, onError: streamErrors.add, onDone: () => done = true);
cache.events.add(FileInfo(file, FileSource.Online,
DateTime.now().add(const Duration(days: 1)), 'https://example.com/broken.webp'));
await cache.events.close();
await Future<void>.delayed(const Duration(milliseconds: 100));
}, (e, _) => uncaught.add(e));
print('uncaught=$uncaught streamErrors=$streamErrors evicted=$evicted done=$done');
expect(streamErrors, isNotEmpty);
expect(evicted, 1);
expect(done, isTrue);
expect(uncaught, isEmpty);
});
}
In an app, the visible symptom is a CachedNetworkImage that keeps showing its placeholder for a URL whose response body is not a valid image, and that does not recover when the widget is rebuilt or the screen is reopened (until the page is reloaded).
What we did locally
We vendored 2.0.3 and changed only _loadAsyncHttpGet: the read and decode are awaited inside try/catch; on failure it schedules evictImage, calls cacheManager.removeFile(cacheKey ?? url) (ignoring a failure there so it does not hide the original error), adds the original error and stack trace to streamController, and closes streamController and chunkEvents. It also counts in-flight decodes so that overlapping cached/refreshed FileInfos are both delivered before closing, and closes in onDone when every decode has already finished. Happy to open a PR with this change and tests if that would help.
Configuration
Version: cached_network_image 4.0.4, cached_network_image_web 2.0.3, cached_network_image_platform_interface 5.0.3 (the same code is on main today)
Flutter: 3.47.7 stable
Platform:
- 🌐 Web (CanvasKit,
ImageRenderMethodForWeb.HttpGet) - 📱 iOS
- 🤖 Android
- Lingua principale
- Dart
- Stelle
- 2.6k
- Fork
- 738
- Merge medio
- 11h 55m
- PR unite (30g)
- 14
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Nessuna guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di Baseflow/flutter_cached_network_image
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
Baseflow/flutter_cached_network_image#492 · 2 reazioni ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 68/100
Baseflow/flutter_cached_network_image#478 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
Baseflow/flutter_cached_network_image#473 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 Meno di un'ora Idoneità per principianti 22/100
Baseflow/flutter_cached_network_image#1061 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 55/100
Baseflow/flutter_cached_network_image#1032 ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di Baseflow/flutter_cached_network_image
Issue simili
-
Standalone dash flashes as its own RSVP step (e.g. Russian dialogue)Forse già presa @bezig01 l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
daniel-pimenta-cc/ledor#53 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
-
Iteration CounterApertaenhancement firebase
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
brysonddelozier/scribbles#24 ·
-
bug good first issue P2-Medium
Difficoltà 2/5 1-3 ore Idoneità per principianti 64/100
basictech01/uniun#305 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
daohoangson/flutter_widget_from_html#1657 ·
I maintainer di solito rispondono entro 3 giorni