担当しているFlutter案件で「ユーザーが同じ画像を選択できないようにしたい」という要望があったため、それに対応するまでに試したことを共有します。
開発環境
Flutter 3.19.6
image_picker 1.1.0
ファイルパスが同一かで判定してみる
最初に思いついた方法はファイルパスでの比較でした。
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
print('path: ${image?.path}');
pickImage()の戻り値でローカルストレージに保存されたファイル情報が取得できるため、その保存パスを比較することで同一ファイル判定ができないか試してみました。
path: /data/user/0/your.package.name/cache/scaled_sample.jpg // 1枚目
path: /data/user/0/your.package.name/cache/scaled_sample.jpg // 2枚目
path: /private/<省略>/image_picker_339FE219-8B2F-454D-A063-67B92BB33E22-21767-000008C3A666E13C.jpg // 1枚目
path: /private/<省略>/image_picker_159530BA-FCC3-4396-A769-9154228F8D95-21767-000008C4043B17AE.jpg // 2枚目
実験してみた結果、Androidでは同じ画像を何度選択しても同一のパスになりましたが、iOSでは選択するたびにパスのファイル名部分が変化してしまい、この方法では同一ファイル判定ができないとわかりました。
こういった比較方法は、利用しているパッケージ側で仕様変更が発生すると機能しなくなる可能性もあるため、なるべく避けた方が良いです。
ファイルのハッシュ値が同一かで判定してみる
ファイルパス以外の方法としてファイルサイズによる判定も検討してみたのですが、今回はもう少し精度を上げたかったので、最終的にはファイルのハッシュ値(SHA-256)を使った方法に落ち着きました。
ハッシュ値の生成にはcryptoパッケージを使います。
import 'package:crypto/crypto.dart';
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
final Uint8List imageBytes = await File(image.path).readAsBytes();
final String hash = sha256.convert(imageBytes).toString();
}
この方法で今回の要望は満たせたのですが、以下の懸念点があるため、これらを企画者に説明し、納得して貰った上での採用となりました。
- 名前だけを変えたファイルは同一ファイルとして判定される
- 通常の処理に加えてバイトデータを取得しているため、メモリ負荷が上がる
1はサービスの仕様によって良い悪いの判断が分かれるところだと思いますが、2は明確な悪影響になります。
個人的には、同一ファイルを弾くためだけに今回の処理を行うのはやり過ぎな気がするため、仕様の調整で済むのであれば、そちらの方が良いと思います。
まとめ
ハッシュ値を作成することで同一ファイル判定ができるようになりました。
今回は画像データの取得を例にあげましたが、動画やその他のファイルでも使える方法なので、機会があれば一度試してみてください。
株式会社ボトルキューブではFlutterを使ったお仕事を募集中です。
お問い合わせは下記リンク先のフォームからご連絡ください。