Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React-native inside a Fragment

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);

like image 940
Ramesh Vishnoi Avatar asked Feb 05 '16 10:02

Ramesh Vishnoi


People also ask

Why fragments are better than container divs?

With React Fragment, you can render multiple elements of a component without adding extra div tags. We can write cleaner, more readable code with React Fragments. It takes up less memory and renders components faster. Each component is rendered as expected.

Is React fragment same as <>?

<> is the shorthand tag for React. Fragment which allows us to group a list of elements without wrapping them in a new node. The only difference between them is that the shorthand version does not support the key attribute.

Can I use fragment in react native?

Integrating your App with a React Native Fragment​ You can render your React Native component into a Fragment instead of a full screen React Native Activity. The component may be termed a "screen" or "fragment" and it will function in the same manner as an Android fragment, likely containing child components.

Can you style a React fragment?

Fragments exists as a workaround to return adjacent JSX elements, they don't render anything to the DOM so they can't be stylized. Save this answer.


2 Answers

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!

like image 156
Lokeh Avatar answered Oct 16 '22 19:10

Lokeh


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();

like image 40
AliAvci Avatar answered Oct 16 '22 21:10

AliAvci