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

ネイティブモジュールでのイベントの発行

状況によっては、プラットフォーム層で特定のイベントをリッスンし、それらをJavaScript層に発行して、アプリケーションがそのようなネイティブイベントに反応できるようにしたい場合があります。また、実行時間の長い操作がイベントを発行し、それが発生したときにUIを更新できる場合もあります。

どちらも、ネイティブモジュールからイベントを発行するための良いユースケースです。このガイドでは、その方法を学びます。

ストレージに新しいキーが追加されたときにイベントを発行する

この例では、ストレージに新しいキーが追加されたときにイベントを発行する方法を学びます。キーの値を変更してもイベントは発行されませんが、新しいキーを追加するとイベントが発行されます。

このガイドはネイティブモジュールガイドから始まります。このガイドに入る前に、そのガイドに慣れておき、可能であればそのガイドの例を実装してください。

ステップ1:NativeLocalStorageの仕様を更新する

最初のステップは、NativeLocalStorageの仕様を更新し、モジュールがイベントを発行できることをReact Nativeに認識させることです。

NativeLocalStorage.tsファイルを開き、次のように更新します。

NativeLocalStorage.ts
+import type {TurboModule, CodegenTypes} from 'react-native';
import {TurboModuleRegistry} from 'react-native';

+export type KeyValuePair = {
+ key: string,
+ value: string,
+}

export interface Spec extends TurboModule {
setItem(value: string, key: string): void;
getItem(key: string): string | null;
removeItem(key: string): void;
clear(): void;

+ readonly onKeyAdded: CodegenTypes.EventEmitter<KeyValuePair>;
}

export default TurboModuleRegistry.getEnforcing<Spec>(
'NativeLocalStorage',
);

import typeステートメントを使用すると、react-nativeからCodegenTypesをインポートしています。これにはEventEmitter型が含まれています。これにより、onKeyAddedプロパティをCodegenTypes.EventEmitter<KeyValuePair>を使用して定義でき、イベントがKeyValuePair型のペイロードを発行することを指定できます。

イベントが発行されると、string型のパラメータを受け取ることが期待されます。

ステップ2:Codegenを生成する

ネイティブモジュールの仕様を更新したので、ネイティブコードで成果物を生成するためにCodegenを再実行する必要があります。

これは、ネイティブモジュールガイドで説明されているプロセスと同じです。

Codegenは `generateCodegenArtifactsFromSchema` Gradleタスクを通じて実行されます。

bash
cd android
./gradlew generateCodegenArtifactsFromSchema

BUILD SUCCESSFUL in 837ms
14 actionable tasks: 3 executed, 11 up-to-date

これはAndroidアプリケーションをビルドする際に自動的に実行されます。

ステップ3:アプリコードを更新する

さあ、新しいイベントを処理するためにアプリのコードを更新する時が来ました。

App.tsxファイルを開き、次のように変更します。

App.tsx
import React from 'react';
import {
+ Alert,
+ EventSubscription,
SafeAreaView,
StyleSheet,
Text,
TextInput,
Button,
} from 'react-native';

import NativeLocalStorage from './specs/NativeLocalStorage';

const EMPTY = '<empty>';

function App(): React.JSX.Element {
const [value, setValue] = React.useState<string | null>(null);
+ const [key, setKey] = React.useState<string | null>(null);
+ const listenerSubscription = React.useRef<null | EventSubscription>(null);

+ React.useEffect(() => {
+ listenerSubscription.current = NativeLocalStorage?.onKeyAdded((pair) => Alert.alert(`New key added: ${pair.key} with value: ${pair.value}`));

+ return () => {
+ listenerSubscription.current?.remove();
+ listenerSubscription.current = null;
+ }
+ }, [])

const [editingValue, setEditingValue] = React.useState<
string | null
>(null);

- React.useEffect(() => {
- const storedValue = NativeLocalStorage?.getItem('myKey');
- setValue(storedValue ?? '');
- }, []);

function saveValue() {
+ if (key == null) {
+ Alert.alert('Please enter a key');
+ return;
+ }
NativeLocalStorage?.setItem(editingValue ?? EMPTY, key);
setValue(editingValue);
}

function clearAll() {
NativeLocalStorage?.clear();
setValue('');
}

function deleteValue() {
+ if (key == null) {
+ Alert.alert('Please enter a key');
+ return;
+ }
NativeLocalStorage?.removeItem(key);
setValue('');
}

+ function retrieveValue() {
+ if (key == null) {
+ Alert.alert('Please enter a key');
+ return;
+ }
+ const val = NativeLocalStorage?.getItem(key);
+ setValue(val);
+ }

return (
<SafeAreaView style={{flex: 1}}>
<Text style={styles.text}>
Current stored value is: {value ?? 'No Value'}
</Text>
+ <Text>Key:</Text>
+ <TextInput
+ placeholder="Enter the key you want to store"
+ style={styles.textInput}
+ onChangeText={setKey}
+ />
+ <Text>Value:</Text>
<TextInput
placeholder="Enter the text you want to store"
style={styles.textInput}
onChangeText={setEditingValue}
/>
<Button title="Save" onPress={saveValue} />
+ <Button title="Retrieve" onPress={retrieveValue} />
<Button title="Delete" onPress={deleteValue} />
<Button title="Clear" onPress={clearAll} />
</SafeAreaView>
);
}

const styles = StyleSheet.create({
text: {
margin: 10,
fontSize: 20,
},
textInput: {
margin: 10,
height: 40,
borderColor: 'black',
borderWidth: 1,
paddingLeft: 5,
paddingRight: 5,
borderRadius: 5,
},
});

export default App;

いくつか注目すべき関連する変更点があります。

  1. EventSubscriptionを処理するために、react-nativeからEventSubscription型をインポートする必要があります。
  2. EventSubscription参照を追跡するためにuseRefを使用する必要があります。
  3. useEffectフックを使用してリスナーを登録します。onKeyAdded関数は、関数パラメータとしてKeyValuePair型のオブジェクトを持つコールバックを受け取ります。
  4. onKeyAddedに追加されたコールバックは、イベントがNativeからJSに発行されるたびに実行されます。
  5. useEffectのクリーンアップ関数で、イベントサブスクリプションをremoveし、refをnullに設定します。

残りの変更は、この新機能のためにアプリを改善するための通常のReactの変更です。

ステップ4:ネイティブコードを記述する

すべて準備が整ったので、ネイティブプラットフォームコードの記述を開始しましょう。

ネイティブモジュールガイドで説明されているiOSのガイドに従ったと仮定すると、残っているのは、イベントを発行するコードをアプリに組み込むことです。

そのためには、次の手順を実行します。

  1. NativeLocalStorage.ktファイルを開きます。
  2. 次のように変更します。
NativeLocalStorage
package com.nativelocalstorage

import android.content.Context
import android.content.SharedPreferences
import com.nativelocalstorage.NativeLocalStorageSpec
+import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.bridge.WritableMap

class NativeLocalStorageModule(reactContext: ReactApplicationContext) : NativeLocalStorageSpec(reactContext) {

override fun getName() = NAME

override fun setItem(value: String, key: String) {
+ var shouldEmit = false
+ if (getItem(key) != null) {
+ shouldEmit = true
+ }
val sharedPref = getReactApplicationContext().getSharedPreferences("my_prefs", Context.MODE_PRIVATE)
val editor = sharedPref.edit()
editor.putString(key, value)
editor.apply()

+ if (shouldEmit == true) {
+ val eventData = Arguments.createMap().apply {
+ putString("key", key)
+ putString("value", value)
+ }
+ emitOnKeyAdded(eventData)
+ }
}

override fun getItem(key: String): String? {
val sharedPref = getReactApplicationContext().getSharedPreferences("my_prefs", Context.MODE_PRIVATE)
val username = sharedPref.getString(key, null)
return username.toString()
}

まず、NativeからJSに送信する必要があるeventDataを作成するために使用するいくつかの型をインポートする必要があります。これらのインポートは次のとおりです。

  • import com.facebook.react.bridge.Arguments
  • import com.facebook.react.bridge.WritableMap

次に、実際にイベントをJSに発行するロジックを実装する必要があります。仕様で定義されているKeyValuePairのような複雑な型の場合、CodegenはReadableMapをパラメータとして期待する関数を生成します。Arguments.createMap()ファクトリメソッドを使用してReadableMapを作成し、apply関数を使用してマップを埋めることができます。マップで使用しているキーが、JSの仕様型で定義されているプロパティと同じであることを確認するのはあなたの責任です。

ステップ5:アプリを実行する

これでアプリを実行しようとすると、この動作が表示されるはずです。

Android
iOS