The libarary is not respecting the cache-control headers
🐛 Bug Report
Expected behavior
on the server side I'm sending these headers along with the image file:
{
'cache-control': 'no-cache,max-age=1',
'etag': hash,
'age': '1',
'content-type': 'image/jpeg'
}
so what I expect is that each time I call CachedNetworkImageProvider the app would send a get request with an if-none-match to check if the ETag of the cached file was changed, but I'm not seeing this request on the server side, note that if I use the flutter_cache_manager package directly(through getSingleFile) with Image.memory then it works as expected, somehow it just breaks when using the flutter_cached_network_image package.
Reproduction steps
- Start a backend server that sends the following cache control HTTP header with 'no-cache' which should force the cache manager to validate the cached file each time it is requested:
import 'dart:io';
import 'dart:typed_data';
import "package:path/path.dart" show dirname, join;
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as io;
import 'package:crypto/crypto.dart' as crypto;
import 'package:shelf_router/shelf_router.dart' as shelf_router;
shelf_router.Router router = shelf_router.Router();
late HttpServer server;
String generateMd5(Uint8List data) {
crypto.Hash md5 = crypto.md5;
crypto.Digest digest = md5.convert(data);
return digest.toString();
}
void main(List<String> args) async {
server = await io.serve(router, InternetAddress.anyIPv4, 8081);
router.get('/avatar/<key>', avatarCallback);
}
avatarCallback(shelf.Request request, String key) async {
print("A request was made");
print(request.headers);
Uint8List avatar =
File(join(dirname(Platform.script.path), 'avatar.png')).readAsBytesSync();
String hash = generateMd5(avatar);
Map<String, Object> responseHeaders = {
'cache-control': 'no-cache,max-age=1',
'etag': hash,
'age': '1',
'content-type': 'image/png'
};
if (request.headers.containsKey('if-none-match')) {
print('found if-none-match');
print('request hash: ${request.headers['if-none-match']}');
print("local hash: ${hash}");
if (request.headers['if-none-match'] == hash) {
return shelf.Response(304, headers: responseHeaders);
}
}
return shelf.Response.ok(avatar, headers: responseHeaders);
}
I have added a print statement in a place where it is called each time a request is made to the file .
- below is a an example flutter app to show the problem:
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
BaseCacheManager def = DefaultCacheManager();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
HomePage({
Key? key,
}) : super(key: key);
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
String imageUrl = 'http://192.168.88.32:8081/avatar/0';
List<Widget> imagesChildren = [];
@override
void initState() {
// TODO: implement initState
super.initState();
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
}
@override
Widget build(BuildContext context) {
print('rebuilding');
print(imagesChildren.length);
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
TextButton(
onPressed: (() async {
Widget wid;
wid = Image.memory(
await def
.getSingleFile(imageUrl)
.then((value) => value.readAsBytesSync()),
width: 50,
height: 50,
);
setState(() {
imagesChildren.add(wid);
});
}),
child: Text('add using cache manager'),
),
TextButton(
onPressed: (() {
Widget wid;
wid = Image(
width: 50,
height: 50,
image: CachedNetworkImageProvider(imageUrl),
);
setState(() {
imagesChildren.add(wid);
});
}),
child: const Text('add using CachedNetworkImageProvider'),
),
ListView(
scrollDirection: Axis.vertical,
shrinkWrap: true,
children: [...imagesChildren],
),
],
),
),
),
);
}
}
- start the server and the flutter app, notice that when you click ' add using cache manager' multiple times, it works as expected(the app sends a request with the cached file's hash to check if the resource was updated, while if you click on the 'add using CachedNetworkImageProvider' multiple times, it only request the file in the first time, then it always shows the cached file and doesn't check the server for updates.
Configuration
Version: 1.x
Platform:
- [ x] :iphone: iOS
- [x ] :robot: Android
Not a single reaction since August 2022?
I have the same problem: When I update the image behind the same url, the server (firebase storage) sends a changed etag and last-modified header, but flutter_cached_network_image will not refetch the image. So if the image (profile image) is being used before stalePeriod exceeds, the image will never be updated.