java.lang.ClassCastException: com.myapp.MainActivity cannot be cast to com.facebook.react.bridge.ReactContext

Viewed 134

I am trying to create a native ui with fragment on android

CealScanQrView.kt

class CealScanQrView(context: Context): FrameLayout(context) {
...
//Contains all the logic of integrating camerax, check below code repo to see the full source code 
...
//Now while submitting the data from native side to react-native I get error

val reactContext = context as ReactContext ///This line causes error and makes my app crash

                        reactContext
                            .getJSModule(RCTEventEmitter::class.java)
                            .receiveEvent(id, topChange, event)

}

CealScanQrFragment.kt

class CealScanQrFragment: Fragment() {
    private lateinit var cealScanQrView: CealScanQrView

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        super.onCreateView(inflater, container, savedInstanceState)
        cealScanQrView = CealScanQrView(requireNotNull(context))
        return cealScanQrView // this CustomView could be any view that you want to render
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // do any logic that should happen in an `onCreate` method, e.g:
        cealScanQrView.setUpCamera(requireActivity())
    }

    override fun onDestroy() {
        super.onDestroy()
        cealScanQrView.destroyCamera()
    }
}


CealScanQrViewManager.kt

class CealScanQrViewManager(
    private val reactContext: ReactApplicationContext
) : ViewGroupManager<FrameLayout>() {

    private val cealScanQrView = "CealScanQrView"
    private val topChange = "topChange"
    private val phasedRegistrationNames = "phasedRegistrationNames"
    private val bubbled = "bubbled"
    private val onChange = "onChange"
    private val create = "create"

    companion object {
        private const val COMMAND_CREATE = 1
    }

    private var propWidth: Int? = null
    private var propHeight: Int? = null

    override fun getName() = cealScanQrView

    override fun createViewInstance(reactContext: ThemedReactContext) = FrameLayout(reactContext)

    override fun getCommandsMap() = mapOf("create" to COMMAND_CREATE)

    override fun receiveCommand(root: FrameLayout, commandId: String?, args: ReadableArray?) {
        super.receiveCommand(root, commandId, args)
        val reactNativeViewId = requireNotNull(args).getInt(0)

        when (commandId?.toInt()) {
            COMMAND_CREATE -> createFragment(root, reactNativeViewId)
        }
    }

    private fun createFragment(root: FrameLayout, reactNativeViewId: Int) {
        val parentView = root.findViewById<ViewGroup>(reactNativeViewId)
        setupLayout(parentView)

        val myFragment = CealScanQrFragment()
        val activity = reactContext.currentActivity as FragmentActivity
        activity.supportFragmentManager
            .beginTransaction()
            .replace(reactNativeViewId, myFragment, reactNativeViewId.toString())
            .commit()
    }

    private fun setupLayout(view: View) {
        Choreographer.getInstance().postFrameCallback(object: Choreographer.FrameCallback {
            override fun doFrame(frameTimeNanos: Long) {
                manuallyLayoutChildren(view)
                view.viewTreeObserver.dispatchOnGlobalLayout()
                Choreographer.getInstance().postFrameCallback(this)
            }
        })
    }

    @ReactPropGroup(names = ["width", "height"], customType = "Style")
    fun setStyle(view: FrameLayout, index: Int, value: Int) {
        if (index == 0) propWidth = value
        if (index == 1) propHeight = value
    }

    private fun manuallyLayoutChildren(view: View) {
        // propWidth and propHeight coming from react-native props
        val width = requireNotNull(propWidth)
        val height = requireNotNull(propHeight)

        view.measure(
            View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
            View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY))

        view.layout(0, 0, width, height)
    }


    override fun getExportedCustomBubblingEventTypeConstants(): Map<String, Any> {
        return mapOf(
            topChange to mapOf(
                phasedRegistrationNames to mapOf(
                    bubbled to onChange
                )
            )
        )
    }
}

val reactContext = context as ReactContext causes the app to crash and throw error saying

 java.lang.ClassCastException: com.myapp.MainActivity cannot be cast to com.facebook.react.bridge.ReactContext

What kind of context I should pass from my fragment to Native UI View so I can use getJSModule method to send data

Full source code here

1 Answers

You can not cast android's Context class to the ReactContext.

If you will use reactContext inside your View class, then you can pass ReactContext instance from ViewManager --> Fragment --> View

1 - Write interface like this:

interface ReactContextProvider {
    fun provideReactContext(): ReactApplicationContext
}

2 - Implement this interface in your CealScanQrViewManager class:

class CealScanQrViewManager(
    private val reactContext: ReactApplicationContext
) : ViewGroupManager<FrameLayout>(), ReactContextProvider {
...

    override fun provideReactContext(): ReactApplicationContext {
        return reactContext
    }

...
}

3 - Add reactContextProvider to the CealScanQrFragment class

var reactContextProvider: ReactContextProvider? = null

4 - Pass this interface to the CealScanQrFragment class in your createFragment function on CealScanQrViewManager class.

val myFragment = CealScanQrFragment()
myFragment.reactContextProvider = this

5 - Define a variable in your CealScanQrView class:

var reactContextProvider: ReactContextProvider? = null

6 - Pass this variable to the CealScanQrView instance on your onCreateView function on CealScanQrFragment:

cealScanQrView = CealScanQrView(requireNotNull(context))
cealScanQrView.reactContextProvider = reactContextProvider

7 - Use your reactContextProvider inside View like below:

reactContextProvider?.provideReactContext()?.let { reactContext -->
    reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, topChange, event)
}
Related