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

[expo-dev-client][expo-modules-core] Android automatic setup of dev client #16441

Merged
merged 22 commits into from
Mar 1, 2022

Conversation

esamelson
Copy link
Contributor

Why

ENG-2611 and ENG-4209 for Android / Android version of #16190

Integrating expo-dev-launcher with the expo-modules automatic setup system is long overdue and will fix a whole host of issues with the current system, which relies heavily on unsafe regexes in the config plugin.

How

Use DevLauncherPackage and DevMenuPackage to hook into the expo modules wrapper system and replace all of the existing integrations with MainApplication/MainActivity.

In order to do so, added a few things to the ReactActivityDelegateWrapper and ReactActivityHandler (discussed via Slack and in #16304):

  • ReactActivityHandler.createReactRootViewContainer: allows us to wrap the root view and capture touch events for the dev menu
  • ReactActivityHandler.onKeyUp: allows us to intercept key commands for the dev menu
  • ReactActivityHandler.onDidCreateReactActivityDelegate: via reflection, allows us to replace the wrapped ReactActivityDelegate for the initial launch with dev-launcher

Everything else was achievable with methods already in place in the expo-modules system πŸŽ‰

This new integration is only additive and is fully backwards-compatible. All of the integrations will no-op if the project is already using the old config-plugin-based system. Also, if the project is using SDK 44 or older, the dev client will still compile, as the new handler code is siloed based on the expo package version.

Test Plan

Tested the following scenarios manually:

In a project without the current manual integration (expo init bare, install local dev-client, resolve local expo/modules-core, add expoPackageVersion = "45.0.0" to android/build.gradle):
βœ… dev launcher runs in a debug build
βœ… can open locally served project from expo-cli
βœ… shake and three finger touch both open and close the dev menu
βœ… can go back to launcher, then open project again
βœ… can use a deep link (via QR code) to open project while app is running
βœ… can use a deep link (via QR code) to open app, it loads the project correctly
βœ… release build does not have the dev client

In a project with the current manual integration (expo init blank, install local dev-client, resolve local expo/modules-core, prebuild), this new one shouldn't interfere:
βœ… shouldEnableAutoSetup evaluates to false in both dev-launcher and dev-menu
βœ… all of the above test cases still pass

Checklist

  • Documentation is up to date to reflect these changes (eg: https://docs.expo.dev and README.md).
  • This diff will work correctly for expo build (eg: updated @expo/xdl).
  • This diff will work correctly for expo prebuild & EAS Build (eg: updated a module plugin).

@linear
Copy link

linear bot commented Feb 25, 2022

Copy link
Contributor

@lukmccall lukmccall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM πŸš€ Solid workπŸ…

Copy link
Contributor

@Kudo Kudo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks Eric for this awesome work! i've just left some comments here. please take a look if these make sense to you.

* @return a ReactRootView instance to be used as a container, or null if no container is needed
*/
@Nullable
default ReactRootView createReactRootViewContainer(Activity activity) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the container necessary to be a ReactRootView? it would be better if ViewGroup is enough. i am afraid if there are any problems for nested ReactRootView.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh i got the reason, because createReactRootView requires a ReactRootView but not a ViewGroup:

how about we insert the ReactRootView container in loadApp?
https://github.com/facebook/react-native/blob/189c2c8958442541c6b4f42860b2943ece612da2/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java#L92

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool idea. this works πŸŽ‰

.mapNotNull { it.onDidCreateReactActivityDelegate(activity, this) }
.firstOrNull()
if (newDelegate != null && newDelegate != this) {
val mDelegateField = ReactActivity::class.java.getDeclaredField("mDelegate")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

proguard rules should be updated for accessing ReactActivity.mDelegate

return delegate.onKeyUp(keyCode, event)
return reactActivityHandlers
.map { it.onKeyUp(keyCode, event) }
.fold(false) { accu, current -> accu || current } || delegate.onKeyUp(keyCode, event)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's tricky here and i always being confused if i did right. taking onNewIntent as an example:

  override fun onNewIntent(intent: Intent?): Boolean {
    val listenerResult = reactActivityLifecycleListeners
      .map { it.onNewIntent(intent) }
      .fold(false) { accu, current -> accu || current }
    val delegateResult = delegate.onNewIntent(intent)
    return listenerResult || delegateResult
  }

where i intentionally separate as two difference variables. even if a listener returns true, i think we should still call delegate.onNewIntent.

in your case, if a listener returns true from onKeyUp, ReactDelegate.onKeyUp will not be called. i'm afraid there will be something broken from react-native's key handler.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(ignore my previous comment here, I messed up when I was testing πŸ˜… )

Consuming the key event and not letting it through to ReactDelegate.onKeyUp is actually purposeful here, as this is the way that we currently override RN's built in dev menu. Otherwise the shake gesture brings up both RN's and our dev menu for a split second. DevMenuAwareReactActivity is already written like this so I don't think this should cause issues from the dev menu specifically. But I understand your concern for the more general case πŸ˜• I will at least document this in a comment, do you think that's sufficient?

@esamelson esamelson requested a review from Kudo February 28, 2022 23:23
Copy link
Contributor

@Kudo Kudo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great work! it looks promising πŸ”₯. thanks for kindly verifying my comments.

@expo-bot expo-bot added bot: passed checks ExpoBot has nothing to complain about and removed bot: suggestions ExpoBot has some suggestions labels Mar 1, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bot: passed checks ExpoBot has nothing to complain about
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants