Androidでバーコードスキャナーを実装

# はじめに

カメラやGPSなどハードウェア依存の機能を作る時、スマートフォンは便利ですよね。
アプリ版にしかない機能を盛り込む事で、Web版との差別化も出来ます。

今回は、Androidで`バーコードスキャナー`機能を実装してみたいと思います。

# 実行環境

- Android Studio: 4.0.1
- API Level: 29
- Kotlin: 1.3.70
- [zxing-android-embedded](https://github.com/journeyapps/zxing-android-embedded): 4.1.0

# インストール

```diff
diff --git a/app/build.gradle b/app/build.gradle
index be61190..22efbee 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -6,6 +6,11 @@ android {
     compileSdkVersion 29
     buildToolsVersion "29.0.3"

+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
     defaultConfig {
         applicationId "com.example.barcodescanner"
         minSdkVersion 24
@@ -29,6 +34,7 @@ dependencies {
     implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
     implementation 'androidx.core:core-ktx:1.3.1'
     implementation 'androidx.appcompat:appcompat:1.2.0'
+    implementation 'com.journeyapps:zxing-android-embedded:4.1.0'
     testImplementation 'junit:junit:4.12'
     androidTestImplementation 'androidx.test.ext:junit:1.1.2'
     androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
```

基本は、GitHubの[README](https://github.com/journeyapps/zxing-android-embedded#adding-aar-dependency-with-gradle)に従って`build.gradle`に記述し、
gradleを実行すればインストールされます。

# メインアクティビティ作成

```kotlin
package com.example.barcodescanner

import android.annotation.SuppressLint
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import com.google.zxing.integration.android.IntentIntegrator
import com.google.zxing.integration.android.IntentResult

class MainActivity : AppCompatActivity()
{
    override fun onCreate(savedInstanceState: Bundle?)
    {
        super.onCreate(savedInstanceState)

        val intentIntegrator = IntentIntegrator(this).initiateScan()
    }

    @SuppressLint("ShowToast")
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
    {
        val result: IntentResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)

        if (null != result) {
            val barcodeValue = result.contents

            if (null == barcodeValue) {
                Toast.makeText(this, "Read Error", Toast.LENGTH_LONG).show()
            } else {
                Toast.makeText(this, barcodeValue, Toast.LENGTH_LONG).show()
            }
        } else {
            super.onActivityResult(requestCode, resultCode, data)
        }
    }
}
```

`IntentIntegrator`で、処理をバーコードスキャナーに移譲しています。
`onActivityResult`実装時のお作法として、
呼び出し元が想定されている機能である事を保証する為に、`resultCode`の値を突き合わせたりしますが、
Zxingではその辺の処理を`parseActivityResult()`で行っています。

# マニフェストに登録

```diff
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 227c879..1e9db0a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.example.barcodescanner">

@@ -7,6 +8,14 @@
         android:label="@string/app_name"
         android:roundIcon="@mipmap/ic_launcher_round"
         android:supportsRtl="true"
-        android:theme="@style/AppTheme" />
+        android:theme="@style/AppTheme"
+        android:hardwareAccelerated="true">
+        <activity android:name=".MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>

 </manifest>
```

メインアクティビティをマニフェストに登録します。
その際、`application`タグの属性に`android:hardwareAccelerated=true`を追加して下さい。

# 実行

![Screenshot_20200930-101914.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126713/9704690b-d8f1-a273-8a0a-d06b885b44c9.png)

アプリケーションを起動すると、バーコードスキャナーが表示されます。

![Screenshot_20200930-101930.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126713/fe71fc1e-a100-d6b1-c52e-64a924b77738.png)

バーコードをスキャンすると、値がポップアップで表示されます。

# その他の設定

### ビープ音をOFFにする

```diff
diff --git a/app/src/main/java/com/example/barcodescanner/MainActivity.kt b/app/src/main/java/com/example/barcodescanner/MainActivity.kt
index 2040542..0fdb9ba 100644
--- a/app/src/main/java/com/example/barcodescanner/MainActivity.kt
+++ b/app/src/main/java/com/example/barcodescanner/MainActivity.kt
@@ -14,7 +14,9 @@ class MainActivity : AppCompatActivity()
     {
         super.onCreate(savedInstanceState)

-        val intentIntegrator = IntentIntegrator(this).initiateScan()
+        val intentIntegrator = IntentIntegrator(this)
+            .setBeepEnabled(false)
+            .initiateScan()
     }

     @SuppressLint("ShowToast")
```

IntentIntegratorのインスタンス生成時、メソッドチェーンで`setBeepEnabled()`を呼び出します。
その際、引数にfalseを渡すとビープ音がOFFになります。

### メッセージを設定する

```diff
diff --git a/app/src/main/java/com/example/barcodescanner/MainActivity.kt b/app/src/main/java/com/example/barcodescanner/MainActivity.kt
index 2040542..a591c19 100644
--- a/app/src/main/java/com/example/barcodescanner/MainActivity.kt
+++ b/app/src/main/java/com/example/barcodescanner/MainActivity.kt
@@ -14,7 +14,9 @@ class MainActivity : AppCompatActivity()
     {
         super.onCreate(savedInstanceState)

-        val intentIntegrator = IntentIntegrator(this).initiateScan()
+        val intentIntegrator = IntentIntegrator(this)
+            .setPrompt("Test Message")
+            .initiateScan()
     }

     @SuppressLint("ShowToast")
```

![Screenshot_20200930-103940.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126713/f67abd2d-d811-2bd3-5182-233ba1dc1598.png)

IntentIntegratorのインスタンス生成時、メソッドチェーンで`setPrompt()`を呼び出します。
その際、引数に文字列を渡すと画面のメッセージが上書きされます。

### 画面を横回転させない

```kotlin
package com.example.barcodescanner

import com.journeyapps.barcodescanner.CaptureActivity

class Zxing : CaptureActivity() {}
```

```diff
diff --git a/app/src/main/java/com/example/barcodescanner/MainActivity.kt b/app/src/main/java/com/example/barcodescanner/MainActivity.kt
index 2040542..8155461 100644
--- a/app/src/main/java/com/example/barcodescanner/MainActivity.kt
+++ b/app/src/main/java/com/example/barcodescanner/MainActivity.kt
@@ -14,7 +14,9 @@ class MainActivity : AppCompatActivity()
     {
         super.onCreate(savedInstanceState)

-        val intentIntegrator = IntentIntegrator(this).initiateScan()
+        val intentIntegrator = IntentIntegrator(this).apply {
+            captureActivity = Zxing::class.java
+        }.initiateScan()
     }

     @SuppressLint("ShowToast")
```

![Screenshot_20200930-104625.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126713/44754bf9-87cc-c8c4-a36c-367f9611517e.png)

`CaptureActivity`を継承したアクティビティを作成し、IntentIntegratorの呼び出し時に指定します。

# 後書き

OCRを勉強中です。
ハードウェア性能も関係しますが、文字の読み取り精度はどの程度なのでしょうか。
とても楽しみです。