LoginSignup
16
7

FlutterのImagePickerで画像のキャッシュが残ってしまう問題の対策

Last updated at Posted at 2024-01-07

株式会社Neverのshoheiです。

株式会社Neverは「NEVER STOP CREATE 作りつづけること」をビジョンに掲げ、理想を実現するためにプロダクトを作り続ける組織です。モバイルアプリケーションの受託開発、技術支援、コンサルティングを行っております。アプリ開発のご依頼や開発面でのお困りの際はお気楽にお問合せください。

概要

Flutterでカメラや写真から画像を扱う際は、image_pickerを使うかと思います。image_pickerはとても簡単に使えて便利なパッケージですが、画像のキャッシュが端末内に残ってしまい、ストレージを圧迫することがあったので、その対策をまとめます。

image_pickerで選択した画像の保存先

iOSとAndroidではそれぞれ異なります。

OS 保存先 保存先の取得方法
iOS tmp領域
/Users/xxx/Library/Developer/CoreSimulator/Devices/8453C0AB-E640-4044-87E3-7A891199062E/data/Containers/Data/Application/1B2DA1B2-CFEA-4070-8F23-F83A7B8DF6CE/tmp/
Directory.systemTemp
Android cache領域
/data/user/0/com.example.flutter_sample/cache/
getTemporaryDirectory()path_providerを利用

これらの領域はアプリを再起動してもファイルは残り続けます。

定期的に削除される領域のようですが12、削除されるまでファイルは残り続けるので、知らないうちにストレージを圧迫してしまいます。

対策

image_pickerで画像バイナリを取得後にキャッシュを削除する

image_pickerで画像を取得すると、XFile形式で取得できます。これをFile形式に変換してImage Widgetにセットして扱うことがあると思いますが、Fileではなく、Uint8Listで扱った後にFile(xxx).deleteSync()でキャッシュを削除します。

Future<void> onPickImage() async {
    final xFile = await ImagePicker().pickImage(source: ImageSource.gallery);
    if (xFile == null) {
      return;
    }
    final bytes = await xFile.readAsBytes(); // 👈 画像バイナリを取得
    setState(() {
      _imageBytes = bytes; // 👈 画像バイナリをセット
    });
    File(xFile.path).deleteSync(); // 👈 キャッシュを削除
}
    @override
    Widget build(BuildContext context) {
        final imageBytes = _imageBytes;
        return Scaffold(
            ...
            body: Center(
                child: imageBytes != null
                    ? Image.memory(
                        imageBytes,
                        width: 300,
                        fit: BoxFit.contain,
                      )
                    : const SizedBox.shrink(),
                ),
            ...
    }

ただし、画像バイナリをメモリ上に展開するので、画像を複数個扱う場合はメモリを圧迫する可能性があり注意が必要です。

キャッシュ削除を実装

キャッシュ削除を実装し、アプリ起動時やユーザーからの操作で実行します。

void deleteFiles() {
    final list = _dir
        .listSync(recursive: true, followLinks: false)
        .whereType<File>()
        .toList();
    for (final file in list) {
        file.deleteSync();
    }
}

_dirにiOSならDirectory.systemTemp、AndroidならgetTemporaryDirectoryをセットします。

これらの処理をまとめたTmpCacheDirectoryは以下の通りです。なお、ファイル数が多くなると処理が重たくなるのでcomputeを使って新しいIsolateでキャッシュを削除します。

import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';

class TmpCacheDirectory {
  TmpCacheDirectory._();

  static late final Directory _dir;

  static Future<void> configure() async {
    if (Platform.isIOS) {
      _dir = Directory.systemTemp;
    } else {
      _dir = await getTemporaryDirectory();
    }
  }

  static Future<void> deleteFiles() async {
    await compute<Directory, void>(
      (dir) {
        final list = dir
            .listSync(recursive: true, followLinks: false)
            .whereType<File>()
            .toList();
        for (final file in list) {
          file.deleteSync();
          debugPrint('delete: ${file.path}');
        }
      },
      _dir,
    );
  }
}
void main() {
    ...

    /// Delete Cache
    Future(() async {
        await TmpCacheDirectory.configure();
        await TmpCacheDirectory.deleteFiles();
    });
 
    runApp(const App());
}

サンプルコードはこちら

tmp cache領域には他のファイルも含まれる場合もあるので、画像だけを削除したい場合は、Fileパスの拡張子等から該当するファイル名だけをフィルタリングして削除してください。

もっと簡潔に消去する術はないのかな🤔

  1. File System Basics - Table 1-1  Commonly used directories of an iOS app

  2. アプリ固有のファイルにアクセスする - キャッシュ ファイルを削除する

16
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
7