Flutterで画像をキャッシュする方法
画面上にコンテンツのサムネイル画像を載せ、クリックするとそのコンテンツに飛ぶUIはスマホでよく見られる。サムネイルはそれほど大きくない画像であるが、毎回ネットワークから取得するのはトラフィック上もユーザー体験上もよろしくない。そこで用いられるのがキャッシュだ。
iOSのSwiftUIではAsyncImage
が画像のキャッシュを内部的に処理する。またAndroidのJetpack ComposeではCoilというイメージキャッシュライブラリを使ってメモリ上、ドライブ上のキャッシュを実現する。Flutterには標準ではキャッシュ機能が組み込まれていない。そのため、以下の方法を使うのが一般的だ。
1. cached_network_image
パッケージ
Flutterで最もよく使われる画像キャッシュライブラリは cached_network_image
だ。
特徴
- ネットワーク画像をダウンロードしてキャッシュ
- 一度ロードした画像は、ローカルストレージに保存され、次回以降はキャッシュを利用
- プレースホルダーやエラーハンドリングも可能
使い方
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
class CachedImageExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CachedNetworkImage(
imageUrl: 'https://example.com/sample.jpg',
placeholder: (context, url) => CircularProgressIndicator(), // 読み込み中
errorWidget: (context, url, error) => Icon(Icons.error), // エラー時
);
}
}
2. flutter_cache_manager パッケージ
cached_network_image
は flutter_cache_manager
を内部で利用しているが、カスタマイズしたキャッシュ管理をしたい場合は直接 flutter_cache_manager
を使用することもできる。
基本的な使い方
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
class CacheManagerExample extends StatelessWidget {
final BaseCacheManager cacheManager = DefaultCacheManager();
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: cacheManager.getSingleFile('https://example.com/sample.jpg'),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) {
return Image.file(snapshot.data as File);
} else {
return CircularProgressIndicator();
}
},
);
}
}
カスタムキャッシュ
class MyCacheManager extends CacheManager {
static const key = "customCacheKey";
static MyCacheManager _instance = MyCacheManager._();
factory MyCacheManager() => _instance;
MyCacheManager._()
: super(
Config(
key,
stalePeriod: Duration(days: 7), // キャッシュの有効期間
maxNrOfCacheObjects: 100, // キャッシュする最大オブジェクト数
),
);
}
3. Image.network のメモリキャッシュ
Flutterの標準ウィジェット Image.network
もデフォルトでメモリキャッシュを利用する。ただし、アプリを終了するとキャッシュがクリアされるため、永続的なキャッシュには cached_network_image
の方が適している。
Image.network('https://example.com/sample.jpg');
どの方法を選ぶべきか?
方法 | 特徴 |
---|---|
cached_network_image |
最も簡単で、一般的に推奨される方法。自動でキャッシュ管理 |
flutter_cache_manager | より細かいキャッシュ管理が必要な場合に使用 |
Image.network | 単純なメモリキャッシュのみで十分な場合 |
キャッシュ方法のまとめ
SwiftUIの AsyncImage
に相当するキャッシュ機能は、Flutterでは cached_network_image
を使うのが最も手軽で推奨される。
プロジェクトでどのようにキャッシュを管理したいかによるが、永続的なキャッシュが必要なら cached_network_image
を使うのがベスト。cached_network_image
は flutter_cache_manager
を内部的に利用しているため、画像のキャッシュをチェックし、なければネットワークから取得する という流れを実現できる。
この流れの実装例を見ていこう。
実装方法
1. flutter_cache_manager
を使ってキャッシュをチェック
- 画像の URL または ID をキーとしてキャッシュを検索。
- キャッシュに 画像がある場合はそれを使い、なければネットワークからダウンロード。
2. cached_network_image
でキャッシュを利用
cached_network_image
はflutter_cache_manager
をデフォルトで利用するため、組み合わせることで 効率的な画像管理 が可能。
コード実装
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
class CachedImageLoader extends StatefulWidget {
final String imageId;
final String imageUrl;
const CachedImageLoader({
required this.imageId,
required this.imageUrl,
Key? key,
}) : super(key: key);
@override
_CachedImageLoaderState createState() => _CachedImageLoaderState();
}
class _CachedImageLoaderState extends State<CachedImageLoader> {
final BaseCacheManager _cacheManager = DefaultCacheManager();
File? _cachedFile;
@override
void initState() {
super.initState();
_checkCache();
}
/// キャッシュを確認し、あれば File を取得
Future<void> _checkCache() async {
final fileInfo = await _cacheManager.getFileFromCache(widget.imageId);
if (fileInfo != null && fileInfo.file.existsSync()) {
setState(() {
_cachedFile = fileInfo.file;
});
}
}
@override
Widget build(BuildContext context) {
return _cachedFile != null
? Image.file(_cachedFile!) // キャッシュから画像を表示
: CachedNetworkImage(
imageUrl: widget.imageUrl,
cacheKey: widget.imageId, // 明示的にキャッシュキーを指定
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
);
}
}
説明
- キャッシュを検索
DefaultCacheManager().getFileFromCache(imageId)
を使い、imageId
(またはimageUrl
)をキーとしてキャッシュをチェック。- もし キャッシュが存在すれば
File
を取得し、それを表示。
- キャッシュがない場合は
CachedNetworkImage
を使用
CachedNetworkImage
を使い、ネットワークから画像を取得・キャッシュ。cacheKey
をimageId
にすることで、一意の ID でキャッシュ管理。
より高度なカスタマイズ
flutter_cache_manager
のConfig
をカスタマイズして、キャッシュの有効期限 や キャッシュサイズ を変更可能。
class MyCacheManager extends CacheManager {
static const key = "customCache";
static final MyCacheManager _instance = MyCacheManager._();
factory MyCacheManager() => _instance;
MyCacheManager._()
: super(
Config(
key,
stalePeriod: Duration(days: 7), // 7日間キャッシュを保持
maxNrOfCacheObjects: 200, // 最大200個の画像をキャッシュ
),
);
}
↑ このカスタムキャッシュマネージャーを CachedNetworkImage
に適用 すると、より効率的なキャッシュ管理が可能。
実装のまとめ
flutter_cache_manager
を利用して キャッシュがあるか事前にチェック 可能。cached_network_image
のcacheKey
を指定することで、 キャッシュの一貫性を保てる。- 必要に応じて
flutter_cache_manager
をカスタマイズ して、キャッシュの有効期限や保存数を調整できる。
結論
まず理論面としてiOSやAndroidでは標準、または推奨のキャッシュ機能付き画像表示ライブラリが使えるのに対し、Flutterではcached_network_image
を使うのが一般的である。実用ではflutter_cache_managerを併用してキャッシュ管理を実施するといったことを解説した。
また画像の ID をキーにしてキャッシュを検索し、キャッシュがあればそれを使い、なければネットワークから取得する というよくある使い方の例を提示した。プロジェクトの必要に応じてこれをモディファイすることでトラフィックの少ない、滑らかな画像スクロールが達成できるはずだ、
またディスクキャッシュを実現できるため、前回起動時のキャッシュが残せる。これはユーザー体験の改善につながるだろう。