Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sharing a single ReactInstanceManager between multiple instances of React native activities

I integrated React Native into a native Android app and I create new instances of React Native activities from the native code.

Here is the code for the class that wraps ReactInstanceManager:

public class ReactNativeInstanceWrapper
{

private static ReactNativeInstanceWrapper instance = new ReactNativeInstanceWrapper();

public static ReactNativeInstanceWrapper getInstance() {
        return instance;
    }

    private ReactInstanceManager reactInstanceManager;

    public ReactInstanceManager GetReactInstanceManager()
    {
        return reactInstanceManager;
    }

    public ReactInstanceManager Rebuild(Application application)
    {
        Boolean isDebugBuild = AppBuildType.IsBuildConfigDebug(application.getBaseContext());

        reactInstanceManager = null;
        synchronized (this) {
            reactInstanceManager = ReactInstanceManager.builder()
                    .setApplication(application)
                    .setBundleAssetName("index.android.bundle")
                    .setJSMainModuleName("index.android")
                    .addPackage(new MainReactPackage())
                    .addPackage(new ReactIntegrationPackage())
                    .addPackage(new PickerPackage())
                    .addPackage(new LinearGradientPackage())
                    .setUseDeveloperSupport(isDebugBuild)
                    .setInitialLifecycleState(LifecycleState.BEFORE_CREATE)
                    .build();
        }

        if (!reactInstanceManager.hasStartedCreatingInitialContext())
        {
            reactInstanceManager.createReactContextInBackground();
        }

        return reactInstanceManager;
    }

I want parse a js bundle while the app is loading and cache it, which makes loading of React Native much faster. The problem is that it looks like sharing a ReactInstanceManager between multiple activities cause some problems.

For example, in one case, if I open a Share sheet in one of my activites and than come back to my RN activity and close it, I can't open a dialog in a new RN activity. It throws a WindowManager$BadTokenException, which probably means that it tries to attach it to an activity that doesn't exist.

In a RN activity, here is how I create ReactRootView in OnCreate:

  this.ReactRootView = new ReactRootView(this);
    setContentView(this.ReactRootView);

    ReactNativeInstanceWrapper reactNativeInstanceWrapper = ReactNativeInstanceWrapper.getInstance();
    this.ReactInstanceManager = reactNativeInstanceWrapper.GetReactInstanceManager();
    if (this.ReactInstanceManager == null) {
      this.ReactInstanceManager = ReactNativeInstanceWrapper.getInstance().Rebuild(getApplication());
    }
    this.ReactRootView.startReactApplication(this.ReactInstanceManager, reactNativeComponent, initialProps);

ReactInstanceManager is supposed to set a new activity in OnResume:

  @Override
  protected void onResume()
  {
    super.onResume();

    if (this.ReactInstanceManager != null) {
      this.ReactInstanceManager.onHostResume(this, this);
    }
  }

But it looks like it still keeps references to an old activity somewhere.

So, what I ended up doing is destroying and rebuilding the instance of ReactInstanceManager every time I leave a RN activity. It's not a perfect option, but it works.

I would like to find a way to create and persist a single instance of ReactInstanceManager instead if recreating it every time in background.

  • react-native -v: 0.42.3
  • node -v: v6.11.0
  • npm -v: 5.2.0
  • platform: Android
like image 890
Yury Avatar asked Jul 27 '17 15:07

Yury


1 Answers

Here is the solution I found.

I used MutableContextWrapper to create initial ReactRootView.

ReactRootView reactRootView = new ReactRootView(new MutableContextWrapper(originActivity));

It allowed me to substitute it with a new context in OnCreate

MutableContextWrapper contextWrapper = (MutableContextWrapper) reactRootView.getContext();
        contextWrapper.setBaseContext(currentActivity);

This way I can preload the whole thing and reuse later. I found this solution by accident in one of the github issues. I wish there was a well documented way of doing that.

In older versions of React Native there also used to be an issue with old reference to a modal dialog, but it is fixed now.

like image 88
Yury Avatar answered Oct 05 '22 12:10

Yury