📷

image_pickerについて勉強してみる

2022/07/07に公開

ドキュメントと動画を見ながら作ってみる

今回は、image_pickerの機能を体験するだけで、画像を保存したりする機能はありません🙇‍♂️

YouTube動画を見ながら、画像とカメラを使うパッケージについて学んでみる🧑🏼‍🎓
カメラ機能を使ってみるには、実機のスマートフォンが必要です😇
iOS、Android両方で動作を確認できました。

参考にした動画
https://www.youtube.com/watch?v=3xlREA-SL_k

今回使用したパッケージ
https://pub.dev/packages/image_picker

image_pickerとは、「画像ライブラリから画像を選択し、カメラで新しい写真を撮るためのiOSおよびAndroid用のFlutterプラグイン」だそうです。

先ずはパッケージを追加!

pubspec.yaml

name: image_picker_tutorial
description: A new Flutter project.

# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1

environment:
  sdk: ">=2.17.3 <3.0.0"

# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  image_picker: ^0.8.5+3

dev_dependencies:
  flutter_test:
    sdk: flutter

  # The "flutter_lints" package below contains a set of recommended lints to
  # encourage good coding practices. The lint set provided by the package is
  # activated in the `analysis_options.yaml` file located at the root of your
  # package. See that file for information about deactivating specific lint
  # rules and activating additional ones.
  flutter_lints: ^2.0.0

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

# The following section is specific to Flutter packages.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg

  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.dev/assets-and-images/#resolution-aware

  # For details regarding adding assets from package dependencies, see
  # https://flutter.dev/assets-and-images/#from-packages

  # To add custom fonts to your application, add a fonts section here,
  # in this "flutter" section. Each entry in this list should have a
  # "family" key with the font family name, and a "fonts" key with a
  # list giving the asset and other descriptors for the font. For
  # example:
  # fonts:
  #   - family: Schyler
  #     fonts:
  #       - asset: fonts/Schyler-Regular.ttf
  #       - asset: fonts/Schyler-Italic.ttf
  #         style: italic
  #   - family: Trajan Pro
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #
  # For details regarding fonts from package dependencies,
  # see https://flutter.dev/custom-fonts/#from-packages

iOSは設定が面倒臭い!

公式の翻訳

次のキーを次の場所にあるInfo.plistファイルに追加します<project root>/ios/Runner/Info.plist。

NSPhotoLibraryUsageDescription-アプリにフォトライブラリの許可が必要な理由を説明してください。これは、ビジュアルエディターではプライバシー-フォトライブラリの使用法の説明と呼ばれます。
NSCameraUsageDescription-アプリがカメラにアクセスする必要がある理由を説明してください。これは、ビジュアルエディターではプライバシー-カメラ使用法の説明と呼ばれます。
NSMicrophoneUsageDescription-動画を録画する場合、アプリがマイクにアクセスする必要がある理由を説明してください。これは、ビジュアルエディターではプライバシー-マイク使用法の説明と呼ばれます。

xmlにコメントをつけたり、ファイル全部書いてる人いない気がするから、全部書いてみた!

xmlにコメントをつける方法
https://www.javadrive.jp/xml/ini/index5.html

ios/Runner/info.pilist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>$(DEVELOPMENT_LANGUAGE)</string>
    <!-- ここから -->
	<key>NSPhotoLibraryUsageDescription</key>
	<string>Photo Library Access Warning</string>
    <key>NSCameraUsageDescription</key>
    <string>Camera Library Access Warning</string>
    <!-- ここまで、ギャラリーとカメラのxmlを追加する -->
	<key>CFBundleDisplayName</key>
	<string>Image Picker Tutorial</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>image_picker_tutorial</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>$(FLUTTER_BUILD_NAME)</string>
	<key>CFBundleSignature</key>
	<string>????</string>
	<key>CFBundleVersion</key>
	<string>$(FLUTTER_BUILD_NUMBER)</string>
	<key>LSRequiresIPhoneOS</key>
	<true/>
	<key>UILaunchStoryboardName</key>
	<string>LaunchScreen</string>
	<key>UIMainStoryboardFile</key>
	<string>Main</string>
	<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>UISupportedInterfaceOrientations~ipad</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationPortraitUpsideDown</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>UIViewControllerBasedStatusBarAppearance</key>
	<false/>
	<key>CADisableMinimumFrameDurationOnPhone</key>
	<true/>
</dict>
</plist>

Androidも設定が必要?

カメラアプリを作るのをやっていたときに、minSdkVersion 21にしないとエラー出たことあるので、21にしておく。

android/app/build.gradle

def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
    localPropertiesFile.withReader('UTF-8') { reader ->
        localProperties.load(reader)
    }
}

def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}

def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
    flutterVersionCode = '1'
}

def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
    flutterVersionName = '1.0'
}

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
    compileSdkVersion flutter.compileSdkVersion
    ndkVersion flutter.ndkVersion

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = '1.8'
    }

    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
    }

    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId "com.example.image_picker_tutorial"
        // You can update the following values to match your application needs.
        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
        minSdkVersion 21 // カメラアプリで数値を入力しないとエラーがでたような?
        targetSdkVersion flutter.targetSdkVersion
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
    }

    buildTypes {
        release {
            // TODO: Add your own signing config for the release build.
            // Signing with the debug keys for now, so `flutter run --release` works.
            signingConfig signingConfigs.debug
        }
    }
}

flutter {
    source '../..'
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

サンプルアプリを作ってみる

main.dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image_picker/image_picker.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  File? image;
  // 画像をギャラリーから選ぶ関数
  Future pickImage() async {
    try {
      final image = await ImagePicker().pickImage(source: ImageSource.gallery);
      // 画像がnullの場合戻る
      if (image == null) return;

      final imageTemp = File(image.path);

      setState(() => this.image = imageTemp);
    } on PlatformException catch (e) {
      print('Failed to pick image: $e');
    }
  }

  // カメラを使う関数
  Future pickImageC() async {
    try {
      final image = await ImagePicker().pickImage(source: ImageSource.camera);
      // 画像がnullの場合戻る
      if (image == null) return;

      final imageTemp = File(image.path);

      setState(() => this.image = imageTemp);
    } on PlatformException catch (e) {
      print('Failed to pick image: $e');
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text("Image Picker Example"),
        ),
        body: Center(
            child: Column(
          children: [
            MaterialButton(
              color: Colors.blue,
              child: const Text(
                "Pick Image from Garary",
                style: TextStyle(
                    color: Colors.white70, fontWeight: FontWeight.bold),
              ),
              onPressed: () {
                pickImage();
              },
            ),
            MaterialButton(
              color: Colors.blue,
              child: const Text(
                "Pick Image from Garary",
                style: TextStyle(
                    color: Colors.white70, fontWeight: FontWeight.bold),
              ),
              onPressed: () {
                pickImageC();
              },
            ),
            SizedBox(height: 20),
            // 画像がないと文字が表示される
            image != null ? Image.file(image!) : Text("No image selected")
          ],
        )) // This trailing comma makes auto-formatting nicer for build methods.
        );
  }
}

これは、何なのか?

import 'dart:io';

リファレンスがあった!
https://api.dart.dev/stable/2.17.3/dart-io/dart-io-library.html

翻訳すると

非Webアプリケーションのファイル、ソケット、HTTP、およびその他のI/Oサポート。

重要:ブラウザベースのアプリはこのライブラリを使用できません。dart:ioライブラリをインポートして使用できるのは次の場合のみです。

サーバー
コマンドラインスクリプト
Flutterモバイルアプリ
フラッターデスクトップアプリ
このライブラリを使用すると、ファイル、ディレクトリ、ソケット、プロセス、HTTPサーバーとクライアントなどを操作できます。入力と出力に関連する多くの操作は非同期であり、FutureまたはStreamを使用して処理されます。どちらもdart:asyncライブラリで定義されています。

つまり、ファイルを操作するプログラムを作るときに必要なライブラリということか🤔

実機の画像をMacにアップロードしたのだが、jpeg、png形式ではないので、Zennにアップロードできなかった😭
iPhoneユーザーなのに、なぜかAndroidの実機でテストした😅
iPhoneの実機はappleDeveioperのアカウント登録が必要なんですよね~
Swiftやってたときにアカウント作ったが、全然使ってない...
勿体無いな😇

最後に

ドキュメントだけでは、理解が難しくて動画を見て勉強するエンジニアもいるので、恥ずかしいことではないと思われます。
でも、読まないと今のバージョンはこの書き方は、非推奨だよと開発者の方が書いていたりするので目を通しておいた方が良いですね😅

おまけ

FlutterAwesomeという海外サイトに、FireBaseのCloudStorageに画像をアップロードし、アップロードした画像を取得して表示するチュートリアルがありました。
Flutter3で作ってみたのですが、動作は確認できました。
ゴミ箱のボタンを押すとStorageのデータも削除されるので、完成度の高いサンプルと思われます。
ご興味のある方は、やってみてください😇

https://flutterawesome.com/a-new-flutter-thats-shows-you-how-to-upload-view-and-delete-images-using-firebase-storage/

cached_network_imageというパッケージが使われていましたね。
すささんという方が、以前ご紹介していましたね。

https://zenn.dev/susatthi/articles/20220615-160504-flutter-cached-network-image-test

Discussion