๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
React-Native

[React Native] Android Native-Module Event Listener ๋งŒ๋“ค๊ธฐ

by LasBe 2025. 1. 10.
๋ฐ˜์‘ํ˜•

๐Ÿ“’ [React Native] Android Native-Module Event Listener ๋งŒ๋“ค๊ธฐ


Native ๋ชจ๋“ˆ์„ ๋งŒ๋“œ๋Š” ๊ฒƒ์€ ๊ฐ„๋‹จํ•˜์ง€๋งŒ ์—ฌ๊ธฐ์— Event Listener๊ฐ€ ๋”ํ•ด์ง„๋‹ค๋ฉด?

์ฐพ์•„๋ณด์•„๋„ ๋”ฑํžˆ ์ฐธ๊ณ ํ•  ๋งŒํ•œ ์˜ˆ์ œ๋„ ์—†๊ณ , ์—…๋ฌด ์ค€๋น„์™€ ๊ณต๋ถ€๋„ ํ•  ๊ฒธ ์ œ๊ฐ€ A๋ถ€ํ„ฐ Z๊นŒ์ง€ ๋งŒ๋“ค์–ด๋ณด๊ณ  ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.

 

๐Ÿ“Œ ํด๋” ๋ฐ ํŒŒ์ผ ์ƒ์„ฑ

์•ˆ๋“œ๋กœ์ด๋“œ ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ž๋ฐ” ํ˜น์€ ์ฝ”ํ‹€๋ฆฐ ํŒŒ์ผ์„ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฝ๋กœ๋กœ ์ƒ์„ฑํ•ด์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

/android/app/src/main/java/ํŒจํ‚ค์ง€๋ช…/

 

์ €๋Š” ๋ชจ๋“ˆ์˜ ์ด๋ฆ„์„ Event๋กœ ์ง€์—ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“Œ ๋ชจ๋“ˆ ํŒŒ์ผ ์ƒ์„ฑ

// EventModule.java

package com.ํŒจํ‚ค์ง€๋ช….event;

import android.os.Handler;
import android.os.Looper;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.modules.core.DeviceEventManagerModule;

public class EventModule extends ReactContextBaseJavaModule {
  ReactApplicationContext reactContext;
  private final Handler handler = new Handler(Looper.getMainLooper());  
  private Runnable runnable;
  private boolean isSending = false;

  EventModule(ReactApplicationContext context) {
    super(context);
    this.reactContext = context;
  }

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

  private void sendEvent(String eventName, String eventData) {
    if (reactContext.hasActiveCatalystInstance()) {
        reactContext
            .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
            .emit(eventName, eventData);
    }
  }

  public void startSendingEvents() {
    if (isSending) return; // ์ด๋ฏธ ์‹คํ–‰ ์ค‘์ด๋ฉด ์ค‘๋ณต ๋ฐฉ์ง€
    isSending = true;
    runnable = new Runnable() {
        @Override
        public void run() {
            sendEvent("MyCustomEvent", "Hello from Native!");
            handler.postDelayed(this, 1000); // 1์ดˆ๋งˆ๋‹ค ์‹คํ–‰
        }
    };
    handler.post(runnable);
  }

  public void stopSendingEvents() {
    if (runnable != null) {
        handler.removeCallbacks(runnable);
        runnable = null;
        isSending = false;
    }
  }

  @ReactMethod
  public void start() {
    startSendingEvents();
  }

  @ReactMethod
  public void stop() {
    stopSendingEvents();
  }
}

๊ทธ๋Ÿผ ๋ชจ๋“ˆ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ์ฃผ์š” ๋‚ด์šฉ๋“ค์„ ํ•˜๋‚˜์”ฉ ์„ค๋ช…๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

 

๐Ÿ”Ž ํŒจํ‚ค์ง€๋ช…

package com.ํŒจํ‚ค์ง€๋ช….event;

์šฐ์„  ํ•ด๋‹น ๋ผ์ธ์€ ํ•ด๋‹น ํด๋ž˜์Šค๊ฐ€ ์†ํ•œ ํŒจํ‚ค์ง€๋ฅผ ์ •์˜ํ•˜๋Š” ์„ ์–ธ์ž…๋‹ˆ๋‹ค.

ํด๋ž˜์Šค๋ฅผ ๋…ผ๋ฆฌ์ ์œผ๋กœ ๊ทธ๋ฃนํ™”ํ•˜๊ธฐ ์œ„ํ•œ ๋„ค์ž„์ŠคํŽ˜์ด์Šค ์—ญํ• ์„ ํ•˜๋‹ˆ ํ•ด๋‹น ํ”„๋กœ์ ํŠธ์˜ ํŒจํ‚ค์ง€ ์ด๋ฆ„์„ ๋„ฃ์–ด์ฃผ๊ณ , ์†Œ์Šค ํŒŒ์ผ์˜ ์‹ค์ œ ๋””๋ ‰ํ„ฐ๋ฆฌ ๊ตฌ์กฐ์™€ ์ผ์น˜์‹œ์ผœ ์ค๋‹ˆ๋‹ค.

๐Ÿ”Ž ReactContextBaseJavaModule

ReactContextBaseJavaModule์€ React Native์—์„œ Java๋กœ ์ž‘์„ฑ๋œ Native Module์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ์ œ๊ณต๋˜๋Š” ๊ธฐ๋ณธ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. ์ด ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•˜๋ฉด React Native์™€ Android ๊ฐ„์— ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›๊ฑฐ๋‚˜ JavaScript์—์„œ Native ๊ธฐ๋Šฅ์„ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import com.facebook.react.bridge.ReactContextBaseJavaModule;

public class EventModule extends ReactContextBaseJavaModule {}

์•ˆ๋“œ๋กœ์ด๋“œ ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ํ•ต์‹ฌ ํด๋ž˜์Šค์ด๊ธฐ์— ๋ฐ˜๋“œ์‹œ extends ํ•ด์ค๋‹ˆ๋‹ค.

๐Ÿ”Ž getName ๋ฉ”์„œ๋“œ Override

public class EventModule extends ReactContextBaseJavaModule {
    @Override
    public String getName() {
      return "EventModule";
    }
}

getName ๋ฉ”์†Œ๋“œ๋ฅผ Override ํ•˜๋Š” ๊ฒƒ์€ ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ์˜ ์ด๋ฆ„์„ ์ •์˜ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ฆ‰ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์—ฌ๊ธฐ์„œ return ํ•˜๋Š” ๋ฌธ์ž์—ด๋กœ RN์—์„œ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค.

 

import { NativeModules } from 'react-native';

const { EventModule } = NativeModules;

 

๐Ÿ”Ž ReactApplicationContext์™€ EventListener

import com.facebook.react.bridge.ReactApplicationContext;

public class EventModule extends ReactContextBaseJavaModule {
  ReactApplicationContext reactContext;

  EventModule(ReactApplicationContext context) {
    super(context);
    reactContext = context;
  }

  private void sendEvent(String eventName, String eventData) {
    if (reactContext.hasActiveCatalystInstance()) {
        reactContext
          .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
          .emit(eventName, eventData);
    }
  }
}

ReactApplicationContext๋Š” Android์˜ Context๋ฅผ ์ƒ์†๋ฐ›์•„, Activity, Service, BroadcastReceiver ๋“ฑ Android ์ปดํฌ๋„ŒํŠธ์™€ ์ƒํ˜ธ์ž‘์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ์— ๋”ํ•ด JavaScript์— DeviceEventEmitter๋ฅผ ํ†ตํ•ด ๋„ค์ดํ‹ฐ๋ธŒ ์ด๋ฒคํŠธ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋„ค์ดํ‹ฐ๋ธŒ ์ด๋ฒคํŠธ๋ฅผ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‹จ๊ณ„๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

  1. context(reactContext) field ์„ ์–ธ
  2. ์ƒ์„ฑ์ž๋กœ context ์ดˆ๊ธฐํ™” ํ•ด์ฃผ๊ธฐ
  3. ์ด๋ฒคํŠธ ์ „์†กํ•˜๋Š” ํ•จ์ˆ˜ ๋งŒ๋“ค๊ธฐ

์ด๋ฒคํŠธ ์ „์†ก ๋™์ž‘์„ ์œ„ํ•œ ์ฃผ์š” ๋ชจ๋“ˆ๊ณผ ๋ฉ”์„œ๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • getJSModule()
    • reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)๋Š” JavaScript ์ด๋ฒคํŠธ ๋ชจ๋“ˆ์ธ RCTDeviceEventEmitter์— ์ ‘๊ทผํ•ฉ๋‹ˆ๋‹ค.
    • ์ด ๋ชจ๋“ˆ์€ React Native์—์„œ ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • emit(eventName, eventData)
    • emit ๋ฉ”์„œ๋“œ๋Š” ์ด๋ฒคํŠธ ์ด๋ฆ„(eventName)๊ณผ ๋ฐ์ดํ„ฐ๋ฅผ JavaScript๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
    • JavaScript ์ชฝ์—์„œ๋Š” EventEmitter๋ฅผ ์‚ฌ์šฉํ•ด ์ด ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ”Ž ๋‹ค๋ฅธ ํ•จ์ˆ˜๋“ค

์ด์™ธ์˜ ๋‹ค๋ฅธ ํ•จ์ˆ˜๋“ค์€ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์‹ ํ•˜๋Š” ๋ถ€๋ถ„์ด ์—†์–ด ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์„ ์กฐ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“  ํ•จ์ˆ˜๋“ค์ž…๋‹ˆ๋‹ค.

 

import com.facebook.react.bridge.ReactMethod;

public class EventModule extends ReactContextBaseJavaModule {
  public void startSendingEvents() {
    if (isSending) return; // ์ด๋ฏธ ์‹คํ–‰ ์ค‘์ด๋ฉด ์ค‘๋ณต ๋ฐฉ์ง€
    isSending = true;
    runnable = new Runnable() {
        @Override
        public void run() {
            sendEvent("MyCustomEvent", "Hello from Native!");
            handler.postDelayed(this, 1000); // 1์ดˆ๋งˆ๋‹ค ์‹คํ–‰
        }
    };
    handler.post(runnable);
  }

  public void stopSendingEvents() {
    if (runnable != null) {
        handler.removeCallbacks(runnable);
        runnable = null;
        isSending = false;
    }
  }

  @ReactMethod
  public void start() {
    startSendingEvents();
  }

  @ReactMethod
  public void stop() {
    stopSendingEvents();
  }
}


@ReactMethod ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ฉ”์„œ๋“œ์— ์ถ”๊ฐ€ํ•ด ์ฃผ๋ฉด RN์—์„œ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•œ ํ•จ์ˆ˜๊ฐ€ ๋˜๊ณ , handler๋‚˜ runnable์€ 1์ดˆ๋งˆ๋‹ค RN์— ์ด์ฃผ๊ธฐ ์œ„ํ•ด ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

๐Ÿ“Œ ํŒจํ‚ค์ง€ ํŒŒ์ผ ์ƒ์„ฑ

// EventPackage.java

package com.ํŒจํ‚ค์ง€๋ช….event;

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 EventPackage implements ReactPackage {
  @Override
  public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
      List<NativeModule> modules = new ArrayList<>();
      modules.add(new EventModule(reactContext));
      return modules;
  }

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

๊ทธ๋Ÿผ React Native ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ์„ ๋งŒ๋“ค์—ˆ์œผ๋‹ˆ ํŒจํ‚ค์ง€ ํŒŒ์ผ์„ ์ด์šฉํ•ด Android ํ”„๋กœ์ ํŠธ์— ์—ฐ๊ฒฐํ•ด ์ค๋‹ˆ๋‹ค.

 

์—ฌ๊ธฐ๋Š” ํŠน๋ณ„ํ•  ๊ฒŒ ์—†์œผ๋‹ˆ ๊ฐ„๋‹จํ•˜๊ฒŒ ์„ค๋ช…ํ•˜๊ณ  ๋„˜์–ด๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค.

 

ReactPackage๋ฅผ implements ํ•œ ๋’ค createNativeModules, createViewManagers ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด ์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

modules.add(new EventModule(reactContext));

๊ทธ๋ฆฌ๊ณ  ์—ฌ๊ธฐ์—๋Š” ๋ฐ˜๋“œ์‹œ ๋‚ด๊ฐ€ ๋งŒ๋“  ๋ชจ๋“ˆ์„ ๋„ฃ์–ด์ฃผ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

 

๐Ÿ“Œ MainApplication์— ๋ชจ๋“ˆ ๋“ฑ๋ก ์„ค์ •

// MainApplication.kt

package com.ํŒจํ‚ค์ง€๋ช…

import com.ํŒจํ‚ค์ง€๋ช….event.EventPackage
import ...


class MainApplication : Application(), ReactApplication {

  override val reactNativeHost: ReactNativeHost =
      object : DefaultReactNativeHost(this) {
        override fun getPackages(): List<ReactPackage> =
            PackageList(this).packages.apply {
              add(EventPackage())
            }
        // ...
      }
  // ...
}

๋ฐฉ๊ธˆ ๋งŒ๋“  ํŒจํ‚ค์ง€ ํŒŒ์ผ์„ ๊ฒฝ๋กœ์— ๋งž์ถฐ import ํ•ด์ค๋‹ˆ๋‹ค.

๊ทธ๋‹ค์Œ getPackages ๋ฉ”์„œ๋“œ ์•ˆ์— add ํ•ด์ฃผ๋ฉด ์ •๋ง ๋์ž…๋‹ˆ๋‹ค.

 

์ €๋Š” ์ตœ์‹  ๋ฒ„์ „์œผ๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์ฝ”ํ‹€๋ฆฐ์ด์ง€๋งŒ, ์ž๋ฐ” ํŒŒ์ผ์ด์–ด๋„ ์ฃผ์„์œผ๋กœ ์˜ˆ์ œ๊ฐ€ ์นœ์ ˆํ•˜๊ฒŒ ๋‚˜์™€์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋˜‘๊ฐ™์ด ๋”ฐ๋ผ ํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

๐Ÿ“Œ React Native์—์„œ ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ ์‚ฌ์šฉํ•˜๊ธฐ

// App.tsx

import React from 'react';
import {NativeEventEmitter, NativeModules, View} from 'react-native';

const {EventModule} = NativeModules;
const eventEmitter = new NativeEventEmitter(EventModule);
EventModule?.start();
eventEmitter.addListener('MyCustomEvent', data => console.log(data));

function App(): React.JSX.Element {
  return <View></View>;
}

export default App;

๊ฐ„๋‹จํ•˜๊ฒŒ ์‹คํ–‰ ํ…Œ์ŠคํŠธ๋งŒ ํ•ด๋ณด๊ธฐ ์œ„ํ•ด App.tsx์—์„œ ์ „์—ญ์œผ๋กœ ๋ถˆ๋Ÿฌ์™”์Šต๋‹ˆ๋‹ค.

๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์ฃผ์š” ๊ตฌ์„ฑ์š”์†Œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๐Ÿ”Ž NativeModules

const {EventModule} = NativeModules;

NativeModules๋Š” React Native์˜ ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œ๊ณต๋˜๋Š” ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.

๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ์˜ getNames์—์„œ return ํ•ด์ค€ ๋ฌธ์ž์—ด๋กœ ์ ‘๊ทผํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

๐Ÿ”Ž NativeEventEmitter

const eventEmitter = new NativeEventEmitter(EventModule);

 

NativeEventEmitter๋Š” ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์ด๋ฒคํŠธ๋ฅผ ๊ฐ์ง€ํ•˜๊ณ , ์ด๋ฅผ React Native์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ฃผ๋Š” ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค.

EventModule์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•˜๋„๋ก ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ”Ž ๊ฒฐ๊ณผ

EventModule?.start();
eventEmitter.addListener('MyCustomEvent', data => console.log(data));

@ReactMethod๋กœ ์ด๋ฒคํŠธ ์‹œ์ž‘์„ ํŠธ๋ฆฌ๊ฑฐํ•ด์ฃผ์—ˆ๋˜ start ํ•จ์ˆ˜๋ฅผ ๋ถˆ๋Ÿฌ์˜จ ๋’ค ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋กœ ์ˆ˜์‹ ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ˜์†”๋กœ ์ฐ์–ด๋ณธ ๊ฒฐ๊ณผ ์•„์ฃผ ์ž˜ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

 

์ „์ฒด ํ™˜๊ฒฝ์„ ํฌํ•จํ•œ ์˜ˆ์ œ๋Š” ์ œ ๊นƒํ—ˆ๋ธŒ์— ์˜ฌ๋ ค๋‘์—ˆ์œผ๋‹ˆ ํ•„์š”ํ•˜์‹œ๋‹ค๋ฉด ์ฐธ๊ณ  ๋ฐ”๋ž๋‹ˆ๋‹ค.

https://github.com/devlasbe/rn_android_native_module_event_listener

 

GitHub - devlasbe/rn_android_native_module_event_listener: React Native์˜ Android Native Module๋กœ Event Listener๋ฅผ ๋งŒ๋“œ๋Š”

React Native์˜ Android Native Module๋กœ Event Listener๋ฅผ ๋งŒ๋“œ๋Š” ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค. - devlasbe/rn_android_native_module_event_listener

github.com

 

๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€


์˜คํ”ˆ ์ฑ„ํŒ