Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dependency Injection in React Native modules

Tags:

We're going through a gradual conversion of my application to React Native. And I keep getting into problems with Dependency Injection in React Native on iOS.

I have some services in my app I'd like to use in a native module. Currently, they are injected through Typhoon and everything works just fine.

However, react native itself initializes and maintains any native module as a singleton. That prevents me from letting Typhoon initialize them, and so I can't inject dependencies into them.

What can be done? Creating the RCTBridge myself is an option, but feels very low-level, and still need to figure out how to inject it into the UIView in the first place.

like image 225
Rubys Avatar asked May 10 '16 15:05

Rubys


2 Answers

I'm not exactly sure why your question didn't receive more up-votes; I myself was struggling to answer the same question and thought I should hop on and answer my first StackOverflow question!

Digging around the RCTBridge class provided the answer. What you need to do is initialise a RCTBridge manually with an instance of a class that implements the RCTBridgeProtocol (and importantly the method 'extraModulesForBridge'; you could even implement this protocol in your View Controller if you wanted to.

// Initialise a class that implements RCTBridgeDelegate // Be warned, RCTBridge won't store a strong reference to your delegate. // You should there store this delegate as a property of your initialising class, or implement the protocol in the View Controller itself.  id<RCTBridgeDelegate> moduleInitialiser = [[classThatImplementsRCTBridgeDelegate alloc] init];  // Create a RCTBridge instance  // (you may store this in a static context if you wish to use with other RCTViews you might initialise.   RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:moduleInitialiser launchOptions:nil];  // Initialise an RCTRootView RCTRootView *rootView = [[RCTRootView alloc]                      initWithBridge:bridge                          moduleName:kModuleName                   initialProperties:nil];  // Note that your class that implements RCTBridgeDelegate SHOULD implement the following methods and it might look something like this.  // This will return the location of our Bundle - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {     return [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios"]; }  // Importantly, this is the method we need to implement to answer this question // We must return an array of initialised Modules // Be warned, I am writing this off of the top of my head, so excuse any syntax errors there might be! - (NSArray *)extraModulesForBridge:(RCTBridge *)bridge {    return @[[OurNativeModule alloc] initWithCustomInitialiser:customDependency]]; } 

Edit: I added this to the React-native documentation. https://facebook.github.io/react-native/docs/native-modules-ios.html#dependency-injection

like image 162
Josh Hargreaves Avatar answered Oct 10 '22 08:10

Josh Hargreaves


The code above works just fine. Here's the Swift 4 version of the code.

@objc(RNModuleInitialiser) final class RNModuleInitialiser: NSObject {      //Inject your dependencies here     init() {      }  }  extension RNModuleInitialiser: RCTBridgeDelegate {      func sourceURL(for bridge: RCTBridge!) -> URL! {         return URL(string: "http://localhost:8081/index.ios.bundle?platform=ios")!     }      func extraModules(for bridge: RCTBridge!) -> [RCTBridgeModule]! {          var extraModules = [RCTBridgeModule]()          //Initialise the modules here using the dependencies injected above          return extraModules     }  } 

When initializing the bridge, pass the moduleInitialiser:

//Make sure to hold the strong reference to the moduleInitaliser  self.moduleInitialiser = RNModuleInitialiser() self.bridge = RCTBridge(delegate: self.moduleInitialiser, launchOptions: nil) 
like image 38
jarora Avatar answered Oct 10 '22 08:10

jarora