Flutterで画像をキャッシュ:オンメモリ&ディスクキャッシュ

投稿者: | 2025年2月5日

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_imageflutter_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_imageflutter_cache_manager を内部的に利用しているため、画像のキャッシュをチェックし、なければネットワークから取得する という流れを実現できる。

この流れの実装例を見ていこう。

実装方法

1. flutter_cache_manager を使ってキャッシュをチェック

  • 画像の URL または ID をキーとしてキャッシュを検索
  • キャッシュに 画像がある場合はそれを使い、なければネットワークからダウンロード

2. cached_network_image でキャッシュを利用

  • cached_network_imageflutter_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),
          );
  }
}

説明

  1. キャッシュを検索
  • DefaultCacheManager().getFileFromCache(imageId) を使い、imageId(または imageUrl)をキーとしてキャッシュをチェック。
  • もし キャッシュが存在すれば File を取得し、それを表示
  1. キャッシュがない場合は CachedNetworkImage を使用
  • CachedNetworkImage を使い、ネットワークから画像を取得・キャッシュ。
  • cacheKeyimageId にすることで、一意の ID でキャッシュ管理。

より高度なカスタマイズ

  • flutter_cache_managerConfig をカスタマイズして、キャッシュの有効期限キャッシュサイズ を変更可能。
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_imagecacheKey を指定することで、 キャッシュの一貫性を保てる
  • 必要に応じて flutter_cache_manager をカスタマイズ して、キャッシュの有効期限や保存数を調整できる。

結論

まず理論面としてiOSやAndroidでは標準、または推奨のキャッシュ機能付き画像表示ライブラリが使えるのに対し、Flutterではcached_network_image を使うのが一般的である。実用ではflutter_cache_managerを併用してキャッシュ管理を実施するといったことを解説した。

また画像の ID をキーにしてキャッシュを検索し、キャッシュがあればそれを使い、なければネットワークから取得する というよくある使い方の例を提示した。プロジェクトの必要に応じてこれをモディファイすることでトラフィックの少ない、滑らかな画像スクロールが達成できるはずだ、

またディスクキャッシュを実現できるため、前回起動時のキャッシュが残せる。これはユーザー体験の改善につながるだろう。

コメントを残す