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

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

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

どちらも、ネイティブモジュールからイベントを発行するのに適したユースケースです。このガイドでは、その方法について説明します。

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

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

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

ステップ1: NativeLocalStorageのスペックを更新する

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

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は `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. useRefを使用して、EventSubscription参照を追跡する必要があります。
  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に送信する必要があるイベントデータを作成するために使用するいくつかの型をインポートする必要があります。これらのインポートは次のとおりです。

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

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

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

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

Android
iOS