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

ネイティブモジュール

React Nativeアプリケーションコードは、React Nativeや既存のライブラリによって提供されていないネイティブプラットフォームAPIと連携する必要があるかもしれません。その統合コードは、ターボネイティブモジュールを使って自分で書くことができます。このガイドでは、その書き方を紹介します。

基本的な手順は以下の通りです。

  1. 最も一般的なJavaScript型アノテーション言語であるFlowまたはTypeScriptのいずれかを使用して、型付きJavaScript仕様を定義します。
  2. 依存関係管理システムをCodegenを実行するように設定し、仕様をネイティブ言語インターフェースに変換します。
  3. 仕様を使用してアプリケーションコードを記述します。
  4. 生成されたインターフェースを使用してネイティブプラットフォームコードを記述し、ネイティブコードをReact Nativeランタイム環境に接続します。

これらの各手順を、例となるターボネイティブモジュールを構築しながら見ていきましょう。このガイドの残りの部分では、以下のコマンドを実行してアプリケーションを作成済みであることを前提としています。

shell
npx @react-native-community/cli@latest init TurboModuleExample --version 0.82

ネイティブ永続ストレージ

このガイドでは、Web Storage APIlocalStorageの実装方法を紹介します。このAPIは、プロジェクトでアプリケーションコードを書いているReact開発者にとって馴染み深いものです。

これをモバイルで動作させるには、AndroidおよびiOS APIを使用する必要があります。

1. 型付き仕様の宣言

React Nativeは、TypeScriptまたはFlowで書かれた仕様を受け取り、AndroidとiOS用のプラットフォーム固有のコードを生成するCodegenというツールを提供しています。この仕様は、ネイティブコードとReact Native JavaScriptランタイムの間でやり取りされるメソッドとデータ型を宣言します。ターボネイティブモジュールは、あなたの仕様、あなたが書くネイティブコード、そしてあなたの仕様から生成されたCodegenインターフェースのすべてを指します。

スペックファイルを作成するには

  1. アプリのルートフォルダ内に、specsという新しいフォルダを作成します。
  2. NativeLocalStorage.tsという新しいファイルを作成します。
情報

仕様で使用できるすべての型と、生成されるネイティブ型は、付録のドキュメントで確認できます。

以下はlocalStorage仕様の実装です。

specs/NativeLocalStorage.ts
import type {TurboModule} from 'react-native';
import {TurboModuleRegistry} from 'react-native';

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

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

2. Codegen実行の設定

仕様はReact Native Codegenツールによって、プラットフォーム固有のインターフェースとボイラープレートを生成するために使用されます。これを行うために、Codegenは私たちの仕様をどこで見つけ、どう扱うかを知る必要があります。package.jsonを更新して、以下を含めてください。

package.json
     "start": "react-native start",
"test": "jest"
},
"codegenConfig": {
"name": "NativeLocalStorageSpec",
"type": "modules",
"jsSrcsDir": "specs",
"android": {
"javaPackageName": "com.nativelocalstorage"
}
},
"dependencies": {

Codegenのすべての設定が完了したら、生成されたコードに接続するためにネイティブコードを準備する必要があります。

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

bash
cd android
./gradlew generateCodegenArtifactsFromSchema

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

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

3. ターボネイティブモジュールを使用したアプリケーションコードの作成

NativeLocalStorageを使用して、ここに永続化したいテキスト、入力フィールド、およびこの値を更新するためのいくつかのボタンを含む修正されたApp.tsxを示します。

TurboModuleRegistryは、ターボネイティブモジュールを取得する2つのモードをサポートしています。

  • get(name: string): T | null: ターボネイティブモジュールが利用できない場合、nullを返します。
  • getEnforcing(name: string): T: ターボネイティブモジュールが利用できない場合、例外をスローします。これは、モジュールが常に利用可能であることを前提としています。
App.tsx
import React from 'react';
import {
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 [editingValue, setEditingValue] = React.useState<
string | null
>(null);

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

function saveValue() {
NativeLocalStorage?.setItem(editingValue ?? EMPTY, 'myKey');
setValue(editingValue);
}

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

function deleteValue() {
NativeLocalStorage?.removeItem('myKey');
setValue('');
}

return (
<SafeAreaView style={{flex: 1}}>
<Text style={styles.text}>
Current stored value is: {value ?? 'No Value'}
</Text>
<TextInput
placeholder="Enter the text you want to store"
style={styles.textInput}
onChangeText={setEditingValue}
/>
<Button title="Save" onPress={saveValue} />
<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;

4. ネイティブプラットフォームコードの記述

すべての準備が整ったので、ネイティブプラットフォームコードの記述を開始します。これは2つの部分に分けて行います。

このガイドでは、New Architectureのみで動作するターボネイティブモジュールを作成する方法を示します。New ArchitectureとLegacy Architectureの両方をサポートする必要がある場合は、後方互換性ガイドを参照してください。

それでは、アプリケーションが閉じられた後もlocalStorageが存続するように、Androidプラットフォームコードを記述する時が来ました。

最初のステップは、生成されたNativeLocalStorageSpecインターフェースを実装することです。

android/app/src/main/java/com/nativelocalstorage/NativeLocalStorageModule.java
package com.nativelocalstorage;

import android.content.Context;
import android.content.SharedPreferences;
import com.nativelocalstorage.NativeLocalStorageSpec;
import com.facebook.react.bridge.ReactApplicationContext;

public class NativeLocalStorageModule extends NativeLocalStorageSpec {

public static final String NAME = "NativeLocalStorage";

public NativeLocalStorageModule(ReactApplicationContext reactContext) {
super(reactContext);
}

@Override
public String getName() {
return NAME;
}

@Override
public void setItem(String value, String key) {
SharedPreferences sharedPref = getReactApplicationContext().getSharedPreferences("my_prefs", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString(key, value);
editor.apply();
}

@Override
public String getItem(String key) {
SharedPreferences sharedPref = getReactApplicationContext().getSharedPreferences("my_prefs", Context.MODE_PRIVATE);
String username = sharedPref.getString(key, null);
return username;
}

@Override
public void removeItem(String key) {
SharedPreferences sharedPref = getReactApplicationContext().getSharedPreferences("my_prefs", Context.MODE_PRIVATE);
sharedPref.edit().remove(key).apply();
}
}

次に、NativeLocalStoragePackageを作成する必要があります。これは、モジュールをBase Native Packageとしてラップすることで、React Nativeランタイムにモジュールを登録するためのオブジェクトを提供します。

android/app/src/main/java/com/nativelocalstorage/NativeLocalStoragePackage.java
package com.nativelocalstorage;

import com.facebook.react.BaseReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.react.module.model.ReactModuleInfoProvider;

import java.util.HashMap;
import java.util.Map;

public class NativeLocalStoragePackage extends BaseReactPackage {

@Override
public NativeModule getModule(String name, ReactApplicationContext reactContext) {
if (name.equals(NativeLocalStorageModule.NAME)) {
return new NativeLocalStorageModule(reactContext);
} else {
return null;
}
}

@Override
public ReactModuleInfoProvider getReactModuleInfoProvider() {
return new ReactModuleInfoProvider() {
@Override
public Map<String, ReactModuleInfo> getReactModuleInfos() {
Map<String, ReactModuleInfo> map = new HashMap<>();
map.put(NativeLocalStorageModule.NAME, new ReactModuleInfo(
NativeLocalStorageModule.NAME, // name
NativeLocalStorageModule.NAME, // className
false, // canOverrideExistingModule
false, // needsEagerInit
false, // isCXXModule
true // isTurboModule
));
return map;
}
};
}
}

最後に、メインアプリケーションのReact NativeにこのPackageを見つける方法を伝える必要があります。これをReact Nativeにパッケージを「登録する」と呼びます。

この場合、getPackagesメソッドで返されるように追加します。

情報

後ほど、ビルドツールが自動的にリンクしてくれるnpmパッケージとしてネイティブモジュールを配布する方法を学びます。

android/app/src/main/java/com/turobmoduleexample/MainApplication.java
package com.inappmodule;

import android.app.Application;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactHost;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactHost;
import com.facebook.react.defaults.DefaultReactNativeHost;
import com.facebook.soloader.SoLoader;
import com.nativelocalstorage.NativeLocalStoragePackage;

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

public class MainApplication extends Application implements ReactApplication {

private final ReactNativeHost reactNativeHost = new DefaultReactNativeHost(this) {
@Override
public 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 NativeLocalStoragePackage());
return packages;
}

@Override
public String getJSMainModuleName() {
return "index";
}

@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}

@Override
public boolean isNewArchEnabled() {
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
}

@Override
public boolean isHermesEnabled() {
return BuildConfig.IS_HERMES_ENABLED;
}
};

@Override
public ReactHost getReactHost() {
return DefaultReactHost.getDefaultReactHost(getApplicationContext(), reactNativeHost);
}

@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, false);
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
DefaultNewArchitectureEntryPoint.load();
}
}
}

これで、エミュレータでコードをビルドして実行できます。

bash
npm run android