About me
- 名前: Koki HASHIMOTO
- Twitter: @nullpoo
- 所属: 株式会社メルカリ
- フロントエンドやってます
Android のインテントを使って、処理結果を受け取りたい
なぜ
- 開発しようとしているアプリで、音声入力を元に表示内容を切り替える機能を作りたい
- 音声入力ダイアログを呼び出して使いたい
- どうせならAndroidとのブリッジを書いてみようと思った
- 数年前にAndroidアプリの開発をやっていたので多少は触れる(はず)
こういうやつ
Androidのインテント
Intent は、他のアプリコンポーネントからのアクションを要求するときに使用するメッセージング オブジェクトです。
(https://developer.android.com/guide/components/intents-filters.html?hl=ja)
カメラの起動、URLをブラウザで開くなどのAndroidの各機能や、他アプリの起動など連携のための仕組み
Androidでインテント遷移先の結果をハンドリング
startActivityForResultで対象Activityを呼び出し、遷移元のActivityではonActivityResultをOverrideして結果を受け取る
public static final int REQUEST_CODE_EXMAPLE = 1000;
private View.OnClickListener mClickListener = new View.OnClickListener() {
@Override public void onClick(View v) {
// SecondActivityの呼び出し
Intent intent = new Intent(getApplicationContext(), SecondActivity.class);
startActivityForResult(intent, MainActivity.REQUEST_CODE_EXMAPLE);
}
}
@Override public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == MainActivity.REQUEST_CODE_EXMAPLE) {
Log.d("EXAMPLE", data.getStringExtra("EXAMPLE_STRING"));
}
}
呼び出し元メソッドと別のメソッドで結果を受け取るため、複数遷移先がある場合など規模が大きくなると処理が散らかりがち
- RxJavaでストリームとして処理するような仕組みを作ればスマートにかけるかもしれない
React Native でブリッジするには
Android側でJS側に公開するメソッドを作成
@ReactMethod を付与するとReact native側で付与したメソッドを呼び出すことができる
@ReactMethod public void startRecognition() {
Activity currentActivity = getCurrentActivity();
// Create Intent
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE,
getReactApplicationContext().getPackageName());
// 遷移するだけで結果は受け取らない
currentActivity.startActivity(intent);
}
React Native側に公開するパッケージを作成&登録
パッケージを作成(MainActivity.java)
public class SpeechRecognizerPackage implements ReactPackage {
@Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> nativeModules = new ArrayList<>();
nativeModules.add(new SpeechRecognizerModule(reactContext));
return nativeModules;
}
...
パッケージを登録(MainApplication.java)
@Override protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(new MainReactPackage(), new SpeechRecognizerPackage());
}
JS側で公開されているメソッドを呼び出す
NativeModulesに登録したパッケージが生えているので呼び出す
import { NativeModules } from 'react-native';
const SpeechRecognizerAndroid = NativeModules.SpeechRecognizerAndroid;
recognition() {
try {
SpeechRecognizerAndroid.startRecognition();
} catch (e) {
console.log(e);
}
}
async / await を使ってstartActivityForResultの結果を受け取る
ReactMethodでPromiseを扱うことができる
ReactMethodの最後の引数にPromiseを追加すると、React native側でPromiseオブジェクトを注入してくれる
https://facebook.github.io/react-native/docs/native-modules-android.html#promises
private Promise mSpeechRecognizerPromise;
@ReactMethod public void startRecognition(Promise promise) {
...
// onActivityResultで値を渡せるように保持
mSpeechRecognizerPromise = promise;
...
// 遷移先の結果を受け取れるように呼び出す
currentActivity.startActivityForResult(intent, Constants.SPEECH_RECOGNIZER_REQUEST_CODE);
}
Promise#resolve で 正常系、rejectで異常系として返す
public SpeechRecognizerModule(ReactApplicationContext reactContext) {
super(reactContext);
reactContext.addActivityEventListener(new ActivityEventListener() {
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
if (requestCode == Constants.SPEECH_RECOGNIZER_REQUEST_CODE
&& resultCode == Activity.RESULT_OK) {
// 処理結果を取得して、Promiseに値を渡す
List<String> candidates = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
mSpeechRecognizerPromise.resolve(candidates.get(0));
}
} else {
mSpeechRecognizerPromise.reject("ERROR XXX", "Error");
}
mSpeechRecognizerPromise = null;
...
});
}
async / await でブリッジ処理を扱う
async / await を使えば Promise をより直感的に扱うことができる
async recognition() {
try {
// 遷移先の結果が返ってくるのを待って表示に反映する
const candidate = await SpeechRecognizerAndroid.startRecognition();
this.setState({candidate});
} catch (e) {
console.log(e);
}
}
render() {
return (
<div>{this.state.candidate}</div>
);
}
まとめ
- Callbackを渡す方法やAndroid→JSのブリッジを作って結果を渡す方法もある
- しかし Promise を async / await でハンドリングするほうがスマートに書ける
- 同期処理でない処理を行うブリッジは Promise で統一したほうが引数もすっきりして良さそう