メインコンテンツへスキップ

Androidネイティブモジュール

情報

Native Module と Native Components は、レガシーアーキテクチャで使用されている安定した技術です。New Architecture が安定した時点で、これらは将来的に非推奨となる予定です。New Architecture は、同様の結果を達成するために Turbo Native ModuleFabric Native Components を使用します。

Android 用 Native Modules へようこそ。Native Modules Intro を読んで、Native Modules が何であるかについて理解することから始めてください。

カレンダーネイティブモジュールの作成

以下のガイドでは、AndroidのカレンダーAPIにJavaScriptからアクセスできるネイティブモジュールCalendarModuleを作成します。最終的には、JavaScriptからCalendarModule.createCalendarEvent('Dinner Party', 'My House');を呼び出すことで、カレンダーイベントを作成するJava/Kotlinメソッドを呼び出せるようになります。

セットアップ

まず、React Nativeアプリケーション内のAndroidプロジェクトをAndroid Studioで開きます。React Nativeアプリケーション内のAndroidプロジェクトは、ここにあります。

Image of opening up an Android project within a React Native app inside of Android Studio.
Androidプロジェクトの場所の画像

ネイティブコードの記述にはAndroid Studioの使用をおすすめします。Android StudioはAndroid開発のために作られたIDEであり、これを使用することでコードの構文エラーなどの些細な問題を素早く解決できます。

Java/Kotlinコードの反復処理を高速化するために、Gradleデーモンを有効にすることもお勧めします。

カスタムネイティブモジュールファイルの作成

最初のステップは、`android/app/src/main/java/com/your-app-name/`フォルダー内(KotlinとJavaの両方で同じフォルダー)にJava/Kotlinファイル(`CalendarModule.java`または`CalendarModule.kt`)を作成することです。このJava/Kotlinファイルには、ネイティブモジュールJava/Kotlinクラスが含まれます。

Image of adding a class called CalendarModule.java within the Android Studio.
CalendarModuleClassの追加方法の画像

次に、以下の内容を追加します。

java
package com.your-apps-package-name; // replace your-apps-package-name with your app’s package name
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.util.Map;
import java.util.HashMap;

public class CalendarModule extends ReactContextBaseJavaModule {
CalendarModule(ReactApplicationContext context) {
super(context);
}
}

ご覧のとおり、`CalendarModule` クラスは `ReactContextBaseJavaModule` クラスを継承しています。Androidの場合、Java/Kotlin ネイティブモジュールは `ReactContextBaseJavaModule` を継承し、JavaScript から必要とされる機能を実装するクラスとして記述されます。

技術的には、Java/Kotlinクラスは`BaseJavaModule`クラスを継承するか、`NativeModule`インターフェースを実装するだけでReact NativeによってNative Moduleと見なされることに注意する価値があります。

ただし、上記のように`ReactContextBaseJavaModule`を使用することをお勧めします。`ReactContextBaseJavaModule`は、アクティビティライフサイクルメソッドにフックする必要があるネイティブモジュールに便利な`ReactApplicationContext`(RAC)へのアクセスを提供します。`ReactContextBaseJavaModule`を使用すると、将来的にネイティブモジュールをタイプセーフにするのも簡単になります。ネイティブモジュールのタイプセーフ(将来のリリースで導入予定)では、React Nativeは各ネイティブモジュールのJavaScript仕様を調べ、`ReactContextBaseJavaModule`を継承する抽象基底クラスを生成します。

モジュール名

AndroidのすべてのJava/Kotlinネイティブモジュールは、`getName()`メソッドを実装する必要があります。このメソッドは、ネイティブモジュールの名前を表す文字列を返します。その後、ネイティブモジュールはJavaScriptでその名前を使用してアクセスできます。たとえば、以下のコードスニペットでは、`getName()`は`"CalendarModule"`を返します。

java
// add to CalendarModule.java
@Override
public String getName() {
return "CalendarModule";
}

ネイティブモジュールはJSで次のようにアクセスできます。

tsx
const {CalendarModule} = ReactNative.NativeModules;

ネイティブメソッドをJavaScriptにエクスポートする

次に、カレンダーイベントを作成し、JavaScriptから呼び出すことができるメソッドをネイティブモジュールに追加する必要があります。JavaScriptから呼び出されることを意図したすべてのネイティブモジュールメソッドには、`@ReactMethod`アノテーションを付ける必要があります。

`CalendarModule`に`createCalendarEvent()`メソッドを設定し、`CalendarModule.createCalendarEvent()`を介してJSから呼び出せるようにします。今のところ、このメソッドは名前と場所を文字列として受け取ります。引数の型のオプションについては後ほど説明します。

java
@ReactMethod
public void createCalendarEvent(String name, String location) {
}

アプリケーションから呼び出したときにメソッドが呼び出されたことを確認するために、メソッドにデバッグログを追加します。以下は、Android utilパッケージからLogクラスをインポートして使用する方法の例です。

java
import android.util.Log;

@ReactMethod
public void createCalendarEvent(String name, String location) {
Log.d("CalendarModule", "Create event called with name: " + name
+ " and location: " + location);
}

ネイティブモジュールの実装が完了し、JavaScriptでフックアップしたら、これらの手順に従ってアプリからのログを表示できます。

同期メソッド

ネイティブメソッドに `isBlockingSynchronousMethod = true` を渡すことで、それを同期メソッドとしてマークできます。

java
@ReactMethod(isBlockingSynchronousMethod = true)

現時点では、同期的にメソッドを呼び出すとパフォーマンスに大きなペナルティが発生し、ネイティブモジュールにスレッド関連のバグが発生する可能性があるため、この方法は推奨しません。さらに、`isBlockingSynchronousMethod`を有効にすることを選択した場合、アプリはGoogle Chromeデバッガを使用できなくなることに注意してください。これは、同期メソッドがJS VMがアプリとメモリを共有することを必要とするためです。Google Chromeデバッガの場合、React NativeはGoogle ChromeのJS VM内で実行され、WebSocketを介してモバイルデバイスと非同期的に通信します。

モジュールの登録(Android固有)

ネイティブモジュールが作成されたら、React Nativeに登録する必要があります。そのためには、ネイティブモジュールを`ReactPackage`に追加し、その`ReactPackage`をReact Nativeに登録する必要があります。初期化中に、React Nativeはすべてのパッケージをループし、各`ReactPackage`内の各ネイティブモジュールを登録します。

React Native は、登録するネイティブモジュールのリストを取得するために、`ReactPackage` の `createNativeModules()` メソッドを呼び出します。Android の場合、モジュールが `createNativeModules` でインスタンス化されず、返されない場合、JavaScript から利用可能にはなりません。

ネイティブモジュールを`ReactPackage`に追加するには、まず`android/app/src/main/java/com/your-app-name/`フォルダ内に`ReactPackage`を実装する新しいJava/Kotlinクラス(`MyAppPackage.java`または`MyAppPackage.kt`)を作成します。

次に、以下の内容を追加します。

java
package com.your-app-name; // replace your-app-name with your app’s name
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class MyAppPackage implements ReactPackage {

@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}

@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();

modules.add(new CalendarModule(reactContext));

return modules;
}

}

このファイルは、作成したネイティブモジュール `CalendarModule` をインポートします。次に、`createNativeModules()` 関数内で `CalendarModule` をインスタンス化し、登録する `NativeModules` のリストとして返します。後でさらにネイティブモジュールを追加する場合は、それらもインスタンス化して、ここで返されるリストに追加できます。

このネイティブモジュール登録方法は、アプリケーション起動時にすべてのネイティブモジュールを初期化するため、アプリケーションの起動時間が長くなることに注意してください。TurboReactPackage を代替として使用できます。TurboReactPackageは、インスタンス化されたネイティブモジュールオブジェクトのリストを返す`createNativeModules`の代わりに、必要に応じてネイティブモジュールオブジェクトを作成する`getModule(String name, ReactApplicationContext rac)`メソッドを実装します。現時点では、TurboReactPackageの実装は少し複雑です。`getModule()`メソッドの実装に加えて、パッケージがインスタンス化できるすべてのネイティブモジュールのリストと、それらをインスタンス化する関数を返す`getReactModuleInfoProvider()`メソッドを実装する必要があります。例はこちらです。繰り返しになりますが、TurboReactPackageを使用するとアプリケーションの起動時間を短縮できますが、現時点では記述が少し面倒です。したがって、TurboReactPackageを使用する場合は注意して進めてください。

`CalendarModule`パッケージを登録するには、`MyAppPackage`をReactNativeHostの`getPackages()`メソッドで返されるパッケージリストに追加する必要があります。`android/app/src/main/java/com/your-app-name/`パスにある`MainApplication.java`または`MainApplication.kt`ファイルを開きます。

ReactNativeHostの`getPackages()`メソッドを見つけ、`getPackages()`が返すパッケージリストにパッケージを追加します。

java
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
packages.add(new MyAppPackage());
return packages;
}

これで、Android用ネイティブモジュールの登録が正常に完了しました!

構築したものをテストする

この時点で、Androidでネイティブモジュールの基本的な足場を設定しました。ネイティブモジュールにアクセスし、JavaScriptでエクスポートされたメソッドを呼び出すことでテストしてください。

アプリケーション内で、ネイティブモジュールの`createCalendarEvent()`メソッドへの呼び出しを追加したい場所を見つけます。以下は、アプリに追加できるコンポーネント`NewModuleButton`の例です。`NewModuleButton`の`onPress()`関数内でネイティブモジュールを呼び出すことができます。

tsx
import React from 'react';
import {NativeModules, Button} from 'react-native';

const NewModuleButton = () => {
const onPress = () => {
console.log('We will invoke the native module here!');
};

return (
<Button
title="Click to invoke your native module!"
color="#841584"
onPress={onPress}
/>
);
};

export default NewModuleButton;

JavaScriptからネイティブモジュールにアクセスするには、まずReact Nativeから`NativeModules`をインポートする必要があります。

tsx
import {NativeModules} from 'react-native';

その後、`NativeModules`から`CalendarModule`ネイティブモジュールにアクセスできます。

tsx
const {CalendarModule} = NativeModules;

CalendarModule ネイティブモジュールが利用可能になったので、ネイティブメソッド `createCalendarEvent()` を呼び出すことができます。以下では、`NewModuleButton` の `onPress()` メソッドに追加されています。

tsx
const onPress = () => {
CalendarModule.createCalendarEvent('testName', 'testLocation');
};

最後のステップは、React Nativeアプリを再構築して、最新のネイティブコード(新しいネイティブモジュールを含む!)が利用可能になるようにすることです。React Nativeアプリケーションが置かれているコマンドラインで、以下を実行します。

シェル
npm run android

イテレーション時のビルド

これらのガイドを読み進め、ネイティブモジュールを繰り返し開発する際は、JavaScriptから最新の変更にアクセスするためにアプリケーションをネイティブで再構築する必要があります。これは、記述しているコードがアプリケーションのネイティブ部分に存在するためです。React NativeのメトロバンドラーはJavaScriptの変更を監視し、その場で再構築できますが、ネイティブコードに対してはそうはしません。したがって、最新のネイティブ変更をテストしたい場合は、上記のコマンドを使用して再構築する必要があります。

まとめ✨

これで、アプリでネイティブモジュールの`createCalendarEvent()`メソッドを呼び出せるはずです。この例では、`NewModuleButton`を押すことで呼び出されます。`createCalendarEvent()`メソッドで設定したログを表示することで、これを確認できます。アプリでADBログを表示するには、これらの手順に従ってください。その後、`Log.d`メッセージ(この例では「Create event called with name: testName and location: testLocation」)を検索して、ネイティブモジュールメソッドを呼び出すたびにメッセージがログに記録されていることを確認できます。

Image of logs.
Android StudioにおけるADBログの画像

この時点で、Androidネイティブモジュールを作成し、React NativeアプリケーションのJavaScriptからネイティブメソッドを呼び出しました。ネイティブモジュールメソッドで利用できる引数の型や、コールバックとプロミスの設定方法などについて詳しく学ぶことができます。

カレンダーネイティブモジュールを超えて

より良いネイティブモジュールエクスポート

上記のように、`NativeModules`からネイティブモジュールをプルしてインポートするのは少し不格好です。

ネイティブモジュールの利用者が必要とするたびにネイティブモジュールにアクセスする必要がないように、モジュール用のJavaScriptラッパーを作成できます。`CalendarModule.js`という名前の新しいJavaScriptファイルを次の内容で作成します。

tsx
/**
* This exposes the native CalendarModule module as a JS module. This has a
* function 'createCalendarEvent' which takes the following parameters:

* 1. String name: A string representing the name of the event
* 2. String location: A string representing the location of the event
*/
import {NativeModules} from 'react-native';
const {CalendarModule} = NativeModules;
export default CalendarModule;

このJavaScriptファイルは、JavaScript側の機能を追加するのに適した場所にもなります。たとえば、TypeScriptのような型システムを使用している場合、ここにネイティブモジュールの型注釈を追加できます。React NativeはまだNativeからJSへの型安全性をサポートしていませんが、すべてのJSコードは型安全になります。そうすることで、将来的に型安全なネイティブモジュールに切り替えることも容易になります。以下は、CalendarModuleに型安全性を追加する例です。

tsx
/**
* This exposes the native CalendarModule module as a JS module. This has a
* function 'createCalendarEvent' which takes the following parameters:
*
* 1. String name: A string representing the name of the event
* 2. String location: A string representing the location of the event
*/
import {NativeModules} from 'react-native';
const {CalendarModule} = NativeModules;
interface CalendarInterface {
createCalendarEvent(name: string, location: string): void;
}
export default CalendarModule as CalendarInterface;

他のJavaScriptファイルでは、このようにネイティブモジュールにアクセスし、そのメソッドを呼び出すことができます。

tsx
import CalendarModule from './CalendarModule';
CalendarModule.createCalendarEvent('foo', 'bar');

これは、`CalendarModule`をインポートする場所が`CalendarModule.js`と同じ階層にあることを前提としています。必要に応じて相対インポートを更新してください。

引数の型

JavaScriptでネイティブモジュールメソッドが呼び出されると、React Nativeは引数をJSオブジェクトから対応するJava/Kotlinオブジェクトに変換します。たとえば、Javaネイティブモジュールメソッドがdoubleを受け入れる場合、JSではそのメソッドを数値で呼び出す必要があります。React Nativeが変換を処理します。以下は、ネイティブモジュールメソッドでサポートされている引数の型と、それらがマップされるJavaScriptの等価物のリストです。

JavaKotlinJavaScript
ブール値ブール値?boolean
ブール値ブール値
ダブルダブル?number
doublenumber
文字列文字列文字列
コールバックコールバック関数
プロミスプロミスプロミス
ReadableMapReadableMapオブジェクト
ReadableArrayReadableArray配列

以下の型は現在サポートされていますが、TurboModulesではサポートされません。使用は避けてください。

  • Integer Java/Kotlin -> ?number
  • Float Java/Kotlin -> ?number
  • int Java -> number
  • float Java -> number

上記のリストにない引数の型については、自分で変換を処理する必要があります。例えば、Androidでは`Date`型の変換は標準ではサポートされていません。以下のようにネイティブメソッド内で`Date`型への変換を自分で処理できます。

java
    String dateFormat = "yyyy-MM-dd";
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
Calendar eStartDate = Calendar.getInstance();
try {
eStartDate.setTime(sdf.parse(startDate));
}

定数のエクスポート

ネイティブモジュールは、JSで利用可能なネイティブメソッド`getConstants()`を実装することで定数をエクスポートできます。以下では、`getConstants()`を実装し、JavaScriptでアクセスできる`DEFAULT_EVENT_NAME`定数を含むマップを返します。

java
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put("DEFAULT_EVENT_NAME", "New Event");
return constants;
}

定数は、JSでネイティブモジュールに対して`getConstants`を呼び出すことでアクセスできます。

tsx
const {DEFAULT_EVENT_NAME} = CalendarModule.getConstants();
console.log(DEFAULT_EVENT_NAME);

技術的には、`getConstants()`でエクスポートされた定数にネイティブモジュールオブジェクトから直接アクセスすることは可能です。しかし、これはTurboModulesではサポートされなくなるため、将来的に不要な移行を避けるために、上記のアプローチに切り替えることを推奨します。

現在、定数は初期化時にのみエクスポートされるため、実行時にgetConstantsの値を変更してもJavaScript環境には影響しません。これはTurbomodulesで変更されます。Turbomodulesでは、`getConstants()`は通常のネイティブモジュールメソッドになり、呼び出しごとにネイティブ側がヒットします。

コールバック

ネイティブモジュールは、特別な種類の引数、つまりコールバックもサポートしています。コールバックは、非同期メソッドのためにJava/KotlinからJavaScriptにデータを渡すために使用されます。また、ネイティブ側からJavaScriptを非同期に実行するためにも使用できます。

コールバックを持つネイティブモジュールメソッドを作成するには、まず `Callback` インターフェースをインポートし、次にネイティブモジュールメソッドに `Callback` 型の新しいパラメータを追加します。コールバック引数にはいくつかのニュアンスがありますが、これらは TurboModules で間もなく解消されます。まず、関数引数には成功コールバックと失敗コールバックの2つのコールバックしか持てません。さらに、ネイティブモジュールメソッド呼び出しの最後の引数が関数の場合、それは成功コールバックとして扱われ、2番目の最後の引数が関数の場合、それは失敗コールバックとして扱われます。

java
import com.facebook.react.bridge.Callback;

@ReactMethod
public void createCalendarEvent(String name, String location, Callback callBack) {
}

Java/Kotlinメソッドでコールバックを呼び出し、JavaScriptに渡したいデータを指定できます。ネイティブコードからJavaScriptにはシリアル化可能なデータしか渡せないことに注意してください。ネイティブオブジェクトを渡す必要がある場合は`WriteableMaps`を使用し、コレクションを使用する必要がある場合は`WritableArrays`を使用できます。また、ネイティブ関数の完了後すぐにコールバックが呼び出されるわけではないことを強調することも重要です。以下では、以前の呼び出しで作成されたイベントのIDがコールバックに渡されます。

java
  @ReactMethod
public void createCalendarEvent(String name, String location, Callback callBack) {
Integer eventId = ...
callBack.invoke(eventId);
}

このメソッドはJavaScriptで次のようにアクセスできます。

tsx
const onPress = () => {
CalendarModule.createCalendarEvent(
'Party',
'My House',
eventId => {
console.log(`Created a new event with id ${eventId}`);
},
);
};

もう一つ重要な注意点は、ネイティブモジュールのメソッドは一度に1つのコールバックしか呼び出せないということです。つまり、成功コールバックと失敗コールバックのいずれか一方しか呼び出せず、各コールバックは最大1回しか呼び出されません。ただし、ネイティブモジュールはコールバックを保存して後で呼び出すことができます。

コールバックでのエラー処理には2つのアプローチがあります。1つ目は、Nodeの慣例に従い、コールバックに渡される最初の引数をエラーオブジェクトとして扱う方法です。

java
  @ReactMethod
public void createCalendarEvent(String name, String location, Callback callBack) {
Integer eventId = ...
callBack.invoke(null, eventId);
}

JavaScriptでは、最初の引数をチェックしてエラーが渡されたかどうかを確認できます。

tsx
const onPress = () => {
CalendarModule.createCalendarEvent(
'testName',
'testLocation',
(error, eventId) => {
if (error) {
console.error(`Error found! ${error}`);
}
console.log(`event id ${eventId} returned`);
},
);
};

もう一つのオプションは、onSuccessとonFailureコールバックを使用することです。

java
@ReactMethod
public void createCalendarEvent(String name, String location, Callback myFailureCallback, Callback mySuccessCallback) {
}

その後、JavaScriptでは、エラー応答と成功応答用に別々のコールバックを追加できます。

tsx
const onPress = () => {
CalendarModule.createCalendarEvent(
'testName',
'testLocation',
error => {
console.error(`Error found! ${error}`);
},
eventId => {
console.log(`event id ${eventId} returned`);
},
);
};

プロミス

ネイティブモジュールはPromiseも満たすことができ、特にES2016のasync/await構文を使用する場合に、JavaScriptを簡素化できます。ネイティブモジュールJava/Kotlinメソッドの最後のパラメータがPromiseである場合、対応するJSメソッドはJS Promiseオブジェクトを返します。

上記のコードをコールバックの代わりにPromiseを使用するようにリファクタリングすると、次のようになります。

java
import com.facebook.react.bridge.Promise;

@ReactMethod
public void createCalendarEvent(String name, String location, Promise promise) {
try {
Integer eventId = ...
promise.resolve(eventId);
} catch(Exception e) {
promise.reject("Create Event Error", e);
}
}

コールバックと同様に、ネイティブモジュールメソッドはプロミスを拒否または解決(両方は不可)でき、最大1回しか実行できません。つまり、成功コールバックまたは失敗コールバックのいずれか一方しか呼び出せず、各コールバックは最大1回しか呼び出されません。ただし、ネイティブモジュールはコールバックを保存して後で呼び出すことができます。

このメソッドのJavaScript側はPromiseを返します。つまり、非同期関数内で`await`キーワードを使用して呼び出し、結果を待つことができます。

tsx
const onSubmit = async () => {
try {
const eventId = await CalendarModule.createCalendarEvent(
'Party',
'My House',
);
console.log(`Created a new event with id ${eventId}`);
} catch (e) {
console.error(e);
}
};

rejectメソッドは、以下の引数の様々な組み合わせを受け取ります。

java
String code, String message, WritableMap userInfo, Throwable throwable

詳細については、`Promise.java`インターフェースがこちらにあります。`userInfo`が提供されない場合、ReactNativeはそれをnullに設定します。残りのパラメーターについては、React Nativeはデフォルト値を使用します。`message`引数は、エラーコールスタックの最上部に表示されるエラー`message`を提供します。以下は、Java/Kotlinでの以下のreject呼び出しからJavaScriptに表示されるエラーメッセージの例です。

Java/Kotlinのreject呼び出し

java
promise.reject("Create Event error", "Error parsing date", e);

Promiseが拒否されたときのReact Nativeアプリでのエラーメッセージ

Image of error message in React Native app.
エラーメッセージの画像

JavaScriptへのイベント送信

ネイティブモジュールは、直接呼び出されなくてもJavaScriptにイベントを通知できます。たとえば、ネイティブのAndroidカレンダーアプリからのカレンダーイベントが間もなく発生することをJavaScriptに通知したい場合があります。これを行う最も簡単な方法は、以下のコードスニペットのように`ReactContext`から取得できる`RCTDeviceEventEmitter`を使用することです。

java
...
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.Arguments;
...
private void sendEvent(ReactContext reactContext,
String eventName,
@Nullable WritableMap params) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}

private int listenerCount = 0;

@ReactMethod
public void addListener(String eventName) {
if (listenerCount == 0) {
// Set up any upstream listeners or background tasks as necessary
}

listenerCount += 1;
}

@ReactMethod
public void removeListeners(Integer count) {
listenerCount -= count;
if (listenerCount == 0) {
// Remove upstream listeners, stop unnecessary background tasks
}
}
...
WritableMap params = Arguments.createMap();
params.putString("eventProperty", "someValue");
...
sendEvent(reactContext, "EventReminder", params);

JavaScriptモジュールは、NativeEventEmitterクラスで`addListener`を使用してイベントを受信するように登録できます。

tsx
import {NativeEventEmitter, NativeModules} from 'react-native';
...
useEffect(() => {
const eventEmitter = new NativeEventEmitter(NativeModules.ToastExample);
let eventListener = eventEmitter.addListener('EventReminder', event => {
console.log(event.eventProperty) // "someValue"
});

// Removes the listener once unmounted
return () => {
eventListener.remove();
};
}, []);

startActivityForResultからアクティビティ結果を取得する

`startActivityForResult`で開始したアクティビティの結果を取得したい場合は、`onActivityResult`をリッスンする必要があります。これを行うには、`BaseActivityEventListener`を拡張するか、`ActivityEventListener`を実装する必要があります。API変更に対してより堅牢であるため、前者が推奨されます。次に、モジュールのコンストラクタで次のようにリスナーを登録する必要があります。

java
reactContext.addActivityEventListener(mActivityResultListener);

これで、以下のメソッドを実装することで`onActivityResult`をリッスンできます。

java
@Override
public void onActivityResult(
final Activity activity,
final int requestCode,
final int resultCode,
final Intent intent) {
// Your logic here
}

これを実証するために、基本的な画像ピッカーを実装しましょう。画像ピッカーはJavaScriptに`pickImage`メソッドを公開し、呼び出されると画像のパスを返します。

kotlin
public class ImagePickerModule extends ReactContextBaseJavaModule {

private static final int IMAGE_PICKER_REQUEST = 1;
private static final String E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST";
private static final String E_PICKER_CANCELLED = "E_PICKER_CANCELLED";
private static final String E_FAILED_TO_SHOW_PICKER = "E_FAILED_TO_SHOW_PICKER";
private static final String E_NO_IMAGE_DATA_FOUND = "E_NO_IMAGE_DATA_FOUND";

private Promise mPickerPromise;

private final ActivityEventListener mActivityEventListener = new BaseActivityEventListener() {

@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent intent) {
if (requestCode == IMAGE_PICKER_REQUEST) {
if (mPickerPromise != null) {
if (resultCode == Activity.RESULT_CANCELED) {
mPickerPromise.reject(E_PICKER_CANCELLED, "Image picker was cancelled");
} else if (resultCode == Activity.RESULT_OK) {
Uri uri = intent.getData();

if (uri == null) {
mPickerPromise.reject(E_NO_IMAGE_DATA_FOUND, "No image data found");
} else {
mPickerPromise.resolve(uri.toString());
}
}

mPickerPromise = null;
}
}
}
};

ImagePickerModule(ReactApplicationContext reactContext) {
super(reactContext);

// Add the listener for `onActivityResult`
reactContext.addActivityEventListener(mActivityEventListener);
}

@Override
public String getName() {
return "ImagePickerModule";
}

@ReactMethod
public void pickImage(final Promise promise) {
Activity currentActivity = getCurrentActivity();

if (currentActivity == null) {
promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist");
return;
}

// Store the promise to resolve/reject when picker returns data
mPickerPromise = promise;

try {
final Intent galleryIntent = new Intent(Intent.ACTION_PICK);

galleryIntent.setType("image/*");

final Intent chooserIntent = Intent.createChooser(galleryIntent, "Pick an image");

currentActivity.startActivityForResult(chooserIntent, IMAGE_PICKER_REQUEST);
} catch (Exception e) {
mPickerPromise.reject(E_FAILED_TO_SHOW_PICKER, e);
mPickerPromise = null;
}
}
}

ライフサイクルイベントのリッスン

アクティビティのライフサイクルイベント(`onResume`、`onPause`など)をリッスンする方法は、`ActivityEventListener`の実装方法と非常に似ています。モジュールは`LifecycleEventListener`を実装する必要があります。次に、モジュールのコンストラクタで次のようにリスナーを登録する必要があります。

java
reactContext.addLifecycleEventListener(this);

これで、以下のメソッドを実装することで、アクティビティのライフサイクルイベントをリッスンできます。

java
@Override
public void onHostResume() {
// Activity `onResume`
}
@Override
public void onHostPause() {
// Activity `onPause`
}
@Override
public void onHostDestroy() {
// Activity `onDestroy`
}

スレッド処理

現在まで、Androidではすべてのネイティブモジュール非同期メソッドが1つのスレッドで実行されます。ネイティブモジュールは、呼び出し元のスレッドについていかなる仮定もすべきではありません。現在の割り当ては将来変更される可能性があります。ブロッキング呼び出しが必要な場合、重い処理は内部で管理されるワーカースレッドにディスパッチされ、そこからコールバックが配信されるべきです。