facebook/react-native

[Integration w/ Existing App] Multiple react-native Applications Design

yelled3 opened this issue · 24 comments

I was reading:
https://facebook.github.io/react-native/docs/embedded-app.html#add-rctrootview-to-container-view
and going over the mentioned example:
https://github.com/tjwudi/EmbededReactNativeExample

and I started thinking on having an hybrid application, where some of the features are pure native iOS and other features will each have it's own react-native app (e.i each have it's own RCTRootView)

this kind of design pattern is fairly common when building large web clientside applications.
e.g Marionette.Application: http://marionettejs.com/docs/v2.4.1/marionette.application.html

so a few key questions here are:

  • does it makes sense to do so?
  • is the current react-native design supports this separation?
  • how really incapsulated is a single react-native app (RCTRootView) from another? (will there be any conflicts due to singleton objects, etc...)

Thoughts?

Yes, you're free to design your application any way you want. I 've written an article here to show how to integrate ReactView as a subview:
https://github.com/checkraiser/beginning-react-native

@checkraiser thanks for link but your article is the same as the example I've included and does not address any of my questions :-)

I am implementing this in my app, I'll write here in detail how to do so in code examples when I get to a computer, but it's based on instantiating your own RCTBridges instead of letting RCTRootView do it.
You pass to each RCTBridge instances of the modules you want to be accessible through javascript

I'm in the process of implementing something like this. I have an existing app and I'm implementing a new feature by embedding an RCTRootView..

The main issue that I've run into so far is that when an RCTRooView is embedded into a native scrollview, they don't play nicely together. With a fully native implementation, a scrollview will only send touches to its subviews if the scrollview isn't scrolling. TouchableHighlights within the RN view seem to avoid this somehow and it's impossible to drag over the RN view without pressing its buttons.

In my view controller initialization

RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:jsCodeLocation 
  moduleProvider:^ NSArray *{
    return @[self];
  } launchOptions:nil];

RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName: @"SocialWaterfall"];

Intializing the bridge manually allows you to scope each viewController instance to the current RCTRootView, so you can have multiple instances of the same module across different RCTRootViews, without clashing.

If you need some more details tell me.

vjeux commented

@jingc would probably have more context in how we currently deal with this. But as far as I know, we don't have a rock solid answer to this problem and it needs more thinking

Definitely not a rock solid solution, I think with this approach(unless I missed something) it's impossible to pass keyed modules into a bridge, meaning they are always keyed by their class name, making them restricted to 1 instance per class per bridge.

@JohnyDays few questions;

  • shouldn't I pass a weakSelf (of my viewcontroller) to the moduleProvider block?
  • have you encountered any issues with this solution?
  • can you add some more details regarding the moduleProvider?
    it didn't appear in the official docs - only thing I found about it was in RCTBridge.h

This block can be used to instantiate modules that require additional
init parameters, or additional configuration prior to being used.
The bridge will call this block to instatiate the modules, and will
be responsible for invalidating/releasing them when the bridge is destroyed.
For this reason, the block should always return new module instances, and
module instances should not be shared between bridges.

typedef NSArray *(^RCTBridgeModuleProviderBlock)(void);

and from reading the above docs, this seems like you're kinda misusing the moduleProvider block.

@vjeux @jingc
can you further share, what are currently the main issues?

The only issue I have found with this approach is that I'm limited to 1 instance per class per bridge, however the standard react export macro approach also limits you in this way.

I'm not sure what a weakSelf is, my obj-c lingo is pretty limited, but I'll check.

I found out about the moduleProvider block by reading the react source code, so this is not a publicly documented API and I recommend it in a use at your own risk kind of way

I'll explain the moduleProvider block with my use case. I have an (already developed without react native) app, this app has a menu, items in the menu can be of different types of view controllers, it was my intention for one of these types to be a react native backed view controller.

One of these is called SocialWaterfallViewController

I first tried exporting using the standard macro, with this approach, upon instantiating an RCTBridge, React automatically instantiates SocialWaterfallViewController and passes it as a module accessible through

require('NativeModules').SocialWaterfallViewController // <SocialWaterfallViewController 0x0002>

Remember I was instantiating the RCTBridge inside of this viewController itself. Why is this a problem? Because now inside javascript, I will have access to <SocialWaterfallViewController 0x0002> instance, while the instance I actually need is <SocialWaterfallViewController 0x0001>, which is the parent of the RCTRootView, and whom exports methods I have to call from javascript.

Using the approach with moduleProvider block, I pass the instance <SocialWaterfallViewController 0x0001> (e.g self) to the RCTBridge, meaning it will prefer that instance instead of instantiating a new one, and javascript now has access to the correct instance.

Thank you for reporting this issue and appreciate your patience. We've notified the core team for an update on this issue. We're looking for a response within the next 30 days or the issue may be closed.

@JohnyDays I'm using the same approach to pass a specific view controller instance to RCTBridge using moduleProviderBlock. But I have some problem with the lifecycle of the view controller instance. They won't get deallocated when they've already been popped from the navigation controller. Do you have this issue?

If I did have that problem, I didn't notice it. Not currently on the iOS track, sorry I can't help you much :\ . Since the RCTBridges themselves were recycled, I don't understand why that would happen.

Documentation for iOS and Android have been added.

@christopherdro the documentation doesn't address the issue at all...

as @vjeux said:

@jingc would probably have more context in how we currently deal with this. But as far as I know, we don't have a rock solid answer to this problem and it needs more thinking

IMO, this should remain open.

Maybe related, is it possible to pack multiple RN applications into one iOS/Android APP and make them communicate with each others?

guyca commented

Has anyone had any success adding multiple react views into an existing native Android app?
It seems that in order to add a react view to a layout, you need to create a new ReactInstanceManager using ReactInstanceManager.Builder which encapsulates the ReactBridge. Then, we need to create a ReactRootView and call startReactApplication ...

Is the Android API behind the iOS API? are there any plans to make the two apis more consistent ?

guyca commented

@buhe passing the same react instance managers to each ReactNativeView works well, thanks. But it works only when all components are defined in the same bundle. (Or am I missing something)
I want to be able to add a React component and have it use a single ReactBridge shared with components added from a different bundle file.

hslls commented

@kinhunt Have you found a solution?

guyca commented

@hslls The idea is pretty simple. You create ReactInstanceManager once and when ever adding a new ReactRootView - use the same react instance manager. Note that if you have Multiple activities, and each has a ReactRootView in it - your ReactInstanceManager might get destroyed when you go back from an activity.

You're welcome to take a look at my efforts in react-native-navigation which is a navigation library for react.

Hi there! This issue is being closed because it has been inactive for a while.

But don't worry, it will live on with ProductPains! Check out its new home: https://productpains.com/post/react-native/integration-w-existing-app-multiple-react-native-applications-design

ProductPains helps the community prioritize the most important issues thanks to its voting feature.
It is easy to use - just login with GitHub. GitHub issues have voting too, nevertheless
Product Pains has been very useful in highlighting the top bugs and feature requests:
https://productpains.com/product/react-native?tab=top

Also, if this issue is a bug, please consider sending a pull request with a fix.
We're a small team and rely on the community for bug fixes of issues that don't affect fb apps.

I know this is an old thread, but I'm actually going through the same and was wondering what are the chances of having a single ReactInstanceManager and append/remove packages as needed.

AFAIK once ReactInstanceManager there's no way to add or remove packages, and that limitation IMHO encourages developers to follow bad architectural patterns. ReactInstanceManager should allow appending and removing packages on-the-go, this way a developer would be able to have a single instance without the need to add all the required packages upon initialization (something that sometimes is not possible due to some packages depending on the current activity/fragment they are part of...).

An example of this requirement is having an Android Fragment being popped from the backstack upon the tap of a React Native button (most of the times the developer will need to have a native module with a method for doing this, and sometimes that module would need to be declared inside the fragment itself).