React-native inside a Fragment
Asked Answered
E

3

17

How to Start react-native inside of a fragment? While putting react-native inside Fragment, onCreateView function is unable to return View from mReactRootView.

View view = inflater.inflate(mReactRootView. , container, false);

Euterpe answered 5/2, 2016 at 10:12 Comment(0)
S
33

I've managed to figure this out with much trial and error. I've seen this question asked around the internet and thought that this was the best place to post the answer. Here is how to do with the latest version of React (0.29 as of this writing):

The first thing we'll do is create an abstract ReactFragment class that we will use throughout our app:

public abstract class ReactFragment extends Fragment {
    private ReactRootView mReactRootView;
    private ReactInstanceManager mReactInstanceManager;

    // This method returns the name of our top-level component to show
    public abstract String getMainComponentName();

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mReactRootView = new ReactRootView(context);
        mReactInstanceManager =
                ((MyApplication) getActivity().getApplication())
                        .getReactNativeHost()
                        .getReactInstanceManager();

    }

    @Override
    public ReactRootView onCreateView(LayoutInflater inflater, ViewGroup group, Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        return mReactRootView;
    }


    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mReactRootView.startReactApplication(
                mReactInstanceManager,
                getMainComponentName(),
                null
        );
    }
}

We'll now be able to create fragments that render React Native components, e.g.:

public class HelloFragment extends ReactFragment {
    @Override
    public String getMainComponentName() { 
        return "hellocomponent"; // name of our React Native component we've registered 
    }
}

A little more work is required, though. Our parent Activity needs to pass some things into the ReactInstanceManager in order for the React Native lifecycle to work properly. Here's what I ended up with:

public class FragmentActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler {
    /*
    * Get the ReactInstanceManager, AKA the bridge between JS and Android
    * We use a singleton here so we can reuse the instance throughout our app
    * instead of constantly re-instantiating and re-downloading the bundle
    */
    private ReactInstanceManager mReactInstanceManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fragment);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });

        /**
         * Get the reference to the ReactInstanceManager
         */
         mReactInstanceManager =
             ((MyApplication) getApplication()).getReactNativeHost().getReactInstanceManager();


        /*
        * We can instantiate a fragment to show for Activity programmatically,
        * or using the layout XML files.
        * This doesn't necessarily have to be a ReactFragment, any Fragment type will do.
        */

        Fragment viewFragment = new HelloFragment();
        getFragmentManager().beginTransaction().add(R.id.container, viewFragment).commit();
    }

    @Override
    public void invokeDefaultOnBackPressed() {
        super.onBackPressed();
    }

    /*
     * Any activity that uses the ReactFragment or ReactActivty
     * Needs to call onHostPause() on the ReactInstanceManager
     */
    @Override
    protected void onPause() {
        super.onPause();

        if (mReactInstanceManager != null) {
            mReactInstanceManager.onHostPause();
        }
    }

    /*
     * Same as onPause - need to call onHostResume
     * on our ReactInstanceManager
     */
    @Override
    protected void onResume() {
        super.onResume();

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

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
            mReactInstanceManager.showDevOptionsDialog();
            return true;
        } 
        return super.onKeyUp(keyCode, event);
    }
}

Finally, you'll notice the reference to (MyApplication) throughout the code; this is a global Application object to contain our ReactInstanceManager, AKA the bridge between Android and React Native. This is the pattern that the React Native projects use internally, so I simply copied it. Here's how it's implemented:

public class MyApplication extends Application implements ReactApplication {
    private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
            return true;
        }

        @Override
        public List<ReactPackage> getPackages() {
            return Arrays.<ReactPackage>asList(
                    new MainReactPackage()
            );
        }
    };

    @Override
    public ReactNativeHost getReactNativeHost() {
        return mReactNativeHost;
    }
}

The trickiest bit was figuring out the lifecycle between the Fragment and the Activity; the ReactRootView needs a reference to the Activity context in order to instantiate, so making sure that getActivity() would not be null was important. Also, registering the onHostPause() and onHostResume() in the parent Activity was unintuitive at first, but ultimately proved simpler once the ReactNativeInstanceManager was abstracted away into a global instead of keeping it on the Activity or Fragment.

Hope this helps someone else out there!

Selfloading answered 6/7, 2016 at 20:47 Comment(7)
i have tried your implementation. it's opening react page in fragment but the touch listener in react is not working. can you please suggest someway to work on this custom view.Lully
@Lully I'm not sure why that would be the case. Does it work when you use a normal ReactActivity or try it in an app created with react-native init?Selfloading
I have tried with react activity and made project with the above mentioned command but it's not working. I am getting "Unable to dispatch touch to JS as the catalyst instance has not been attached" in my log cat.Lully
@Lully try adding this to your activity:Selfloading
@Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) { mReactInstanceManager.showDevOptionsDialog(); return true; } return super.onKeyUp(keyCode, event); }Selfloading
React Native 0.29 now supports ReactFragmentActivity l.e one or more ReactActivity can co exist in ApplicationEuterpe
@Lully : I got the same problem to you. My solution is add onResume(){...mReactInstanceManager.onHostResume(getActivity(), (DefaultHardwareBackBtnHandler) getActivity());}Ingratitude
F
4

There is now an official ReactFragment available here that can be used to host react native inside a fragment.

Just make sure you have your react native host setup correctly, as the fragment tries to access the react native host on the application level, or overload it in a subclass:

// inside the ReactFragment
protected ReactNativeHost getReactNativeHost() {
    return ((ReactApplication) getActivity().getApplication()).getReactNativeHost();
}

you can then create the fragment using:

val reactNativeProcessFragment = ReactFragment.Builder()
    .setComponentName("nameUsedIn_AppRegistry.registerComponent")
    .build()
Foah answered 29/6, 2020 at 8:19 Comment(0)
C
2

There are libraries available that handle this for you.

One that I use is react-native-android-fragment

As per the instructions on the linked GitHub repository:

  1. Add the following line to your build.gradle compile 'com.github.hudl:react-native-android-fragment:v0.43.2'.

e.g.

allprojects {
  repositories {
    ...
    maven { url 'https://jitpack.io' }
  }
}

dependencies {
  // Version will correspond to its dependnecy on React Native
  compile 'com.github.hudl:react-native-android-fragment:v0.43.2'
}
  1. Build you react code into the fragment

    Fragment reactFragment = new ReactFragment.Builder() .setComponentName("HelloWorld") .setLaunchOptions(launchOptions) // A Bundle of launch options .build();

  2. Place the Fragment in a FrameLayout that you would have in your XML layout file. In my case, the FrameLayout ID is react_holder.

    getSupportFragmentManager() .beginTransaction() .add(R.id.react_holder, reactFragment) .commit();

Cautery answered 25/7, 2017 at 16:51 Comment(1)
Let us continue this discussion in chat.Kershner

© 2022 - 2024 — McMap. All rights reserved.