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);
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 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.
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.
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.
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!
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:
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'
}
Build you react code into the fragment
Fragment reactFragment = new ReactFragment.Builder()
.setComponentName("HelloWorld")
.setLaunchOptions(launchOptions) // A Bundle of launch options
.build();
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();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With