AndroidでFlutterアプリでイメージ選択に気をつけろ

はじめに

こんにちは👋、アプリチームのルーカス(YOUTRUST / X) です。

今回、Flutterアプリで画像選択を実装する際、Androidならではのあまり知られていない衝撃の3つのポイントを大公開します!😮 個人的にはこれらについて全く知識がなかったのですが、不具合のご報告をいただいたため、しっかりと調査を行いました。
なお、AndroidデバイスはメーカーやOSのバージョンによって大きく異なるため、本記事のテストはPixel 7aとSamsung Galaxy M23で実施しました。Androidの世界は、時として迷路のように感じられることがありますので、僕がその道筋を丁寧にご案内いたします〜🚀

Android迷路

1. ピッカーの選び方 〜AndroidとiOSの違いを見極める〜

迷路始まり

弊社では画像選択のため、image_pickerパッケージを採用しています。
📸YOUTRUSTのアプリでは、投稿時に画像だけでなく動画も添付できるため、iOS側では画像と動画の両方が選択可能なダイアログを表示するpickMedia関数を呼び出しています。しかし、Androidではそのダイアログがファイルブラウザとして表示され、.pdf.mp3など、画像以外のファイルも選択できてしまいます。そこで、Android側ではpickImage関数を呼び出すように変更しました。✨ pub.dev

os Android iOS
pickImage
pickMedia

使用する関数はアプリの仕様によって異なりますが、YOUTRUSTの場合は、この使い方が必要となりました。

2. 許可の話

もっと深く入りました

基本的に、iOSは許可リクエストに関して厳格な印象がありますが、今回の実装では、実はAndroid側の方がより困難でした。😰 YOUTRUSTのアプリは、まだAndroid 10までのデバイスをサポートしているため、画像選択に必要な許可が異なります。 Android 12以前ではandroid.permission.READ_EXTERNAL_STORAGEが必要ですが、Android 13以上の場合はandroid.permission.READ_MEDIA_IMAGESを使用する必要があります。

Flutterのコードでそんな感じで許可がリクエストできます:

 static Future<Permission> _getAndroidPermissionForAddingPhotos() async {
    // Android:
    // Devices running Android 12 (API level 32) or lower:
    // use [Permission.storage].
    // Devices running Android 13 (API level 33) and above:
    // Should use [Permission.photos].

    final androidInfo = await DeviceInfoPlugin().androidInfo;
    if (androidInfo.version.sdkInt <= 32) {
      return Permission.storage;
    } else {
      return Permission.photos;
    }
  }

DeviceInfoPluginは、device_info_plusパッケージから利用しており、許可のリクエストにはpermission_handlerを使用しています。permission_handlerはリクエスト時に、まず既に許可が与えられているかどうかを確認するため、すでに許可されている場合はリクエストをスキップします。そのため、とても便利だと感じています。😊 pub.dev pub.dev

3. HEICの初見殺し

HEICファイル出てきた

ここまで全て実装できた方、おめでとうございます!🎉しかし、以下の事実は99%の人が知らない可能性があります。.HEIC形式のファイルタイプについては、iOSユーザーならご存知のはずです。しかし、実はAndroidでもこのファイルタイプが使用可能なのです!😲僕も全く知りませんでしたが、弊社のアプリエンジニアの朝日さんのおかげで発見することができました。実は、Googleフォトという、写真の閲覧、画像のコレクション整理、クラウド保存などが可能なアプリがあります。Googleフォトでは.HEICファイルが使用されているため、Androidでも.HEICファイルを選択できる場合があります。しかし、file_pickerを通じて.HEICを選択した場合、nullが返されてしまいました。では、どうするのでしょうか?🤔

公式のレポジトリを見てみると、関連するissueが一つありました。

github.com

おすすめの解決策は、.HEIC形式の画像を.jpeg形式に変換することです。🔄

  static bool isHEIC(String path) {
    return path?.isNotEmpty &&
        (path.toLowerCase().endsWith('.heic')
            || path.toLowerCase().endsWith('.heif'));
  }

  // Check if HEIC, and if so then convert HEIC to JPG
  String result = await HeicToJpg.convert(
      fullHEICPath,
      jpgPath: fullConvertedPath
  ).catchError((e, stack) {
    print('error: $e', stack);
  });

最初は、上記の様にheic_to_jpgパッケージから提供されるHeicToJpg関数を使用して実装しようかと思っていました。しかし、このパッケージは非推奨で、Issueも多発していたため、別の変換方法を調査しました。🔍

結果、一般的な画像変換ライブラリであるFlutterImageCompressを使用する方法を採用し、以下のように実装しました。

 bool _isHEIC(String? path) {
    if (path == null) {
      return false;
    }

    return path.isNotEmpty &&
        (path.toLowerCase().endsWith('.heic') ||
            path.toLowerCase().endsWith('.heif'));
  }


  if (Platform.isAndroid && _isHEIC(image?.path)) {
      // Android does not handle HEIC images, so we need to convert it to jpg
      final tmpDir = (await getTemporaryDirectory()).path;
      final target = '$tmpDir/${DateTime.now().millisecondsSinceEpoch}.jpg';
      final result = await FlutterImageCompress.compressAndGetFile(
        image?.path ?? '',
        target,
        quality: 100,
  );

      ....
 }

最後に

できた!!!

ここまで読んでくれて、ありがとうございました!!🙏何かみなさまのアプリで利用できるところがあれば、嬉しいです。。😊

YOUTRUSTでは、エンジニアを募集しています。興味がある方はぜひご応募ください!✨

herp.careers