RORK LABEN
PLAY — Google Play の target API level 36 要件が昨日8月31日に発効しました。今日以降、新規アプリと既存アプリの更新は Android 16 対応が必須ですVISIBILITY — API 35 のままのアプリは掲載こそ続きますが、新しい Android 版のユーザーには表示されなくなります。エラーが出ないまま新規インストールだけが減る点に注意が要りますEXTENSION — 間に合わなかった場合は、Play Console から2026年11月1日までの延長申請が出せます。恒久対応の計画とセットで進めるのが実務的ですAPPLE — Apple 側は9月9日にイベント、iOS 27 の正式リリースは9月14日と報じられています。生成したアプリの iOS 27 実機確認はリリース週の前に済ませておきたいところですEXPO — Expo が expo-paste-input を公開しました(8月28日)。React Native の TextInput に画像・GIF・ステッカーの貼り付けを追加するネイティブモジュールですEAS — EAS Observe が8月20日に GA になりました。クラッシュや性能の観測を、ビルドや配信と同じ EAS 上で持てるようになっていますPLAY — Google Play の target API level 36 要件が昨日8月31日に発効しました。今日以降、新規アプリと既存アプリの更新は Android 16 対応が必須ですVISIBILITY — API 35 のままのアプリは掲載こそ続きますが、新しい Android 版のユーザーには表示されなくなります。エラーが出ないまま新規インストールだけが減る点に注意が要りますEXTENSION — 間に合わなかった場合は、Play Console から2026年11月1日までの延長申請が出せます。恒久対応の計画とセットで進めるのが実務的ですAPPLE — Apple 側は9月9日にイベント、iOS 27 の正式リリースは9月14日と報じられています。生成したアプリの iOS 27 実機確認はリリース週の前に済ませておきたいところですEXPO — Expo が expo-paste-input を公開しました(8月28日)。React Native の TextInput に画像・GIF・ステッカーの貼り付けを追加するネイティブモジュールですEAS — EAS Observe が8月20日に GA になりました。クラッシュや性能の観測を、ビルドや配信と同じ EAS 上で持てるようになっています
記事一覧/開発ツール
開発ツール/2026-04-30中級

Rork アプリで Bluetooth Low Energy(BLE)通信を実装する — react-native-ble-plx で IoT デバイスと連携

Rorkで作ったアプリから心拍計やセンサーなどのBLEデバイスと通信する実装ガイドです。react-native-ble-plxの選定、Android 12以降で別物になる権限、スキャンと接続、Base64でのデータ読み書き、再接続の設計、実機検証の必要性までを整理します。

Rork548Bluetooth Low EnergyBLEIoTReact Native234

「Rork で作ったアプリから心拍計の値を取りたい」「自作の M5Stack 基板にコマンドを送りたい」— BLE(Bluetooth Low Energy)が絡む相談を受けるたびに、最初の壁になるのは権限まわりとスキャン処理だと感じます。Web の解説記事の多くは Native iOS / Android が前提で、Expo・React Native・Rork の組み合わせで何をどこまで触れるのかが意外と整理されていません。

ライブラリは react-native-ble-plx 一択でいいか

React Native + Expo の世界で BLE を扱うとき、現実的な選択肢は次の二つです。

  • react-native-ble-plx: メンテナンスが活発で、Expo の prebuild と相性が良いです。iOS / Android の双方を一つの API で扱える設計になっています
  • react-native-ble-manager: 古くから使われていますが、Expo SDK のバージョンアップに追随しないことがあります

特別な理由がない限り react-native-ble-plx を選んでおくと、Expo SDK のメジャー更新時に詰む確率が下がります。Rork のビルド構成(Expo prebuild ベース)にもそのまま乗りますので、追加の native コード書き換えはほぼ発生しません。

権限の落とし穴 — Android 12 以降は別物

iOS と Android で必要な権限が大きく異なる点が、最大の落とし穴です。

  • iOS: NSBluetoothAlwaysUsageDescription を Info.plist に必ず記述します。記述がないとアプリ起動直後にクラッシュします
  • Android 12(API 31)以降: BLUETOOTH_SCANBLUETOOTH_CONNECT がランタイム権限化されました。組み合わせによっては ACCESS_FINE_LOCATION も必要です
  • Android 11 以前: BLE スキャンに ACCESS_FINE_LOCATION が必須です

つまり Android では、SDK バージョンで挙動が変わる前提で実装します。Platform.Version で分岐するか、ライブラリ側のヘルパーで権限要求を行うのが安全です。

import { PermissionsAndroid, Platform } from "react-native";
 
// Android 12 以降と 11 以前で必要な権限が変わる
async function requestBlePermissions(): Promise<boolean> {
  if (Platform.OS !== "android") return true;
 
  const apiLevel = Platform.Version as number;
  const perms =
    apiLevel >= 31
      ? [
          PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
          PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
          PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
        ]
      : [PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION];
 
  const results = await PermissionsAndroid.requestMultiple(perms);
  return Object.values(results).every(
    (r) => r === PermissionsAndroid.RESULTS.GRANTED,
  );
}
// 期待動作: 権限が全て許可されたとき true、ひとつでも拒否されたとき false を返す

「Android 12 で ACCESS_FINE_LOCATION を要求すると Google Play 審査で弾かれるのでは」と心配されるかもしれませんが、BLE 用途であれば location 権限の利用は許容されています。ストア説明文に用途を明記すれば問題なく審査を通せます。

デバイスをスキャンして接続する流れ

スキャン・接続・解放までの基本フローを一つの hook にまとめると、UI 側の実装がきれいになります。

import { useEffect, useRef, useState } from "react";
import { BleManager, Device } from "react-native-ble-plx";
 
export function useBleScanner(targetName: string) {
  const managerRef = useRef<BleManager | null>(null);
  const [device, setDevice] = useState<Device | null>(null);
  const [error, setError] = useState<string | null>(null);
 
  useEffect(() => {
    const manager = new BleManager();
    managerRef.current = manager;
 
    const start = async () => {
      const ok = await requestBlePermissions();
      if (!ok) {
        setError("Bluetooth/Location 権限が許可されていません");
        return;
      }
 
      manager.startDeviceScan(null, null, (err, scanned) => {
        if (err) {
          setError(err.message);
          return;
        }
        if (scanned?.name === targetName) {
          manager.stopDeviceScan();
          scanned
            .connect()
            .then((d) => d.discoverAllServicesAndCharacteristics())
            .then(setDevice)
            .catch((e) => setError(String(e)));
        }
      });
    };
 
    start();
 
    return () => {
      manager.stopDeviceScan();
      manager.destroy();
    };
  }, [targetName]);
 
  return { device, error };
}
// 期待動作: targetName と一致する BLE デバイスを発見したら接続し、device を state にセットする

ここで一番大事なのは discoverAllServicesAndCharacteristics() を必ず呼ぶことです。これを忘れると Service / Characteristic UUID の検索ができず、後段の readCharacteristicForDeviceService not found エラーが出ます。私はこれで2時間溶かしました。

バイト列と Base64 — データの読み書き

react-native-ble-plx は Characteristic の値を Base64 でやり取りします。生のバイト列ではないので、自前のエンコード/デコードが必要です。

import { Buffer } from "buffer";
 
// 受信: Base64 → バイト配列
const value = await device.readCharacteristicForService(
  serviceUUID,
  characteristicUUID,
);
const bytes = Buffer.from(value.value ?? "", "base64");
console.log("Heart rate:", bytes[1]); // 例: HRP プロファイルの 2 バイト目
 
// 送信: バイト配列 → Base64
const payload = Buffer.from([0x01, 0x02, 0x03]).toString("base64");
await device.writeCharacteristicWithResponseForService(
  serviceUUID,
  characteristicUUID,
  payload,
);
// 期待動作: 受信側 console に Heart rate の数値が出力され、送信は writeCharacteristicWithResponseForService の Promise が resolve する

組込側の仕様書がリトルエンディアンかビッグエンディアンか、何バイト目に何が入るかは、必ず先に確認しておきましょう。私の経験では、ここの読み違いがバグの 7 割を占めます。

切断と再接続の設計

BLE は Wi-Fi よりずっと不安定です。電車や地下に入った瞬間に切れる前提で組まないと、ユーザー体験が壊れます。

  • device.onDisconnected() で切断を検知し、UI に状態を反映します
  • アプリをバックグラウンドに送っても接続を維持したい場合は、iOS では Background Modes の bluetooth-central を有効にします
  • 自動再接続を実装するなら、指数バックオフで最大 30 秒程度に上限を設けて諦める設計が無難です

エラーハンドリングの基本的な考え方は Rork アプリのエラーハンドリングとクラッシュ対策入門 で書いた原則と同じで、「BLE は失敗するもの」として扱うのが結局いちばん安定します。

実機での検証 — シミュレータでは確認できない

BLE は Xcode のシミュレータでも Android の Emulator でも動きません。実機での確認が必須です。

  • スキャンに 10 秒以上かかる場合は、Service UUID をスキャンフィルタに渡して絞り込むとバッテリー消費も抑えられます
  • 端末側のフォーム入力と組み合わせる場合は、Rork アプリのフォームバリデーション完全ガイド で扱った zod スキーマと組み合わせると、ペアリング前に入力ミスを弾けます
  • 画面の縦横切替で接続が切れる挙動が出る場合は Rork で作ったアプリの画面回転・オリエンテーション制御が効かないときの対処法 と合わせて、Configuration 変更時の再接続ロジックを見直すと安定します

ハードウェア絡みのデバッグは「アプリ側 / ファーム側 / Bluetooth スタック」の三層で原因を切り分けると、特定の速度が一段上がります。

全体を振り返って — まずは「読み」だけ動かす

BLE 実装は「権限 → スキャン → 接続 → 読み書き → 切断」と段階が多いので、初手でいきなり全部つなげようとすると挫折しがちです。今日できる最小の一歩としては、心拍計や万歩計のような既存の BLE デバイスを一台用意して、device.readCharacteristicForService で値を1回だけ読み取るところまで書き切ってみてください。それが動けば、書き込みも通知(notify)も同じ枠組みで足し算するだけです。

シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥2,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

開発ツール2026-08-22
一括置換は全ファイル成功しました。壊れたのは、消さなかった行のほうです
生成コードに一括置換をかけると、一致した行ではなく隣の行が壊れます。運用中のプロジェクトで実際に起きた破損と、置換の不変条件を検査する20行のガードスクリプトをまとめました。
開発ツール2026-08-14
Expo SDK 57 に上げる前に、prebuild で消えるネイティブ変更を洗い出す
Expo SDK 57 では expo prebuild が既定で ios と android を消してから再生成します。手編集を上げる前に洗い出す方法、config plugin への移し替え、57.0.9 で解消したメモリ回帰までを実務の順序でまとめました。
開発ツール2026-08-10
署名付きURLに変えたらキャッシュが効かなくなった — expo-image のキーを URL から切り離す
署名付きURLは毎回クエリが変わるため、URL をそのままキーにしている限りキャッシュは当たりません。expo-image の writeToCacheAsync / readFromCacheAsync でキーを自分で握り、再ダウンロードを半減させるまでの設計と実測です。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →