ReactNative(expo)でQRをReadする：実践編？


expoのQRスキャン機能は便利です。しかし、スキャンの開始や終了を制御できません。
なので、開始や終了はページ遷移なのでハック的に制御する必要があります。その方法を試してみます。

## はじめに

この記事の内容は[自分用のテンプレート](https://qiita.com/zaburo/items/f0a400efbaac933a0780)を利用しており、一般の方が読むと情報が不足している可能性があります。

## 前提

StackNavigatorやReduxを利用することになりますが、０から実装すると大変なので個人的テンプレートを利用します。
下記では、

* Tagを指定してclone（StackNavigatorやReduxが設定されている）
* Tabを指定してcloneするとno branchになるので、masterを作成し移動
* npm installする

ということをしています。

```bash
git clone -b 1-4 https://github.com/eizaburo/react-temp-basic qrread
cd qrread
git checkout -b master
npm install
```

## 実装

### 大まかな流れ

下記のようなイメージ。

* Homeに「QRコードを読み取る」ボタンを設置し、Detailに移動
* DetailにはQRリード機能を実装
* QRリードイベントで読み取りデータをReduxのstateに書き込み、Homeに戻る
* Homeでは読み取りデータがあれば、その内容と「データ送信」ボタンを表示

読み取りデータや読み取り情報を操作するメソッドはReduxで保持します。

### Home.js

Detail画面のリンクボタンに加え、読み取りデータが存在した場合（Detailから戻ってきたとき）、その内容を表示し、さらにデータ送信ボタンも表示します。

{ (条件) && (表示内容) }とすることで、条件を満たすときのみ表示することが可能です。

```diff:screens/Home.js
import React from 'react';
import { View, Text, Button } from 'react-native';
import { connect } from 'react-redux';
import { updateName } from '../actions/userAction';
+import { updateScanData } from '../actions/qrAction';

class Home extends React.Component {
    render() {
        return (
            <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
                <Text>Home</Text>
+                <Button
+                    title='QRコードを読み取る'
+                    onPress={() => this.handleMoveToQrRead()}
+                />
+                { 
+                    this.props.state.qrData.qr.data !== '' && 
+                    <Text>読み取りデータ：{this.props.state.qrData.qr.data}</Text>    
+                }
+                { 
+                    this.props.state.qrData.qr.data !== '' && 
+                    <Button
+                        title="データ送信"
+                        onPress={()=>alert(this.props.state.qrData.qr.data)}
+                    />
+                }
            </View>
        );
    }

+    handleMoveToQrRead = () => {
+        this.props.updateScanData('');
+        this.props.navigation.navigate('Detail')
+    }
}

const mapStateToProps = state => (
    {
        state: state,
    }
);

const mapDispatchToProps = dispatch => (
    {
        updateName: (name) => dispatch(updateName(name)),
+        updateScanData: (data) => dispatch(updateScanData(data)),
    }
);

export default connect(mapStateToProps, mapDispatchToProps)(Home);
```

### Detail.js

QRコードを読み取り、読み取りに成功したらHome画面に戻ります。
その時、updateScanData()を実行しReduxのstateを更新します。

```diff:screens/Detail.js
import React from 'react';
import { View, Text, Button } from 'react-native';
import { connect } from 'react-redux';
+import { BarCodeScanner, Permissions } from 'expo';
+import { updateScanData } from '../actions/qrAction';

class Detail extends React.Component {

+    state = {
+        hasCameraPermission: null,
+    }

+    async componentDidMount() {
+        //カメラ利用の許可要求
+        const { status } = await Permissions.askAsync(Permissions.CAMERA);
+        this.setState({
+            hasCameraPermission: status === 'granted'
+        });
+    }

    render() {

        //許可状況を取得
+        const { hasCameraPermission } = this.state;

        //許可確認中に表示
+        if (hasCameraPermission === null) {
+            return <Text>Requesting for camera permission.</Text>
+        }

        //許可NGだった場合の表示
+        if (hasCameraPermission === false) {
+            return <Text>No access to camera.</Text>
+        }

        //スキャナ表示
        return (
            <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
+                <BarCodeScanner
+                    onBarCodeRead={this.handleBarCodeScanned}
+                    style={{ height: 300, width: 300 }}
+                />
            </View>
        );
    }

    //読み取った際の動き
+    handleBarCodeScanned = ({type, data}) => {
+        this.props.updateScanData(data);
+        this.props.navigation.navigate('Home');
+    }
}

const mapStateToProps = state => (
    {
        state: state,
    }
);

const mapDispatchToProps = dispatch => (
    {
        updateName: (name) => dispatch(updateName(name)),
+        updateScanData: (data) => dispatch(updateScanData(data)),
    }
);

+export default connect(mapStateToProps, mapDispatchToProps)(Detail);
```

### qrAction.js

読み取りデータ更新用のaction。

```js:actions/qrAction.js
export const updateScanData = data => (
    {
        type: 'UPDATE_SCAN_DATA',
        data: data
    }
);
```

### qrReducer.js

読み取りデータ更新用のreducer。

```js:reducers/qrReducer.js
const initialState = {
    qr: {
        data: '',
        type: ''
    }
}

const qrReducer = (state = initialState, action) => {
    switch(action.type){
        case 'UPDATE_SCAN_DATA':
            return Object.assign({}, state, {
                qr: {
                    data: action.data
                }
            });
        default:
            return state;
    }
}

export default qrReducer;
```

### createStore.js

reducerの追加。
複数のreducerを追加した場合、追加したreducerは（今回の場合）、

* this.props.state.userData.user.name
* this.props.state.qrData.qr.data

という感じでstate以下にぶら下がります。

```diff:createStore.js
import { createStore as reduxCreateStore, combineReducers, applyMiddleware } from 'redux';
import userReducer from './reducers/userReducer';
+import qrReducer from './reducers/qrReducer';


export default createStore = () => {
    const store = reduxCreateStore(
        combineReducers({
            userData: userReducer,
+            qrData: qrReducer,
        }),
        applyMiddleware(
            //
        )
    );
    return store;
}
```
基本的には以上です。