react-nativereact-native-modules

React Native - Native Modules unavailable outside of App


I have a problem of accessing NativeModules.OverlayModule using react-native. In App.tsx it shows data, in LeftSidebar.tsx it is empty. I use LeftSidebar as overlay on top of other apps. here is the code.

I have this index.js

import {AppRegistry} from 'react-native';
import LeftSidebar from 'widgets/LeftSidebar';
import App from './App';
import {name} from './app.json';

AppRegistry.registerComponent(name, () => App);
AppRegistry.registerComponent('leftSidebar', () => LeftSidebar);

App.tsx (I can get data in console.log)

import FixedBottomButton from 'components/FixedBottomButton';
import {NativeModules} from 'react-native';

function App(): React.JSX.Element {
  console.log('APP', NativeModules.OverlayModule); // {disable: {}...}
  return <FixedBottomButton />;
}

export default App;

LeftSidebar.tsx (I can't get data in console.log)

import React, {memo} from 'react';
import {
  View,
  Image,
  TouchableOpacity,
  StyleSheet,
  NativeModules,
} from 'react-native';
import {xLgIcon} from 'assets/icons';
import {useLeftSidebar} from './useLeftSidebar';

function LeftSidebar(): JSX.Element {
  console.log('LeftSidebar', NativeModules.OverlayModule); // null
  const {handleClose} = useLeftSidebar();

  const list = [];

  return (
    <View style={styles.container}>
      {list.map(({source, onPress}, index) => (
        <TouchableOpacity key={index} onPress={onPress}>
          <Image source={source} style={styles.icon} />
        </TouchableOpacity>
      ))}
    </View>
  );
}

export default memo(LeftSidebar);

here is my OverlayModule.kt

package com.autoclickbot


class OverlayModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
    override fun getName() = "OverlayModule"

    @ReactMethod
    fun run(state: Boolean {
        val hasPermission = Settings.canDrawOverlays(reactContext)

        if (!hasPermission) {
            requestOverlayPermission();
            return
        }
    }

    @ReactMethod
    fun requestOverlayPermission() {
        Log.d("OverlayModule", "requestOverlayPermission method called")
        val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:${reactContext.packageName}"))
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        reactContext.startActivity(intent)
    }

    @ReactMethod
    fun enable() {
        Log.d("OverlayModule", "display method called")
        val intent = Intent(reactContext, OverlayService::class.java)
        reactContext.startService(intent)
    }

    @ReactMethod
    fun disable() {
        Log.d("OverlayModule", "closeOverlay method called")
        val intent = Intent(reactContext, OverlayService::class.java)
        reactContext.stopService(intent)
    }
}

MyAppPackage.kt

class MyAppPackage : ReactPackage {
    override fun createViewManagers(reactContext: ReactApplicationContext): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf()

    override fun createNativeModules(reactContext: ReactApplicationContext): MutableList<NativeModule> {
        return mutableListOf(
            OverlayModule(reactContext),
        )
    }
}

OverlayService.kt

class OverlayService : Service() {
    private var mWindowManager: WindowManager? = null
    private var mReactRootView: ReactRootView? = null
    private var mReactInstanceManager: ReactInstanceManager? = null

    override fun onBind(intent: Intent): IBinder? {
        return null
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.d("OverlayService", "onStartCommand called")

        val type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY 
            else WindowManager.LayoutParams.TYPE_PHONE

        val params = WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                type,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
                PixelFormat.TRANSLUCENT
        )

        params.gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL // Set gravity to top left (Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)


        mWindowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager

        mReactRootView = ReactRootView(this)
        mReactInstanceManager = ReactInstanceManager.builder()
            .setApplication(application)
            .setBundleAssetName("index.android.bundle")
            .setJSMainModulePath("index")
            .addPackage(MainReactPackage())
            .setUseDeveloperSupport(BuildConfig.DEBUG)
            .setInitialLifecycleState(LifecycleState.BEFORE_CREATE) // Use BEFORE_CREATE
            .build()

        mReactRootView?.startReactApplication(mReactInstanceManager, "leftSidebar", null)

        mWindowManager?.addView(mReactRootView, params)

        return START_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d("OverlayService", "onDestroy called")
        if (mReactRootView != null) {
            mWindowManager?.removeView(mReactRootView)
        }
    }
}

Solution

  • problem solved after adding MyAppPackage() in OverlayService.kt

    mReactInstanceManager = ReactInstanceManager.builder()
        .setApplication(application)
        .setBundleAssetName("index.android.bundle")
        .setJSMainModulePath("index")
        .addPackage(MainReactPackage())
        .addPackage(MyAppPackage()) // adding my package
        .setUseDeveloperSupport(BuildConfig.DEBUG)
        .setInitialLifecycleState(LifecycleState.BEFORE_CREATE) // Use BEFORE_CREATE
        .build()