androidでカメラを使う（なるべくシンプルな方法）

# 手順
いや、これでもシンプルな方なんですよ。たぶん。すげえ大変そうだけど。

![Apr-17-2024 10-16-51.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/33db36af-a718-f431-f21c-d93d9545e864.gif)

１、アプリからデバイスのカメラを立ち上げる
２、撮影した画像をデバイスのストレージに一時保存する
３、一時保存したファイルを読み込んで使う

という流れです。
なぜ画像を一時保存するかというと、カメラからそのまま画像を受け取ると低画質になるからです。

### カメラとファイル保存の設定

manifests/AndroidManifests.xmlにカメラとストレージの仕様設定を追加。

manifest内のapplicationと同階層に以下を追加。
これはカメラのハードウェアと、カメラ権限、デバイスストレージの使用権限を設定しています。

```xml:AndroidManifests.xml
    <uses-feature android:name="android.hardware.camera" android:required="true" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
```

application内のactivityと同階層に以下を追加。
アプリはデバイスのファイルに直接アクセスできず、ファイルプロバイダーを経由する必要があるので、ファイルプロバイダーを設定します。
authoritiesはなんでもいいと思いますが、後で使います。アプリのパッケージ名（MainActivity.ktsのトップに記載されている）.fileproviderが推奨です。

```xml
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.example.YourAppName.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
```

AndroidManifests.xml全体は以下のようになります。

```xml:AndroidManifests.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-feature android:name="android.hardware.camera" android:required="true" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.YourAppName"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.YourAppName">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.example.YourAppName.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
    </application>

</manifest>
```

### 画像ファイルを一時保存するパスを設定する。

res/xml に file_paths.xml というファイルを作ります。
デバイスのPicturesディレクトリを指定します。

```xml:res/xml/file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-files-path
        name="images"
        path="Pictures"/>
</paths>
```

### 画像表示用のモジュールをインストール。

アプリのbuild.gradle.ktsに以下を追加します。

```kotlin:build.gradle.kts
implementation("io.coil-kt:coil-compose:2.1.0")
```

### カメラを立ち上げる

必要なモジュールをインポート。

```kotlin:MainActivity.kt
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import com.example.cautionplaterecognition.ui.theme.YourAppNameTheme
import java.io.File

import coil.compose.AsyncImage
import coil.request.ImageRequest
```

ActivityResultContract.TakePicture()というのを登録してlaunchすることでカメラを立ち上げます。successした場合、渡したuriに画像が保存されます。
Pictureディレクトリにファイルのパスを作り、ファイルプロバイダーでアクセスしてuriを取得します。
ファイルプロバイダーの名前はマニフェストファイルで設定したものと一致する必要があります。


```kotlin:MainActivity.kt
private lateinit var photoFile: File
private var imageUri by mutableStateOf<Uri?>(null)
private val takePictureLauncher = registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
    if (success) {
        imageUri = Uri.fromFile(photoFile)
    }
}


private fun createImageFile(): File {
    val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
    return File.createTempFile(
        "JPEG_${System.currentTimeMillis()}_",
        ".jpg",
        storageDir
    )
}

private fun takePhoto() {
    photoFile = createImageFile()
    val photoURI: Uri = FileProvider.getUriForFile(
        this,
        "com.example.cautionplaterecognition.fileprovider",
        photoFile
    )
    takePictureLauncher.launch(photoURI)
}
```

### UIに反映する

imageUriが変更されたら反映してくれるようにImageDisplayというComposableオブジェクトを作ります。

```kotlin:MainActivity.kt
@Composable
fun ImageDisplay(imageUri: Uri) {
    AsyncImage(
        model = ImageRequest.Builder(LocalContext.current)
            .data(imageUri)
            .crossfade(true)
            .build(),
        contentDescription = "Captured Image",
        modifier = Modifier.fillMaxSize(),
        contentScale = ContentScale.Crop
    )
}
```

```kotlin:MainActivity.kts
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            YourAppNameTheme {
                Box(modifier = Modifier.fillMaxSize()) {
                    imageUri?.let {
                        ImageDisplay(it)
                    }
                    Column(
                        modifier = Modifier
                            .align(Alignment.TopCenter)
                            .padding(top = 16.dp),
                        horizontalAlignment = Alignment.CenterHorizontally
                    ) {
                        Button(onClick = {

                            if (checkAndRequestPermissions()) {
                                takePhoto()
                            }
                        }) {
                            Text("Take Photo")
                        }
                    }
                }
            }
        }
    }
```

### ユーザーにカメラパーミッションを許可してもらう
カメラを使用するには、ユーザーにアプリ設定の権限を与えてもらう必要があります。
権限をチェックし、ない場合はポップアップでユーザーに依頼します（ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION)）。

権限リクエストの結果が許可であれば、カメラを立ち上げ、許可されない場合はポップアップで設定を依頼します。

```kotlin:MainActivity.kt
private fun checkAndRequestPermissions(): Boolean {
    if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.CAMERA)) {
            // パーミッションが必要であることの説明を表示
            Toast.makeText(this, "カメラのパーミッションが必要です。設定から許可してください。", Toast.LENGTH_LONG).show()
        }
        ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION)
        return false
    }
    return true
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if (requestCode == REQUEST_CAMERA_PERMISSION) {
        if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // パーミッションが許可されたらカメラを起動
            takePhoto()
        } else {
            // パーミッションが拒否された場合
            Toast.makeText(
                this,
                "カメラのパーミッションが必要です。設定から許可してください。",
                Toast.LENGTH_SHORT
            ).show()
        }
    }
}
```

コード全体

```kotlin:MainActivity.kts
package com.example.yourappname

import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import com.example.cautionplaterecognition.ui.theme.YourAppNameTheme
import java.io.File

import coil.compose.AsyncImage
import coil.request.ImageRequest

class MainActivity : ComponentActivity() {
    private lateinit var photoFile: File
    private var imageUri by mutableStateOf<Uri?>(null)
    companion object {
        const val REQUEST_CAMERA_PERMISSION = 101

    }
    private val takePictureLauncher = registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
        if (success) {
            imageUri = Uri.fromFile(photoFile)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            YourAppNameTheme {
                Box(modifier = Modifier.fillMaxSize()) {
                    imageUri?.let {
                        ImageDisplay(it)
                    }
                    Column(
                        modifier = Modifier
                            .align(Alignment.TopCenter)
                            .padding(top = 16.dp),
                        horizontalAlignment = Alignment.CenterHorizontally
                    ) {
                        Button(onClick = {

                            if (checkAndRequestPermissions()) {
                                takePhoto()
                            }
                        }) {
                            Text("Take Photo")
                        }
                    }
                }
            }
        }
    }

    private fun takePhoto() {
        photoFile = createImageFile()
        val photoURI: Uri = FileProvider.getUriForFile(
            this,
            "com.example.yourappname.fileprovider",
            photoFile
        )
        takePictureLauncher.launch(photoURI)
    }

    private fun createImageFile(): File {
        val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
        return File.createTempFile(
            "JPEG_${System.currentTimeMillis()}_",
            ".jpg",
            storageDir
        )
    }

    private fun checkAndRequestPermissions(): Boolean {
        if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.CAMERA)) {
                // パーミッションが必要であることの説明を表示
                Toast.makeText(this, "カメラのパーミッションが必要です。設定から許可してください。", Toast.LENGTH_LONG).show()
            }
            ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION)
            return false
        }
        return true
    }
    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == REQUEST_CAMERA_PERMISSION) {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // パーミッションが許可されたらカメラを起動
                takePhoto()
            } else {
                // パーミッションが拒否された場合
                Toast.makeText(
                    this,
                    "カメラのパーミッションが必要です。設定から許可してください。",
                    Toast.LENGTH_SHORT
                ).show()
            }
        }
    }
}
@Composable
fun ImageDisplay(imageUri: Uri) {
    AsyncImage(
        model = ImageRequest.Builder(LocalContext.current)
            .data(imageUri)
            .crossfade(true)
            .build(),
        contentDescription = "Captured Image",
        modifier = Modifier.fillMaxSize(),
        contentScale = ContentScale.Inside
    )
}
```

🐣
****
フリーランスエンジニアです。
AIについて色々記事を書いていますのでよかったらプロフィールを見てみてください。

もし以下のようなご要望をお持ちでしたらお気軽にご相談ください。
AIサービスを開発したい、ビジネスにAIを組み込んで効率化したい、AIを使ったスマホアプリを開発したい、
ARを使ったアプリケーションを作りたい、スマホアプリを作りたいけどどこに相談したらいいかわからない…

いずれも中間コストを省いたリーズナブルな価格でお請けできます。

お仕事のご相談はこちらまで
rockyshikoku@gmail.com

機械学習やAR技術を使ったアプリケーションを作っています。
機械学習／AR関連の情報を発信しています。

[X](https://twitter.com/JackdeS11)
[Medium](https://medium.com/@rockyshikoku)
[GitHub](https://github.com/john-rocky)