Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Perf] Small delay displaying RCTRootView after a push #1277

Closed
marcshilling opened this issue May 14, 2015 · 19 comments
Closed

[Perf] Small delay displaying RCTRootView after a push #1277

marcshilling opened this issue May 14, 2015 · 19 comments
Labels
Resolution: Locked This issue was locked by the bot.

Comments

@marcshilling
Copy link

I am attempting to integrate React Native into an existing Objective-C app. My goal is to replace a UIViewController's root UIView with an RCTRootView. Following these instructions https://facebook.github.io/react-native/docs/embedded-app.html, I am doing the following in viewDidLoad: of the view controller:

NSURL *jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle"];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"SimpleApp" launchOptions:nil];
rootView.frame = self.view.frame;
self.view = rootView;

Everything works fine, except that there is a very small (maybe half second) delay between when the view controller is pushed and the React Native view is actually displayed. I tried pre-bundling the javascript but there was no difference. I can only assume this is because the javascript is loaded asynchronously. Is there anyway to guarantee the view is loaded so that it is immediately seen upon a push transition?

@ide
Copy link
Contributor

ide commented May 14, 2015

There's an NSNotification called RCTJavaScriptDidLoadNotification that runs once the JS has finished downloading you could listen to. Alternatively you could write a native module that lets JS tell ObjC when your React view has mounted.

@marcshilling
Copy link
Author

@ide I don't really care about knowing when the JS has finished downloading, because as far as I can tell that when will never be fast enough.

What you are suggesting is an interaction like this:

  1. User taps a table view cell
  2. Wait for the RCTJavaScriptDidLoadNotification to fire
  3. Push detail view controller

To me, this is unacceptable. A native iOS user would expect there to be no delay between steps 1 and 3.

@ide
Copy link
Contributor

ide commented May 14, 2015

I understand you want to improve perceived performance. Two things you could do (separately or together) are:

  1. Immediately push a view controller that displays a loading indicator. This way the UI immediately responds when the user taps the table cell and also lets them know the app is loading content and isn't stalled.
  2. Preload a hidden RCTRootView in advance, mounting an empty component just to set up the JS environment. When the user taps the table cell and you know the content to display, and the RCTRootView has successfully finished loading (known via the technique in my first post), send an event from native to JS with the data you want to display and show the RCTRootView.

@nicklockwood
Copy link
Contributor

@marcshilling The JS can be preloaded by the bridge in advance of your RCTRootView being displayed. I suggest storing an RCTBridge instance in your app delegate and preloading the JS. You can then create a new RCTRootView on each tap, using the same bridge instance (via -[RCTRootView initWithBridge:]), and it should load much more quickly.

@ide
Copy link
Contributor

ide commented May 14, 2015

@nicklockwood's suggestion is really clean and should work well. ++

@nicklockwood
Copy link
Contributor

We should probably provide a different template for apps with multiple RCTRootViews. Or maybe just deprecate the RCTRootView convenience constructor that obscures the existence of RCTBridge.

@dsibiski
Copy link
Contributor

👍 on @nicklockwood's suggestion as well. I'm using this method in an existing iOS app and it has been working wonderfully for me.

I've noticed also that even with this method, there can still be a very slight delay between when the view is pushed and when the content gets rendered. So, in some cases, I'm taking a hybrid approach. Not only do I have the RCTBridge instance in my app delegate, but I also pre-load my RCTRootViews when the calling view loads. Then, when it's time to push the rootView, it's already been loaded and there is no delay at all. Of course, this isn't feasible in all situations (might not work in a UITableView), but it does work.

@brentvatne brentvatne changed the title Small delay displaying RCTRootView after a push [Perf] Small delay displaying RCTRootView after a push May 30, 2015
@brentvatne
Copy link
Collaborator

@dsibiski - any interest in updating the Embedded App docs to reflect your approach? It seems like you have a good amount of experience with it 😄

@nicklockwood

We should probably provide a different template for apps with multiple RCTRootViews. Or maybe just deprecate the RCTRootView convenience constructor that obscures the existence of RCTBridge.

Should we spin this off into a separate issue?

@nicklockwood
Copy link
Contributor

You might be interested in the new RCTContentDidAppearNotification in the latest release btw :-)

@cxfeng1-zz
Copy link
Contributor

@nicklockwood Unfortunately the RCTBridge must be initialized on the main thread, if I preload a bridge in my app delegate, it will not only block my app's launch but also consume a lot of memory, any advice about this?

@nicklockwood
Copy link
Contributor

I'm surprised that RCTBridge blocks your app loading noticably. It's true that it has to be initialized on the main thread, but most of what it does afterwards is done on a background thread.

@nicklockwood
Copy link
Contributor

You can load the bridge whenever you want, it doesn't have to be at app launch time. For example, you might load it on the screen before you present your RCTRootView controller so that by the time the user presses the button to display the React view, it's already loaded the JS. You could then throw it away again if they go somewhere else in the app.

To avoid the additional delay due to the JS app initialization, you could also try pre-creating the RCTRootView off-screen, and then set it as the root of your view controller when it's presented.

@cxfeng1-zz
Copy link
Contributor

@nicklockwood Thanks for your advice, In my case, the RCTRootView must be preloaded at app launch time, but my app is a pretty large app which is very sensitive about what can be done at launch( RCTBridge's setup method now takes about 500ms, it must be called on main thread, and if the bridge is not initialized, the JS can not be downloaded and loaded.)

So I'm looking forward to a setup API which can be called on a background thread completely, for example, it can set up a context which have downloaded and loaded the JS on the background thread , and when I come to main thread, I can initialize a bridge with the context, thus save a lot of time.

@idibidiart
Copy link

+1 if that makes sense :)

@dsibiski
Copy link
Contributor

dsibiski commented Sep 3, 2015

I'm working on an example repo that will cover more use cases than the one discussed in the docs. There are a couple very simple examples now, but I plan to add more advanced ones very soon.

https://github.com/dsibiski/react-native-embedded-app-example

I'll also try to work on the 'Embedded Apps' documentation to reflect some of the possible examples.

@idibidiart
Copy link

@dsibiski

I have been looking at your repo and will create some issues soon. One thing I noticed right away is that it's light on comments :)

Thank you and I also meant to update this thread with a link to it, but you beat me to it.

Marc

@dsibiski
Copy link
Contributor

dsibiski commented Sep 4, 2015

@idibidiart You're right! I'll try to add some comments around the interesting bits. Good call.

@javache
Copy link
Member

javache commented Oct 23, 2015

There's also a loadingView you can set on RCTRootView to display a spinner while the React context is initialising. Feel free to reopen this task if you have any other questions about bridge sharing/preloading.

@javache javache closed this as completed Oct 23, 2015
@eshwartm
Copy link

eshwartm commented Jun 14, 2018

Let me cut to the chase, I'm trying to load an RCTRootView in a UITableViewCell. The data for the react view is coming from an API.

I have created the bridge in the AppDelegate as @nicklockwood mentioned. I am quickly initializing an RCTRootView when the detail view loads, and then supplying it with constraints to the UITableViewCell. When the API call is finished and I have the data, I send the RCTRootView the appProperties as data.

But at this point, the RCTRootView doesn't size to fit properly with the UITableViewCell. I am calling tableView.reloadData after a second or two for the sizing to happen.

What am I doing wrong? How can the performance be improved here?

@facebook facebook locked as resolved and limited conversation to collaborators Jul 22, 2018
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Jul 22, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests

10 participants