見出し画像

React Native で外部機器接続ネイティブ SDK を用いた開発を行う際の7つの tips

こんにちは!dinii でソフトウェアエンジニア兼 PM をしている長谷川(@hassey_11)です。
第4回となるエンジニアブログですが、今回は React Native を用いたネイティブ SDK 連携開発についてお話します。
dinii といえばフルスタックで JS を採用している会社ですが、実は結構ガッツリ Native 開発もしてますよ!ということが伝われば嬉しいです笑

dinii ではモバイルオーダーの Web アプリだけでなく、キャッシャーやキッチンプリンターと連動する店舗スタッフ向けのネイティブアプリも多数開発しています。
今回はその中でも、多くの店舗様から要望をいただいていたレジアプリと自動釣銭機の連携部分について、開発中に得られた知見を tips として紹介していきます。
単にネイティブ SDK を利用するだけでなく、特に外部機器接続を伴う開発も意識した tips になっていますので、(なかなか少ないとは思いますが)同様の開発を考えている方の参考になると幸いです。
また、開発する上で参考になったドキュメントもついでに載せていきます。

1. ネイティブでの開発言語は Kotlin / Swift を利用する

記事執筆時点(2020/12/26)の React Native の最新バージョン 0.63 では、ネイティブ部分のコードはそれぞれ iOS では Objective-C 、Android では Java で記述されています。
公式ドキュメントでは Native Module のサンプルコードはいずれも上記の言語で記述されていますが、それぞれ通常のネイティブアプリと同様に Swift/Kotlin で開発していくことが可能です。

Swift での記述参考記事
Kotlin での記述参考記事

React Native において Native Module 開発を行う場合、それぞれの処理に対して可能な限り Platform 固有の処理はネイティブレイヤ内で完結させることが望ましいです。
また外部接続機器が存在する場合は、接続監視などに複雑な処理が必要だったりそもそも SDK の API がかなりクセのあるものであったりする場合が往々にしてあります。
上記のような理由で、初めは小さかった Native Module もどんどん肥大化・複雑化していくことは避けられないので、いずれの Platform においてもより効率的にネイティブコードの記述ができる Swift/Kotlin の導入を初めから行っておくと良いと思います。


2. Native Module は Interface を定義して扱う

Native Module 開発を行う際にしんどい点として、NativeModule の Interface が静的解析できないということがあります。このため、単に利用するだけでは any 型として認識されてしまい、TypeScript などを使っていてもコンパイル時に型エラーを検知することができません。
またこれとは別に、外部接続機器を伴う開発を行う場合には、外部接続機器が実際にないとそれを用いた機能の開発が難しいという問題もあります。

これらの問題に対して、Native Module の Interface を定義してあてることで、コンパイル時の型チェックやモック化による機器なしでの開発を進めることが可能になるという二つのメリットを得ることができます。モック化に関しては API Client のモック化とほぼ同様の考え方ですね。
もちろん、ここで定義した型と実際の Native Module の型が正しい保証はないので、両者の型の一致はテスト等で担保する必要があります。

import {NativeModules} from "react-native";

interface HogeModuleInterface extends EventSubscriptionVendor {
 supportedEvents(): string[];
}

// この値を環境変数等で指定することで、モックを使うかどうかを容易に制御できる
const useMock = false;

export const hogeCashChangerModule: HogeModuleInterface | null = useMock
 ? mockHogeModule
 : NativeModules.HogeModule;


3. アプリ全体の Lifecycle に関わる処理はネイティブレイヤで行う

外部接続 SDK を用いる際、アプリ起動時の初期セットアップや終了時のクリーンアップを行う必要がある場合があります。

これらの処理をそれぞれブリッジ関数として用意して JS 側の App Component 等で管理すると一見見通しが良さそうに見えますが、ユーザによるアプリの kill 時や立ち上げ時に JS 側の処理が必ず呼ばれる保証はありません。

そのため、全体の Lifecycle に関わる処理はネイティブ側で記述する必要があります。
セットアップに関しては両 OS 共に init 関数を利用できます。クリーンアップに関しては少々面倒です。Android では Module ごとに onDestroy の処理を記述する API があるためそれを利用すれば良いのですが、iOS に関してはそのような API が存在しないため、static なクリーンアップ関数を用意しアプリケーションクラスの appWillTerminate 等で呼び出すなどが必要です。このアプリケーションクラスは Objective-C で記述されているため、ここのみ Objective-C による記述が避けられないです。

Android の onDestroy API
iOS の クリーンアップ関数


4. 局所的に副作用を伴う SDK の関数は hooks で wrap し利用する

例えば Bluetooth の Search API 等は、実行後に検索停止を行う必要があったりします。停止処理を呼び出さない限り、次回以降の再検索時に失敗してしまいます。
このように副作用を伴う関数の呼び出しは、React の useEffect を利用すると綺麗に書くことができ、またクリーンアップを気にせず利用することができるようになる。


export const useHogeDevice = ({skipSearch}: {skipSearch: boolean}) =>
  useEffectAsync(async () => {
   if (skipSearch) return;
   
   HogeModule.startSearchDevices(setDevices);
   return HogeModule.stopSearchDevices();
}, [skipSearch]);


5. スレッド制御はネイティブレイヤでのみ行う

接続周りの開発において、バックグラウンドのスレッド制御や排他処理は頻繁に現れます。
これらは JS 側での制御でのみ行うことは非常に険しいため、ネイティブレイヤで実現しましょう。
これを行う際にも、1 の Swift/Kotlin 採用は非常に重要です。


6. Callback を伴う SDK の関数は可能な限り Promise に変換して扱う

ネイティブでの非同期実装の実装ではよく、コールバックを用いた実装が行われています。

以下のようなケース以外は、基本的に Promise 化して JS 側で扱うと良いです。
1. callback が複数回呼ばれるケース( Promise が複数回 resolve されることはできないので、event emitter を利用する必要がある )
2. callback が呼ばれるまでにかかる時間が未知数のケース( 場合によっては Promise が解消されないので、JS の処理が終わらない不具合に繋がる )

それ以外の場合は、callback の宣言と Promise の resolve / reject を排他的に行うことで、JS 側ではシンプルな async 関数として利用できます。
2 のような場合でも、独自で timeout を実装などすれば、async として利用して問題ないかと思います。


7. 仕様書を読み込み、詳細な仕様はベンダにすぐに確認できる環境を作る

実はこれが一番大事で、SDK の仕様書には大抵の開発に必要なことや開発方針がすべて書いてある上に、開発上はまりやすいコーナーケース一覧などが記載されています。
そのため、まずはこれを丁寧に読み込んだ上で、仕様書と異なる or 記されていないものがあると推測した場合には、すぐにベンダの担当者とコミュニケーションをとるのが一番効率的な解決策になります。(もちろん、仕様書を正しく丁寧に読み込んだ上でです。)

インタフェースなどから内部実装を想像し闇雲にチューニングを重ねるよりも、ベンダに気軽に相談できるような環境構築とコミュニケーションが取れるようにしていると、心理的にもより安心して開発を進めることができます。
ありがたいことに現状弊社が利用させていただいている SDK などは総じて仕様書が非常に丁寧でわかりやすい上に、どの担当者さまもコミュニケーションを積極的にとってくださるので、非常に効率的に開発を進めることができています。

第4回はちょっと技術寄りの話になりましたが、これから React Native で Native SDK 連携をする開発をしようと思っている方の参考に少しでもなれば幸いです。
また、dinii も実は Mobile Native の結構面白い部分を開発してるということが伝われば嬉しいです。
dinii のエンジニアリングについてはこちら

この記事が気に入ったらサポートをしてみませんか?