How do I send events from iOS (Swift) to JavaScript in React Native?

Currently, I am in the midst of a project where my goal is to seamlessly integrate React-Native into a native Swift application. To ensure that both sides are kept in sync with the state, I have implemented a 'message bus' - a mechanism designed to facilitate the passing of events between Javascript and native environments.

The process works flawlessly when transmitting an event from JS to iOS; the event is successfully received, parsed, and processed by my Swift code. However, sending an event from Swift to Javascript proves to be more challenging, especially due to inadequate documentation, resulting in the following error:

terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Error when sending event: RCTCrossPlatformEventBus.Event with body: {   "name" : "Authenticated",   "data" : "{\"firstName\":\"1\",\"email\":\"34\",\"lastName\":\"2\",\"password\":\"3\"}" }. RCTCallableJSModules is not set. This is probably because you've explicitly synthesized the RCTCallableJSModules in RCTEventEmitter, even though it's inherited from RCTEventEmitter.'

This particular error seems to be quite common, with numerous questions on Stack Overflow and GitHub issues related to it. Unfortunately, most of the solutions available date back about ±5 years and do not offer much help in resolving my current dilemma. Below, I have outlined my implementation. Any guidance on how to overcome this obstacle would be greatly appreciated.

RCTCrossPlatformEventBus.m

#import <Foundation/Foundation.h>
#import "React/RCTBridgeModule.h"
#import "React/RCTEventEmitter.h"


@interface RCT_EXTERN_MODULE(RCTCrossPlatformEventBus, RCTEventEmitter)
    RCT_EXTERN_METHOD(supportedEvents)
    RCT_EXTERN_METHOD(processHybridEvent: (NSString *)name) // this receives JS events
@end

RCTCrossPlatformEventBus.swift

@objc(RCTCrossPlatformEventBus)
open class RCTCrossPlatformEventBus: RCTEventEmitter {
        
    override init() {
        super.init()
    }
    
    static let appShared = RCTCrossPlatformEventBus()
    
    @objc
    public override static func requiresMainQueueSetup() -> Bool {
        return true
    }
    
    /// Processes a received event received from hybrid code
    /// - Parameters:
    ///   - json: the json encoded string that was sent
    @objc func processHybridEvent(_ json: String) {
        print("Swift Native processing event: \(json)")
        DispatchQueue.main.async {
            var jsonObject: [String: Any]?
            if let jsonData = json.data(using: .utf8), let dict = try? JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) as? [String:Any] {
                jsonObject = dict
            }
            NotificationCenter.default.post(name: .RCTCrossPlatformEventBusEvent, object: self, userInfo: jsonObject)
        }
    }

    /// Posts an event to both the hybrid code
    /// - Parameters:
    ///   - json: the json encoded string that will be sent
    @objc func postEvent(json: String) {
        self.sendEvent(withName: "RCTCrossPlatformEventBus.Event", body: json)
    }
    
    open override func supportedEvents() -> [String]! {
        return ["RCTCrossPlatformEventBus.Event"]
    }
    
    open override func constantsToExport() -> [AnyHashable : Any]! {
        return [:]
      }
}

App_Bridging_Header.h

#ifndef ArchitectureDemo_Bridging_Header_h
#define ArchitectureDemo_Bridging_Header_h

#import "React/RCTBridgeModule.h"
#import "React/RCTEventEmitter.h"
#import "RCTCrossPlatformEventBus.m"

Then, in Javascript (Typescript actually)

import { NativeModules, NativeEventEmitter } from 'react-native'
import { BehaviorSubject, Observable } from 'rxjs'
const { CrossPlatformEventBus } = NativeModules;

const eventEmitter = new NativeEventEmitter(CrossPlatformEventBus)

class RNCrossPlatformEventBus {

    // we set up a private pipeline for events we can post to
    private postableEventBus = new BehaviorSubject<string>('')
    // and then expose it's observable for everyone to subscribe to
    eventBus = this.postableEventBus.asObservable()

    constructor() {
        eventEmitter.addListener('RCTCrossPlatformEventBus.Event', (body) => {
            this.processEventFromNative(body)
        })
    }

    postEvent(json: string) {
        this.postableEventBus.next(json)
        CrossPlatformEventBus.processHybridEvent(json);
    }

    processEventFromNative(jsonString: string) {
        this.postableEventBus.next(jsonString)
        console.log(`React-Native received event from native ${jsonString}`)
    }
}

export default new RNCrossPlatformEventBus()

Answer №1

After tackling the issue head-on, I have successfully resolved it and decided to share my solution here for future reference.

Upon further investigation, I discovered that initializing my RCTCrossPlatformEventBus - within my swift file - was actually an invalid operation. React-Native already handles this initialization, so instead of creating a singleton from scratch, all I needed to do was override its initializer like this:

open class RCTCrossPlatformEventBus: RCTEventEmitter {
    
    public static var shared: RCTCrossPlatformEventBus?
    
    override init() {
        super.init()
        RCTCrossPlatformEventBus.shared = self
    }
    
    ...

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

I am looking to create a password generator that saves all generated options to a file

I am looking to create a custom password generator that writes all generated options to a file. For example, using the numbers "0123456789" and having a password length of 3 characters. However, I am facing an issue with the file writing process where it ...

Is it possible to modify the sizes parameter of the GPUComputationRenderer?

Currently, I am utilizing gpuCompute = new GPUComputationRenderer( sizeX, sizeY, renderer ); for texture purposes. I am looking to update the values of sizeX and sizeY within this code snippet. However, after searching through the library, I have not been ...

Is there a problem with how the public directory is currently configured?

Recently, I completed a Webpack project and organized it in the following structure: static/ // Contains js and css files index.html // Main file I decided to integrate this setup into an Express environment by placing it inside the public/ folder. Here& ...

Utilize Discord.js v13 to stream audio directly from a specified URL

Can anyone help me figure out how to play audio from a URL using discord.js v13? I attempted this code but it's not working as expected. const connection = joinVoiceChannel({ channelId: voiceChannel.id, guildId: message.guild.id, adapterCreator ...

Error: Unable to cast value "undefined" to an ObjectId for the "_id" field in the "User" model

Whenever a user logs into their account, I am trying to retrieve their data on the login screen. The login functionality itself works perfectly, but unfortunately, the user data is not displaying. I have tried troubleshooting this issue by making changes i ...

What is the best way to extract a JSON object from a website search using getJSON or similar techniques?

Can anyone provide guidance on utilizing .getJSON() to access JSON-encoded information from a website that is being searched? I've implemented a Google Custom Search API for my site with the aim of retrieving the JSON file from the search results. Fo ...

React Native's stories-view module encounters a frustrating installation hurdle due to an unresolved dependency issue

Encountered an error while trying to install react-native-stories-view package: Error: ERESOLVE unable to resolve dependency tree While resolving the dependencies, the following issues were found: - Conflicting versions of React: Current version: 17.0. ...

In Node.js, I encountered an issue where req.body was returning as undefined, despite the fact that when I logged req, I could

I am having trouble logging the req.body to the console in my Twilio text app. When I try console.log(req), the body and its contents show up, but it says that the body is undefined when I try to log it on its own. Any help would be greatly appreciated. ...

How can you continuously calculate and show the total quantity of items in a list?

Currently, I have lists set up in my sidebar and I'm looking to include a label displaying the number of items within each list. To provide a better understanding of what I'm aiming for, I've put together a JSFiddle demonstration at the fol ...

Guide to setting up identically named properties in Child container components

As I am still fairly new to React and its concepts, I may not be executing this correctly. Although the question might seem lengthy, I'll try my best to keep it simple. I have created a component for TableHead that extends some material-ui components ...

JavaScript is failing to add items to an array

Attempting to add coordinates and user-specified data from a form to an array named "seatsArray". Here's the code snippet: <div> <img onLoad="shiftzoom.add(this,{showcoords:true,relativecoords:true,zoom:100});" id="image" src="plan1.bmp" wid ...

What is the reason behind not being able to import React under the name "react"?

Describing the issue here too, however the response lacks detail React can't be found import React from 'react' <- I am aware this statement is accurate Given that "React" is a default export and not a named export, shouldn't this s ...

NodeJs backend encounters undefined object due to FormData format request sent from Angular frontend

Never encountered this issue before despite having used this method for a long time. (Angular Frontend) const myFormData = new FormData(); myFormData.append("ok", "true"); this.http.put(my_Express_backend_url, myFormData); (Express ...

Converting a string array to an array object in JavaScript: A step-by-step guide

My task involves extracting an array from a string and manipulating its objects. var arrayString = "[{'name': 'Ruwaida Abdo'}, {'name': 'Najlaa Saadi'}]"; This scenario arises when working with a JSON file where ce ...

Issues with finding your way

Whenever I am in the History.js file and click on a product list item to navigate to another page for more details and contact the seller, the issue arises. When I click on an item in History.js, nothing happens on that page. However, when I switch to Home ...

What is the most effective way to structure a React function incorporating nested objects and mapping?

As a newcomer to Typescript, I am facing challenges in properly typing the following code snippet. I have experimented with Interfaces and individually typing properties as well, but it seems like I am only scratching the surface and encountering new typin ...

How do I customize the HOME page in JHipster to display unique content based on user roles?

I am currently developing a JHipster project where I need to display different home pages based on the role of the user logging in. Specifically, I am utilizing Angular 1.x for this project. For instance, I have roles such as ROLE_ADMIN and ROLE_USER, each ...

Modifying the delimiter used in paste range feature of ag-grid

Is there a way to enable pasting tab separated data into an ag-grid column instead of a row? Currently, when pasting newline separated data it goes into columns and tab separated goes into rows. The documentation suggests using the "clipboardDeliminator" ...

Google Maps Circle Radius Functionality Malfunctioning

I've been trying to implement a map scaling feature using a slider in my code but I'm having trouble getting it to work. While the map is displaying correctly, the radius won't update as intended. Any assistance with this would be greatly ap ...

Is there a way to display the HTML input date by simply clicking on text or an image?

I need to display a date picker when I click on specific text or an image. I am working with reactjs and utilizing the HTML input type="date", rather than the React-datepicker library. Here is the code snippet: import { useRef } from "r ...