[React Native] Native Module ์ƒ์„ฑ


๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ์ด๋ž€

React Native์—์„œ ์ง€์›ํ•˜์ง€ ์•Š๋Š” Android,IOS ๋งŒ์˜ ์ „์šฉ ๊ธฐ๋Šฅ์„ ๊ฐ€์ง„ ์ฝ”๋“œ

๐Ÿš— Android ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ๋งŒ๋“ค๊ธฐ ( Java )

Android Studio > ReactNative ํ”„๋กœ์ ํŠธ / android ํด๋” ์—ด๊ธฐ

1. ๋ชจ๋“ˆ ํด๋ž˜์Šค ๋งŒ๋“ค๊ธฐ

2. ReactNative์— ์—ฐ๊ฒฐํ•  ํŒจํ‚ค์ง€ ๋งŒ๋“ค๊ธฐ

3. ๋งŒ๋“  ํŒจํ‚ค์ง€ ๋“ฑ๋กํ•˜๊ธฐ

Copy
// app/java/com.nativemoduleworkshop/pakagename.java
package com.pakagename.newarchitecture;

import android.app.Application;
import androidx.annotation.NonNull;
import com.facebook.react.PackageList;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.ReactPackageTurboModuleManagerDelegate;
import com.facebook.react.bridge.JSIModulePackage;
import com.facebook.react.bridge.JSIModuleProvider;
import com.facebook.react.bridge.JSIModuleSpec;
import com.facebook.react.bridge.JSIModuleType;
import com.facebook.react.bridge.JavaScriptContextHolder;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.UIManager;
import com.facebook.react.fabric.ComponentFactory;
import com.facebook.react.fabric.CoreComponentsRegistry;
import com.facebook.react.fabric.EmptyReactNativeConfig;
import com.facebook.react.fabric.FabricJSIModuleProvider;
import com.facebook.react.fabric.ReactNativeConfig;
import com.facebook.react.uimanager.ViewManagerRegistry;

//ReactNative Java ๋ชจ๋“ˆ ์ƒ์†
import com.pakagename.BuildConfig;
import com.pakagename.newarchitecture.components.MainComponentsRegistry;
import com.pakagename.newarchitecture.modules.MainApplicationTurboModuleManagerDelegate;
import java.util.ArrayList;
import java.util.List;
// ReactNative์— ๋“ฑ๋กํ•  ํŒจํ‚ค์ง€๋ฅผ ๋งŒ๋“ค๊ธฐ

/**
 * A {@link ReactNativeHost} that helps you load everything needed for the New Architecture, both
 * TurboModule delegates and the Fabric Renderer.
 *
 * <p>Please note that this class is used ONLY if you opt-in for the New Architecture (see the
 * `newArchEnabled` property). Is ignored otherwise.
 */
public class MainApplicationReactNativeHost extends ReactNativeHost {
  public MainApplicationReactNativeHost(Application application) {
    super(application);
  }

  @Override
  public boolean getUseDeveloperSupport() {

  /*
        ํŒจํ‚ค์ง€๋ฅผ ๋ฐ›์•„์˜ค๋Š” ๋ถ€๋ถ„
         */
    return BuildConfig.DEBUG;
  }

  @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());
    // TurboModules must also be loaded here providing a valid TurboReactPackage implementation:
    //     packages.add(new TurboReactPackage() { ... });
    // If you have custom Fabric Components, their ViewManagers should also be loaded here
    // inside a ReactPackage.
    return packages;
  }

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

  @NonNull
  @Override
  protected ReactPackageTurboModuleManagerDelegate.Builder
      getReactPackageTurboModuleManagerDelegateBuilder() {
    // Here we provide the ReactPackageTurboModuleManagerDelegate Builder. This is necessary
    // for the new architecture and to use TurboModules correctly.
    return new MainApplicationTurboModuleManagerDelegate.Builder();

  }

  @Override
  protected JSIModulePackage getJSIModulePackage() {
    return new JSIModulePackage() {
      @Override
      public List<JSIModuleSpec> getJSIModules(
          final ReactApplicationContext reactApplicationContext,
          final JavaScriptContextHolder jsContext) {
        final List<JSIModuleSpec> specs = new ArrayList<>();

        //  @ReactMethod ๋ฅผ ๋ถ™์—ฌ์ฃผ๋ฉด,
        // ์ดํ›„ js ์ฝ”๋“œ์—์„œ ํ˜ธ์ถœ ํ•  ์ˆ˜ ์žˆ๋‹ค.

        // Here we provide a new JSIModuleSpec that will be responsible of providing the
        // custom Fabric Components.
        specs.add(
            new JSIModuleSpec() {
              @Override
              public JSIModuleType getJSIModuleType() {
                return JSIModuleType.UIManager;
              }

              @Override
              public JSIModuleProvider<UIManager> getJSIModuleProvider() {
                final ComponentFactory componentFactory = new ComponentFactory();
                CoreComponentsRegistry.register(componentFactory);

                // Here we register a Components Registry.
                // The one that is generated with the template contains no components
                // and just provides you the one from React Native core.
                MainComponentsRegistry.register(componentFactory);

                final ReactInstanceManager reactInstanceManager = getReactInstanceManager();

                ViewManagerRegistry viewManagerRegistry =
                    new ViewManagerRegistry(
                        reactInstanceManager.getOrCreateViewManagers(reactApplicationContext));

                return new FabricJSIModuleProvider(
                    reactApplicationContext,
                    componentFactory,
                    ReactNativeConfig.DEFAULT_CONFIG,
                    viewManagerRegistry);
              }
            });
        return specs;
      }
    };
  }
}

์—ฐ๊ฒฐํ•œ ํŒจํ‚ค์ง€ ์‚ฌ์šฉํ•˜๊ธฐ

Copy
// ํ”„๋กœ์ ํŠธ/App.js
import { NativeModules } from 'react-native'
const App = () => {
	const { ToastModule } = NativeModules;
    ToastModule.show('Hello Module!', ToastModule.SHORT);
	return ...
}

๐Ÿš— ์ฝ”ํ‹€๋ฆฐ์œผ๋กœ ๋งŒ๋“ค๊ธฐ

React Native ํ”„๋กœ์ ํŠธ ์ฝ”ํ‹€๋ฆฐ์œผ๋กœ ๋ณ€๊ฒฝํ•˜๊ธฐ

ํ”„๋กœ์ ํŠธ / android / build.gradle ํŒŒ์ผ ์ˆ˜์ •

Copy
...
buildscript {
  ext {
    buildToolsVersion = "31.0.0"
    minSdkVersion = 21
    compileSdkVersion = 31
    targetSdkVersion = 31
    kotlinVersion = "1.5.0" // ์ฝ”ํ‹€๋ฆฐ ์ถ”๊ฐ€
    ...
  }

  ...

  dependencies {
    classpath("com.android.tools.build:gradle:7.1.1")
    classpath("com.facebook.react:react-native-gradle-plugin")
    classpath("de.undercouch:gradle-download-task:5.0.1")
    // ์ฝ”ํ‹€๋ฆฐ ์ถ”๊ฐ€
    classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
    ...
  }
  ...
}

ํ”„๋กœ์ ํŠธ / android / app / build.gradle ํŒŒ์ผ ์ˆ˜์ •

Copy
...
dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
    ...
}
// ๋งจ ๋งˆ์ง€๋ง‰ ์ค„ ์•„๋ž˜์— ์ž‘์„ฑ
apply plugin: 'kotlin-android'

๋ชจ๋“ˆ ๋งŒ๋“ค๊ธฐ

Copy
// ํ”„๋กœ์ ํŠธ/app/java/com.nativemoduleworkshop/BrightnessModule.kt
package com.nativemoduleworkshop
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
// ReactContext ๋ชจ๋“ˆ ์ƒ์† ๋ฐ›๊ธฐ ( ์ฝ”ํ‹€๋ฆฐ ๋ฐฉ์‹ )
class BrightnessModule(reactContext: ReactApplicationContext) :
    ReactContextBaseJavaModule(reactContext) {
    // ๋ชจ๋“ˆ ์ด๋ฆ„ ์ง€์ •
    override fun getName(): String {
        return "BrightnessModule"
    }
    // ์ƒ์ˆ˜ ๋‚ด๋ณด๋‚ด๊ธฐ
    override fun getConstants(): MutableMap<String, Any>? {
        val constants = HashMap<String, Any>()
        constants.put("SAMPLE_VALUE", "Hello World")
        return constants;
    }
    // ๋ชจ๋“ˆ์—์„œ ์‚ฌ์šฉํ•  ๋ฉ”์„œ๋“œ ์ •์˜
    @ReactMethod
    fun getBrightness(){
    }
    @ReactMethod
    fun setBrightness(brightness: Float){
    }
}

ํŒจํ‚ค์ง€ ๋งŒ๋“ค๊ธฐ

Copy
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ReactShadowNode
import com.facebook.react.uimanager.ViewManager
import java.util.Collections
import kotlin.collections.ArrayList
class BrightnessPackage : ReactPackage {
    // ๋ชจ๋“ˆ ๋“ฑ๋ก
    override fun createNativeModules(reactContext: ReactApplicationContext): MutableList<NativeModule> {
        val modules = ArrayList<NativeModule>()
        modules.add(BrightnessModule(reactContext))
        return modules;
    }
    // ๋„ค์ดํ‹ฐ๋ธŒ UI ์ปดํฌ๋„ŒํŠธ ๋“ฑ๋ก
    override fun createViewManagers(reactContext: ReactApplicationContext): MutableList<ViewManager<*, ReactShadowNode<*>>> {
        return Collections.emptyList();
    }
}

ํŒจํ‚ค์ง€ ๋“ฑ๋กํ•˜๊ธฐ

Java์™€ ๋™์ผํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ํŒจํ‚ค์ง€ ๋“ฑ๋กํ•˜๊ธฐ

Copy
// app/java/com.nativemoduleworkshop/MainApplication.java
public class MainApplication extends Application implements ReactApplication {
  private final ReactNativeHost mReactNativeHost =
      new ReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() { ... }
        /*
        ํŒจํ‚ค์ง€๋ฅผ ๋ฐ›์•„์˜ค๋Š” ๋ถ€๋ถ„
         */
        @Override
        protected List<ReactPackage> getPackages() {
          @SuppressWarnings("UnnecessaryLocalVariable")
          List<ReactPackage> packages = new PackageList(this).getPackages();

          // toast ํŒจํ‚ค์ง€ ์ถ”๊ฐ€
          packages.add(new ToastPackage());
          // Brightness ํŒจํ‚ค์ง€ ์ถ”๊ฐ€
          packages.add(new BrightnessPackage());
          return packages;
        }
        @Override
        protected String getJSMainModuleName() { ... }
      };

      ...
}


๐Ÿš— IOS ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ๋งŒ๋“ค๊ธฐ ( Objective.c )

XCode > ReactNative ํ”„๋กœ์ ํŠธ / ios / ํ”„๋กœ์ ํŠธ.xcworkspace ์—ด๊ธฐ

Header File ์ƒ์„ฑ

ํ”„๋กœ์ ํŠธ / ํ”„๋กœ์ ํŠธ ํด๋” ์šฐํด๋ฆญ > New FIle > Header File

Header File ์ž‘์„ฑ, ์ €์žฅ

Copy
// RCTBridgeModule ํ—ค๋”ํŒŒ์ผ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
#import <React/RCTBridgeModule.h>
// js ํ˜ธ์ถœ ๊ฐ€๋Šฅํ•œ ๋ฉ”์„œ๋“œ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ํ—ค๋”ํŒŒ์ผ
#import <UIKit/UIKit.h>
// RCTBridgeModule ๊ฐ์ฒด๋ฅผ, RCTAlertModule๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉํ•˜๊ฒ ๋‹ค.
@interface RCTAlertModule : NSObject <RCTBridgeModule>
@end

Objective.c File ์ƒ์„ฑ

ํ”„๋กœ์ ํŠธ / ํ”„๋กœ์ ํŠธ ํด๋” ์šฐํด๋ฆญ > New FIle > Objective.c File

Objective.c File ์ž‘์„ฑ, ์ €์žฅ

Copy
 ,/// RCTAlertModule.m
// ํ—ค๋”ํŒŒ์ผ ๊ฐ€์ ธ์˜ค๊ธฐ
#import "RCTAlertModule.h"
// ํ—ค๋”ํŒŒ์ผ์—์„œ ์ž‘์„ฑํ•œ RCTAlertModule ์ƒ์† ๋ฐ›๊ธฐ
@implementation RCTAlertModule
/* ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ ๋‚ด๋ณด๋‚ด๋Š” ๋ฉ”์„œ๋“œ
 ์ธ์ž๋กœ ๋ชจ๋“ˆ์˜ ์ด๋ฆ„์„ ์ง€์ •ํ•œ๋‹ค.
 ์ธ์ž๋ฅผ ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด, ํด๋ž˜์Šค ์ด๋ฆ„์—์„œ RCT๋ฅผ ์ œ์™ธํ•œ ๋ถ€๋ถ„์„ ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค (AlertModule)
 ์ธ์ž๋ฅผ ๋ฌธ์ž์—ด ""๋กœ ์ง€์ •ํ•˜์ง€ ๋ง๊ฒƒ!
*/
RCT_EXPORT_MODULE();
// js์—์„œ ํ˜ธ์ถœ ๊ฐ€๋Šฅํ•œ ๋ฉ”์„œ๋“œ ๋งŒ๋“ค๊ธฐ
RCT_EXPORT_METHOD(alert:(NSString *)message)
{
  UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"My Alert"message:@"This is an alert." preferredStyle:UIAlertControllerStyleAlert];

  UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@:"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}];

  [alert addAction:defaultAction];

  UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;

  // UI๊ด€๋ จ ์ž‘์—…์„ main์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰
  dispatch_async(dispatch_get_main_queue(), ^{
    [rootViewController presentViewController:alert animated:YES completion:nil]
  })
}
// ์ƒ์ˆ˜ ๋‚ด๋ณด๋‚ด๊ธฐ
- (NSDictionary *)constantsToExport
{
  return @{
    @"STRING_VALUE": @"Hello World",
    @"NUMBER_VALUE": @(15)
  };
}
// ๋ชจ๋“ˆ์ด js ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์ „์—, main์Šค๋ ˆ๋“œ์—์„œ ์ƒ์ˆ˜ ์ดˆ๊ธฐํ™” ํ•˜๊ธฐ
+ (BOOL)requiresMainQueueSetup
{
  return YES;
}
@end

๋ชจ๋“ˆ ์‚ฌ์šฉํ•˜๊ธฐ

Copy
// ํ”„๋กœ์ ํŠธ/App.js
import { NativeModules } from 'react-native'
const App = () => {
    const { AlertModule } = NativeModules;
    AlertModule.alert('Hello Module!');
    console.log(AlertModule.NUMBER_VALUE);
    return ...
}

๐Ÿš— Swift๋กœ ๋งŒ๋“ค๊ธฐ

XCode > ReactNative ํ”„๋กœ์ ํŠธ / ios / ํ”„๋กœ์ ํŠธ.xcworkspace ์—ด๊ธฐ

Swift File ์ƒ์„ฑ

ํ”„๋กœ์ ํŠธ / ํ”„๋กœ์ ํŠธ ํด๋” ์šฐํด๋ฆญ > New FIle > Swift File

Create Bridging Header ์ƒ์„ฑ ( ๋ฒ„ํŠผ ํด๋ฆญ )

Copy
// NativeModuleWorkshop-Bridging-Header.h
#import "React/RCTBridgeModule.h"

Swiftย File ์ž‘์„ฑ, ์ €์žฅ

Copy
dfsdf