React Native BLEペリフェラルをスキャンする

# はじめに
React Nativeアプリケーションで、BLEペリフェラルデバイスのスキャンを行います。Androidでのみ動作確認していますが、Androidのみで動作可能なAPIは利用していないので、iOSでも動作可能かと思います。OSSはBLE Managerを利用します。

https://github.com/innoveit/react-native-ble-manager

# 初期セットアップ
まず通常通りアプリケーションの雛形を作り、それからyarn addでBLE Managerをインストールします。

```bash
$ react-native init BLESample
$ cd BLESample
$ yarn add react-native-ble-manager
```

Androidのマニフェスト(android/app/src/main/AndroidManifest.xml)に以下の3つのパーミッションを設定する。

```xml

    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
```

src/screens/MainScreen.jsを作成して、App.jsからはMainScreenを呼び出すようにしておきます。

```JavaScript
// Appjs
import React from 'react';

import MainScreen from './src/screens/MainScreen'

const App: () => React$Node = () => {
  return (
    <MainScreen/>
  );
};

export default App;
```

# スマートフォンのBluetooth ON/OFFを取得する
BLE Managerは利用する前にまずstartを呼び出す必要があります。Componentの初期化時のcomponentDidMountで呼び出します。BLE Managerの初期化の後にBluetoothの状態（スマートフォンのBluetooth ON/OFF設定)を確認します。

Bluetoothの状態はBleManager.checkState()で取得できますが、その前にListenerの登録が必要です。Bluetoothの状態を以下のように単純にTextで表示します。スマートフォンのBluetoothの設定に応じてon/offが切り替わります。

```javascript
class MainScreen extends React.Component {
  state = {
    bt_status: 'on',
  }

  componentDidMount() {
    BleManager.start({showAlert: false})
    .then(() => {
      console.log('BleManager initialized')
      bleManagerEmitter.addListener(
        'BleManagerDidUpdateState',
        (args) => {
            this.setState({bt_status: args.state})
        }
      );
      BleManager.checkState()
    })
    .catch((error) => {
        console.log(error)
    });
  }
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.status_text}>Bluetooth: {this.state.bt_status}</Text>
      </View>
    );
  }
}
```


# ペリフェラルをスキャンする
ペリフェラルのスキャンはscan() APIを利用します。結果は、BleManagerDiscoverPeripheralリスナーで返却されます。事前にリスナーを登録します。

```javascript
bleManagerEmitter.addListener(
    'BleManagerDiscoverPeripheral',
    (args) => {
    }
);
```

ペリフェラルのスキャン結果は、以下のデータで返されます。

```
id - String - the id of the peripheral
name - String - the name of the peripheral
rssi - Number - the RSSI value
advertising - JSON - the advertising payload, here are some examples:
    isConnectable - Boolean
    serviceUUIDs - Array of String
    manufacturerData - JSON - contains the raw bytes and data (Base64 
    encoded string)
    serviceData - JSON - contains the raw bytes and data (Base64 encoded string)
    txPowerLevel - Int
```

スキャンした結果をリストで表現するコードを以下のように実装します。ComponentのstateにperipheralListを設定します。BleManagerDiscoverPeripheralのリスナーでペリフェラルの情報が通知された時点で、リストに追加します。stateに設定しているので、ペリフェラルが見つかる毎にUIがリフレッシュされます。

```javascript
class MainScreen extends React.Component {
  state = {
    bt_status: 'on',
    peripheralList: [],
  };

  componentDidMount() {
    BleManager.start({showAlert: false})
    .then(() => {
      console.log('BleManager initialized');
      bleManagerEmitter.addListener(
        'BleManagerDidUpdateState',
        (args) => {
            this.setState({bt_status: args.state});
        }
      );
      bleManagerEmitter.addListener(
        'BleManagerDiscoverPeripheral',
        (args) => {
          peripheral = { 'id': args.id, 'name': args.name };
          this.state.peripheralList.push(peripheral);
          this.setState(this.state.peripheralList);
        }
      );
      BleManager.checkState();
    })
    .catch((error) => {
        console.log(error);
    });
  }

  scan() {
    BleManager.scan([], 5, false)
    .then(() => {
      console.log('Scan started');
      this.setState({peripheralList: []});
    })
    .catch((error) => {
      console.log(error);
    });
  }

  renderPeripheral({ item }) {
    return (
      <View style={styles.peripheralList}>
        <Text style={styles.id_text}>{item.id}</Text>
        <Text style={styles.id_text}>{item.name}</Text>
      </View>
    );
  }

  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.status_text}>Bluetooth: {this.state.bt_status}</Text>
        <Button title="Scan" onPress={this.scan.bind(this)} />
        <FlatList data={this.state.peripheralList} renderItem={this.renderPeripheral.bind(this)} />
      </View>
    );
  }
}
```

<img width="266" alt="スクリーンショット 2020-04-25 21.14.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85182/885d23f9-6258-9fbc-24d4-db197569013a.png">