【Android】標準ギャラリーを使用してギャラリーから画像を取得する

## はじめに
アプリから端末内の画像を取得して何か処理（加工や表示）をしたいということがあると思います。
身近な例で言うと、チャットアプリで相手に端末の画像を共有する、アイコン画像を端末内の画像に設定する等のようなケースです。

アプリから端末内の画像を取得する方法は、MediaStoreを使った方法や、ContentResolverを使った方法等ありますが、今回は標準ギャラリーを用いた方法をまとめていきたいと思います。

## 標準ギャラリーとは
標準ギャラリーとは、アプリにプリインストールされているギャラリーアプリです。
このギャラリーアプリを自作のアプリから呼び出すことで、簡単に画像を自前のアプリに持ってくることができます。

## 実装
今回は端末内の画像を取得し、それを表示するようなアプリを作りってみたいと思います。

### レイアウトの作成
レイアウトはViewで実装します。もちろん、Composeでも問題はありません。
今回のレイアウトは至ってシンプルで、画像を表示するViewと、標準ギャラリーを起動させるリスナーを設定するボタンを置いています。
``` activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <!-- 取得した画像を表示するView -->
    <ImageView
        android:id="@+id/image_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@+id/btn_choose_image"
        />
        
    <!-- 標準ギャラリーを起動するリスナーを設定するボタン -->
    <Button
        android:id="@+id/btn_choose_image"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Select Image"
        app:layout_constraintBottom_toBottomOf="parent"
    />

</androidx.constraintlayout.widget.ConstraintLayout>
```

### 標準ギャラリーから画像を受け取り、Viewに設定する
次にロジックの部分を作成していきます。
まずは標準ギャラリーから画像を受け取り、Viewに設定するregisterForActivityResultを定義します。

registerForActivityResultは、結果を返すActivityを起動する時に使うAPIになります。
以前はstartActivityForeResultが使われていたのですが、このAPIが非推奨になり、代わりにregisterForActivityResultが使われるようになりました。

今回はIntentの結果を確認し、RESULT_OKの場合は、画像をViewに設定し、それ以外の場合は、トーストでエラーを表示するような処理を作成します。

``` MainActivity.kt
class MainActivity : AppCompatActivity() {
    // 今回は標準ギャラリーをIntentで起動するため、ActivityResultContracts.StartActivityForResult()を使用
    private val selectPictureLauncher =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
            if (it.resultCode == Activity.RESULT_OK) {
                val imageView: ImageView = findViewById(R.id.image_view)
                
                // 選択した画像をImageViewに表示
                imageView.setImageURI(it.data?.data)
            } else {
                // エラーが発生した場合はトーストを表示
                Toast.makeText(this, "エラーが発生しました", Toast.LENGTH_LONG).show()
            }
        }
```

### 標準ギャラリーを起動するリスナーを設定する
最後に標準ギャラリーを起動するリスナーを設定していきます。
``` MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val button = findViewById<View>(R.id.btn_choose_image)
        
        button.setOnClickListener {
            // ギャラリーを開いて画像を選択するIntentを作成
            val intent = Intent(Intent.ACTION_PICK).also {
                it.type = "image/*"
            }

            // selectPictureLauncherを使用し、ギャラリーを開く
            selectPictureLauncher.launch(intent)
        }
    }
```

ここで実装は完了です。
実際に動かしてみると、ボタンを押すとギャラリーが開き、画像を選択してアプリに戻ると、画像がアプリに表示されるようになっているかと思います。

## おわりに
今回は、標準ギャラリーを使用してアプリから端末内の画像にアクセスする方法をまとめてみました。
特にパーミッションの取得も不要で、簡単に画像を取得できようになりますね。

欠点として、画像選択時の見た目をカスタマイズできないと言う点があります。
この欠点を補う方法としてはMediaStoreを使用し、カスタムギャラリーの作成して画像取得を行う方法があるようですので、こちらもどこかで試してみようと思います。