React Native(Expo)でQRコードの読取／生成

## 必要な条件

このチュートリアルでは, QRコードの生成や読み取る事が出来るAndroid アプリを作っていきたいと思います。 チュートリアルに沿っていくには、次の条件が必要になります：

テスト用のAndroidスマホ
Visual Studio Code（他のIDEでも良い）
Node (npm)
Reactの基礎知識

## 準備
### Expo CLI
パソコンのコマンドラインでExpo　CLIをインストールしましょう：`npm install -g expo-cli`。アカウントを持っていれば`expo login` を実行します。アカウントを持っていなければ、`expo register`を実行します。

### Expoアプリ
AndroidスマホでExpoアプリをダウンロードします。ダウンロードが終わってから、アプリを開きます。「Profile」タブでログインが出来ます。

### アプリ作成
好きなIDEのターミナルで次のコードを実行します：`expo init expo-qrcode`。「Choose a template」が表示される時に「blank」を選びます。

`cd expo-qrcode`を実行して、必要なパッケージをインストールします：`npm i expo-barcode-scanner @react-navigation/stack @react-navigation/native react-native-qrcode-svg`

`npm start`を実行します。Expoアプで、「Scan QR Code」をクリックして、http://localhost:19002/ で表示されているQRコードをスキャンします。ダウンロードが終わってから、スマホでアプリが開きます。

開発中、コードを変更する時にスマホで表示されるまでに時間が掛かる可能性があります。その場合には、http://localhost:19002/ で「Run in web browser」をクリックするとパソコンのブラウザーで、アプリの様子が確認できます。

## App.jsx
App.jsxのコードをすべてを削除し、以下のコードに置き換えます。

```App.jsx
import React from "react";
import { createStackNavigator } from "@react-navigation/stack";
import { NavigationContainer } from "@react-navigation/native";

import Home from "./screens/Home";
import Scanner from "./screens/Scanner";
import Generator from "./screens/Generator";

//スタックナビゲーターのおかげで、画面間の移動ができます
const Stack = createStackNavigator();

function App() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="ホーム" component={Home} />
      <Stack.Screen name="スキャナー" component={Scanner} />
      <Stack.Screen name="生成" component={Generator} />
    </Stack.Navigator>
  );
}

export default () => {
  return (
    <NavigationContainer>
      <App />
    </NavigationContainer>
  );
};
```

## ホーム画面
プロジェクトのルートで、screensフォルダを作りましょう。そのフォルダで、Home.jsxを作ります。

```Home.jsx
import React from "react";
import { StyleSheet, Text, View, Pressable } from "react-native";
import { useNavigation } from "@react-navigation/native";

export default function Home() {
  const navigation = useNavigation();

  return (
    <View style={styles.container}>
      {QRコードスキャナー画面に移動}
      <Pressable
        onPress={() => navigation.navigate("スキャナー")}
        style={styles.button}
      >
        <Text style={styles.text}>QRコードを読み取る</Text>
      </Pressable>
      {QRコード生成画面に移動}
      <Pressable
        onPress={() => navigation.navigate("生成")}
        style={styles.button}
      >
        <Text style={styles.text}>QRコードを生成する</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
  button: {
    backgroundColor: "cyan",
    borderRadius: 4,
    paddingVertical: 10,
    paddingHorizontal: 20,
    margin: 20,
    elevation: 3,
  },
  text: {
    fontSize: 16,
    lineHeight: 21,
    fontWeight: "bold",
    letterSpacing: 0.25,
    color: "whitesmoke",
  },
});
```

## スキャナー
まだscreensフォルダで、Scanner.jsxを作ります。

```Scanner.jsx
import React, { useState, useEffect } from "react";
import { Text, View, StyleSheet, Pressable, Linking } from "react-native";
import { BarCodeScanner } from "expo-barcode-scanner";

export default function Scanner() {
  //アプリはカメラを使う許可が認められるかどうか
  const [hasPermission, setHasPermission] = useState(null);
  //アプリはQRコードをスキャンしたかどうか
  const [scanned, setScanned] = useState(false);

  //最初のレンダリングで、カメラの許可を要求する
  useEffect(() => {
    (async () => {
      const { status } = await BarCodeScanner.requestPermissionsAsync();
      setHasPermission(status === "granted");
    })();
  }, []);

  //QRコードがスキャンされると、読み取ったリンクを開く
  //リンクを開く事がでない場合にはメッセージを表示する
  const handleBarCodeScanned = ({ data }) => {
    Linking.openURL(data)
      .then(() => setScanned(true))
      .catch((err) => {
        setScanned(true);
        alert("リンクを開く事ができませんでした。");
      });
  };

  return (
    <View style={styles.container}>
      {/* カメラアにクセスすることがまだ許可も拒否もされていない場合 */}
      {hasPermission === null && <Text>カメラの許可を要求しています。</Text>}
      {/* カメラアにクセスすることが拒否されている合 */}
      {hasPermission === false && <Text>カメラにアクセスできません</Text>}
      {/* カメラアにクセスすることが許可されている場合 */}
      {hasPermission && (
        <BarCodeScanner
          barCodeTypes={["qr"]}
          onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
          style={StyleSheet.absoluteFillObject}
        />
      )}
      {/* スキャンが終わってから表示する */}
      {scanned && (
        <Pressable onPress={() => setScanned(false)} style={styles.button}>
          <Text style={styles.text}>別のQRコードを読み取る</Text>
        </Pressable>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: "column",
    justifyContent: "center",
    alignItems: "center",
  },
  button: {
    backgroundColor: "cyan",
    borderRadius: 4,
    paddingVertical: 10,
    paddingHorizontal: 20,
    margin: 20,
    elevation: 3,
  },
  text: {
    fontSize: 16,
    lineHeight: 21,
    fontWeight: "bold",
    letterSpacing: 0.25,
    color: "whitesmoke",
  },
});
```

## QRコードの生成

/screens/Generator.jsxを作っていきましょう。

```Generator.jsx
import React, { useState } from "react";
import { StyleSheet, Text, View, TextInput, Pressable } from "react-native";
import QRCode from "react-native-qrcode-svg";

export default function Generator() {
  //QRコードになるリンク
  const [qrCodeValue, setQrCodeValue] = useState("");
  //入力されたテキスト
  const [inputText, onChangeInputText] = useState("");

  //inputTextが空白ではなければ、
  //qrCodeValueを入力されたテキストに設定する
  const generateQrCode = () => {
    if (!inputText) {
      alert("リンクを入力してください");
      return;
    }
    setQrCodeValue(inputText);
  };

  return (
    <View style={styles.container}>
      <Text>リンクを入力してください</Text>
      <TextInput
        style={styles.input}
        value={inputText}
        onChangeText={onChangeInputText}
      />
      <Pressable style={styles.button} onPress={generateQrCode}>
        <Text style={styles.buttonText}>バーコードを生成する</Text>
      </Pressable>
      {/* qrCodeValueは空文字列でなければ、QRコードを表示する */}
      {qrCodeValue ? <QRCode value={qrCodeValue} /> : <Text></Text>}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
  input: {
    height: 40,
    marginHorizontal: 12,
    marginVertical: 20,
    borderWidth: 1,
    padding: 10,
    width: 200,
    backgroundColor: "white",
  },
  button: {
    backgroundColor: "cyan",
    borderRadius: 4,
    paddingVertical: 10,
    paddingHorizontal: 20,
    marginBottom: 40,
    elevation: 3,
  },
  buttonText: {
    fontSize: 16,
    lineHeight: 21,
    fontWeight: "bold",
    letterSpacing: 0.25,
    color: "whitesmoke",
  },
});
```

## デバッグ

開発中、次のエラーが発生しました。同じエラーが発生すれば、解決策の手順に従って下さい。

**エラー：**`RNGestureHandlerRootView" was not found in the UIManager`

**解決策：**
1. `expo install react-native-gesture-handler`
2. App.jsxに`import 'react-native-gesture-handler';`を追加する

## 最後に

これで、QRコードの生成や読み取ることができるAndroid アプリが完了です。Androidスマホで、QRコードを読み取るや生成してみてください！