React Native

React Native inside Native apps, the Navigation challenge

You want to switch to React Native, but you have an existing native app? Should you trash it? Of course not!
At BAM, we integrated React Native inside two native apps, both in production with a few millions users. There is seldom documentation on the matter, so you'll learn about some challenges we faced and how to solve them.
React Native


This article will deal with the first challenge we had to face: the navigation. But Before diving into the technical details, you might be wondering why you would switch to React Native.

Why React Native

Here are the main advantages:

One single codebase

One of the key selling points is that you develop one single code for both iOS and Android.

Better build times

When editing your code, reloading the app to see your changes takes only a few seconds. No need to wait for the Gradle build to finish anymore!

Plus, you can benefit from features similar to those available to web developers, like hot reloading:

Hot reloading

React Native is essentially native

React Native consists of Javascript code controlling Native UI.

React Native in a nutshell

An ++code><Image />++/code> React component will display a native ++code>UIImage++/code> on iOS or a native ++code>ImageView++/code> on Android.

So React Native is essentially Native! If you open your React Native app in the XCode hierarchy inspector or the Android Studio one, you'll indeed see the Native UI components:

Xcode RN inspector

That's why React Native is an adequate solution to keep performance and UX consistent with the native app.

The navigation challenge

Why we use native-navigation

The first challenge that we need to tackle is how to handle navigation.

When using React Native, the officially recommended solution for navigating between screens is react-navigation. But it's a full javascript solution so that's not a good fit here.

Let me explain, we need to keep a consistent navigation stack, right?

For instance, you might want to have a hybrid navigation stack like so:

  • your app opens with a native screen
  • you push a native screen
  • then you push a React screen
  • then push another React screen from it
  • then let's say from this React screen, you want to push back to a native screen.
Hybrid Navigation Stack


We therefore need a solution aware of the whole navigation stack.

Plus, we also want to keep a consistent look and feel throughout all the app. The user should not know whether he is seeing React Native components or pure native components.

Lucky us! Airbnb is developing a great Open source solution with that exact goal in mind: integrating React Native screens inside their apps. This solution is Native Navigation.

Since they're not actively maintaining it, we forked it to @bam.tech/native-navigation and added a few features (such as XCode 9 support and iPhone X support).

To install it, checkout the installation guide.

Push some React Native screens!

Once installing ++code>native-navigation++/code> is done, it is fairly simple to manage the navigation stack:

  • Register your screens in Javascript:

++pre>import Navigator from "native-navigation";
import ArticleDetailScreen from "./screens/ArticleDetail";

Navigator.registerScreen("ArticleDetail", () => ArticleDetailScreen);++/pre>

  • You can now push your RN views from the native side: in Swift for iOS

++pre>import NativeNavigation

...

let screen = ReactViewController(
 moduleName: "ArticleDetail",
 props: ["articleId": articleRef.identifier as AnyObject],
)
navigationController?.pushReactViewController(screen,
                 animated: true)++/pre>

in Java for Android

++pre>final Bundle props = new Bundle();
props.putString("articleId", article.getId());
screenCoordinator.pushScreen("ArticleDetail", props);++/pre>

  • Or push React Native views from the JavaScript side

++pre>Navigator.push("ArticleDetail", { articleId: article.id });++/pre>

For instance on iOS:

++pre>import NativeNavigation

@objc(NativeRedirector)
class NativeRedirector: NSObject {
 @objc(openNativePage:)
 func openTeamPage(options: NSDictionary) -> Void {
   let viewController = NativeViewController.instantiate()

   if let navController =
ReactNavigationCoordinator.sharedInstance.topNavigationController() {
     // Native React Native bridges are executed in a separate thread
     // You need to dispatch in the main thread to push a view controller
     DispatchQueue.main.async(execute: {() -> Void in
       navController.pushViewController(viewController, animated: true)
     })
   }
 }
}++/pre>

Customizing the native navigation bar in Javascript

To ensure a consistent navigation between purely native and React Native, we need to make sure the navigation bar and its transitions in React Native match what the native side does.

Fortunately, the navigation bar with ++code>native-navigation++/code> is actually native! It should be pretty familiar to native developers:

  • On iOS, it's the usual ++code>UINavigationBar++/code> attached to any ++code>UIViewController++/code>
  • On Android, it's a ++code>ReactToolbar++/code> class, extending the Android ++code>Toolbar
    ++/code>


The other good news is you can customize it for your screens, from the Javascript side with ++code>Navigator.Config++/code>. It's the React Native way, it's a Javascript component controlling Native UI.

You can checkout here the list of options available.

++pre>import Navigator from "native-navigation";
import ArticleDetailScreen from "./screens/ArticleDetail";

Navigator.registerScreen("ArticleDetail", () => (
 <Navigator.Config
   translucent
   statusBarTranslucent
   elevation={5}
   rightButtons={[
     {
       title: "Right button"
     }
   ]}
   onRightPress={() => alert("button clicked")}
 >
   <ArticleDetailScreen />
 </Navigator.Config>
));++/pre>

Remember our navigation stack from earlier, we can do it now! ?


Here's an example of the Kickstarter iOS app hijacked by React Native :


With its React Native code:

++pre>import React from "react";
import { Button, NativeModules, Text, View } from "react-native";
import Navigator from "@bam.tech/native-navigation";

Navigator.registerScreen("RNScreen2", () => () => (
 <Navigator.Config title="RN Again" backButtonTitle="">
   <View
     style={{
       flex: 1,
       justifyContent: "space-around",
       alignItems: "center",
       paddingVertical: 100
     }}
   >
     <Button
       title="Open native screen"
       style={{ fontSize: 30 }}
       onPress={() => NativeModules.NativeRedirector.openTeamPage({})}
     />
   </View>
 </Navigator.Config>
));

Navigator.registerScreen("RNScreen", () => () => (
 <Navigator.Config title="React Native!" backButtonTitle="">
   <View
     style={{
       flex: 1,
       justifyContent: "space-around",
       alignItems: "center",
       paddingVertical: 100
     }}
   >
     <Text style={{ fontWeight: "bold", fontSize: 20 }}>BRO</Text>
     <Text style={{ fontSize: 20 }}>Do you even React?</Text>
     <Button
       title="Open RN Screen"
       onPress={() => Navigator.push("RNScreen2")}
     />
   </View>
 </Navigator.Config>
));++/pre>

Questions

Do you have any other issue in mind about integrating React into existing native apps? Feel free to tell us what you want to hear about next in the comments! :)

Développeur mobile ?

Rejoins nos équipes