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

Comments

Projects
None yet
10 participants
@marcshilling

marcshilling commented May 14, 2015

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

This comment has been minimized.

Show comment
Hide comment
@ide

ide May 14, 2015

Collaborator

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.

Collaborator

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

This comment has been minimized.

Show comment
Hide comment
@marcshilling

marcshilling May 14, 2015

@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.

marcshilling commented May 14, 2015

@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

This comment has been minimized.

Show comment
Hide comment
@ide

ide May 14, 2015

Collaborator

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.
Collaborator

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

This comment has been minimized.

Show comment
Hide comment
@nicklockwood

nicklockwood May 14, 2015

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.

Contributor

nicklockwood commented May 14, 2015

@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

This comment has been minimized.

Show comment
Hide comment
@ide

ide May 14, 2015

Collaborator

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

Collaborator

ide commented May 14, 2015

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

@nicklockwood

This comment has been minimized.

Show comment
Hide comment
@nicklockwood

nicklockwood May 14, 2015

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.

Contributor

nicklockwood commented May 14, 2015

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

This comment has been minimized.

Show comment
Hide comment
@dsibiski

dsibiski May 17, 2015

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.

Contributor

dsibiski commented May 17, 2015

👍 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 from Small delay displaying RCTRootView after a push to [Perf] Small delay displaying RCTRootView after a push May 30, 2015

@brentvatne

This comment has been minimized.

Show comment
Hide comment
@brentvatne

brentvatne May 30, 2015

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?

Collaborator

brentvatne commented May 30, 2015

@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

This comment has been minimized.

Show comment
Hide comment
@nicklockwood

nicklockwood May 30, 2015

Contributor

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

Contributor

nicklockwood commented May 30, 2015

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

@cxfeng1

This comment has been minimized.

Show comment
Hide comment
@cxfeng1

cxfeng1 Jun 2, 2015

@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?

cxfeng1 commented Jun 2, 2015

@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

This comment has been minimized.

Show comment
Hide comment
@nicklockwood

nicklockwood Jun 2, 2015

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.

Contributor

nicklockwood commented Jun 2, 2015

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 nicklockwood reopened this Jun 2, 2015

@nicklockwood

This comment has been minimized.

Show comment
Hide comment
@nicklockwood

nicklockwood Jun 2, 2015

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.

Contributor

nicklockwood commented Jun 2, 2015

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

This comment has been minimized.

Show comment
Hide comment
@cxfeng1

cxfeng1 Jun 3, 2015

@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.

cxfeng1 commented Jun 3, 2015

@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

This comment has been minimized.

Show comment
Hide comment
@idibidiart

idibidiart Sep 2, 2015

+1 if that makes sense :)

idibidiart commented Sep 2, 2015

+1 if that makes sense :)

@dsibiski

This comment has been minimized.

Show comment
Hide comment
@dsibiski

dsibiski Sep 3, 2015

Contributor

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.

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

This comment has been minimized.

Show comment
Hide comment
@idibidiart

idibidiart Sep 3, 2015

@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

idibidiart commented Sep 3, 2015

@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

This comment has been minimized.

Show comment
Hide comment
@dsibiski

dsibiski Sep 4, 2015

Contributor

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

Contributor

dsibiski commented Sep 4, 2015

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

@javache

This comment has been minimized.

Show comment
Hide comment
@javache

javache Oct 23, 2015

Member

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.

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 Oct 23, 2015

@eshwartm

This comment has been minimized.

Show comment
Hide comment
@eshwartm

eshwartm 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?

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

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.