Flutter 네트워크 이미지 캐싱
이미지 캐싱은 중요합니다.
캐싱을 통해서 불필요한 중복 접근을 막을 수 있고, 그렇게 함으로써 여러 비용을 절감할 수 있습니다.
매 번 요청할 떄 마다 이미지를 서버로부터 가져오게 되면, 요청 과정에서 네트워크 비용이나, 실질적인 요금 비용 등
다양한 비용이 발생할 수 있는데 비해서
한 번 이미지를 불러온 뒤에, 캐싱을 통해 이미지를 다시 받아오는 중복 작업을 피하는 과정을 이뤄낼 수 있습니다.
이미지 캐싱 구현에는 다양한 방법이 있습니다.
아래 코드는 동료 팀원분께서 저에게 제공해주신 감사한 코드입니다.
retry 횟수는 3번, timeout은 3초로 지정하여 진행하는 예제입니다.
Image.network()를 통해 곧바로 이미지를 받아오지 않고, http를 이용해 직접 읽어온 다음에
DefaultCacheManager를 이용하여, 해당 url에 대해 이전에 가져왔던 기록이 있으면 또 가져오지 않고 캐싱된 데이터를 이용합니다.
import 'package:flutter/foundation.dart';
import 'package:flutter/.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:http/http.dart' as http;
Future<Uint8List> fetchImage(String url,
{int retry = 3, int timeout = 3}) async {
int failCount = 0;
while (failCount < retry) {
try {
try {
var file = await DefaultCacheManager().getSingleFile(url);
if (await file.exists()) {
var res = await file.readAsBytes();
return res;
}
} catch (e) {
if (kDebugMode) {
print(e);
}
}
final response =
await http.get(Uri.parse(url)).timeout(Duration(seconds: timeout));
if (response.statusCode == 200) {
return response.bodyBytes;
} else {
throw Exception('Fail to load data.');
}
} catch (e) {
if (kDebugMode) {
print(e);
}
failCount++;
}
}
return (await rootBundle.load('images/sample.png')).buffer.asUint8List();
}
이렇게 timeout과 retry를 걸어놓았기 때문에, 이미지가 아직 업로드 중이거나 서버에 일시적인 오류가 생겨서 이미지를 불러올 수 없을때
곧바로 에러를 뿜지 않고, 추가로 요청하는 메커니즘을 가지는 것이 아주 좋은 방법인 것 같습니다.
만약 추가 작업이 모두 실패하게 된다면 sample.png 이미지가 보여지게 됩니다.
그리고 이 fetchImage 함수를 이용해서 Image Widget을 생성할 수 있습니다.
이 또한 마찬가지로 팀원분께서 제공해주신 코드입니다.
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:guysomedc/util/get_image_with_retry.dart';
class NetworkImageWithRetryTimeout extends StatefulWidget {
final String url;
final int retry;
final int timeout;
final BoxFit? fit;
const NetworkImageWithRetryTimeout(this.url,
{Key? key, this.retry = 10, this.timeout = 3, this.fit})
: super(key: key);
@override
State<NetworkImageWithRetryTimeout> createState() =>
_NetworkImageWithRetryTimeoutState();
}
class _NetworkImageWithRetryTimeoutState
extends State<NetworkImageWithRetryTimeout> {
late Future<Uint8List> image;
String oldUrl = '';
@override
void initState() {
super.initState();
oldUrl = widget.url;
image = fetchImage(widget.url);
}
@override
Widget build(BuildContext context) {
if (oldUrl != widget.url) {
oldUrl = widget.url;
image = fetchImage(
widget.url,
retry: widget.retry,
timeout: widget.timeout,
);
}
return FutureBuilder<Uint8List>(
future: image,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Image.memory(
snapshot.data as Uint8List,
fit: widget.fit ?? BoxFit.fill,
);
} else if (snapshot.hasError) {
return Image.asset('images/sample.png');
}
return Image.asset('images/sample.png');
},
);
}
}
팀원분께 정말 많이 배우고 있습니다.
제가 존경하는 분이기도 합니다. 가이섬.com 사이트를 만드신 분으로, 음원차트에 관심있으신 분들은 들어가보셔도 좋을 것 같습니다.
그리고 이렇게 직접 구현하는 방법 외에도, 플러터 codebook에서 알려주는 방법도 존재합니다.
기존에 Image.network를 쓰던 자리를 아래와 같이만 바꿔주면 됩니다.
CachedNetworkImage(
placeholder: (context, url) => const CircularProgressIndicator(), // 로딩 미리보기
imageUrl: 'https://picsum.photos/250?image=9', // 이미지 url
),
CacheNetworkImage 사용법은 간단합니다. 아래 링크를 남겨놓겠습니다.
첫번째 방법처럼 retry나 timeout을 설정하려며 추가로 코드를 작성해주시면 됩니다.
플러터 코드북에 참고자료
https://docs.flutter.dev/cookbook/images/cached-images