This post was written by the team at Rangle.io. You can leverage their expertise with custom React and JavaScript training for you or your team. Learn more about creating React Native applications with some further reading on Rangle’s blog, which covers topics like common tooling and workflow, writing your own libraries, and more.
React Native helps you build beautiful user interfaces and experiences for iOS and Android apps, using the fundamental UI building blocks of mobile apps, but written in JavaScript.
But most great mobile apps require realtime connectivity in one way or another – chat messages, notifications, configuration updates, the list goes on. PubNub provides the messaging, signaling, and configuration, and React takes care of the UI. Building realtime native mobile apps just got easier.
This post aims to introduce you to React Native and get you started building a simple chat application that runs on Android and iOS. We will walk you through every step of the process and the final result will be a working chat app that allows you to authenticate through GitHub and use the PubNub APIs to communicate with your friends; all on a real mobile device!
So, without further ado, let’s get started!
Configuring your Development Environment
The first thing you need to do is configure your machine to run React Native applications on both Android and iOS. By far the easiest way to accomplish this is through the use of device emulators. There are very nice device emulators available for both platforms and they integrate very well with React Native. In fact, compared to running on a real physical device, you may find it easier to use the emulators during your development process. Later on it will make sense to build and deploy your application to a real hardware device, but for now the emulators are more than good enough to do what we need.
Both the Android SDK and the iOS “SDK” (Xcode >= 7.0) come with device emulators that are easy to manage. The React Native documentation has a section on getting started and I recommend you start there. The important thing here is to install all of the dependencies that we are going to need to do React Native development. Go through the installation process for both Android and iOS (by clicking the little iOS and Android buttons at the top of the page, along with whatever host platform you will be developing from, e.g. Mac).
Creating a New Project
Now that we have the development tools installed and ready, let’s create a project skeleton. Our goal here is to create an empty React Native project that is capable of running on both the Android and iOS simulators. If we do this correctly, we should be able to open up the simulator on either platform and see the text “Welcome to React Native!” on a white screen.
The good news is that you don’t need to create these skeletons manually or by copying and pasting configuration files. React Native has a tool (react-native) that is capable of generating an empty project skeleton.
Open up a Terminal (Mac) or Git Bash (Windows) instance and enter this command:
You should see a bunch of output about how it is creating your project and installing its dependencies, as well as some details on how to run your new application. (This process also runs npm install for you.)
Running the Application Inside an Emulator
From here on, there are two commands you must use to run your application–one for each platform:
react-native run-android [for Android]
react-native run-ios [for iPhone and iPad]
One important thing to note here is that the react-native iOS runner will automatically open the iOS device emulator for you, but the Android runner will not. Therefore if you wish to run your application inside of an Android emulator, you need to start the Android device emulator manually before doing so. This is a more involved process than the one required for iPhone and iPad; details on how to do so can be found in Appendix A below.
So with that out of the way, let’s start our new application:
react-native run-ios
This will start the Xcode Simulator, deploy our new application to it, and then start our application. Later, when you configure your real hardware device, it will deploy and run there instead. But an emulator is a great way to run and debug a React Native application in a way that behaves exactly like a real device will. (These virtual devices run the same platform code as a real device would, just inside of an emulator.)
Now that you have a running application, we can debug that application. The React Native debugger is going to be the most important tool in your developer tool belt. It will allow you to do typical things like set breakpoints, pause execution, inspect state, and everything else you would typically do when debugging a web application.
Whether your application is running on an iOS emulator or an Android emulator, the debugger operates the same way: through a web browser, and specifically through Chrome. This allows you to use all the familiar Chrome Developer Tools functionality when debugging your application. So assuming that your application was started successfully, you should see a phone-sized window that contains this text:
Welcome to React Native!
To get started, edit index.ios.js
Press Cmd+R to reload,
Cmd+D or shake for dev menu
Now, do as it suggests and hit Cmd+D (note that this key sequence may be different if you are running from Windows or Linux). You will then see the menu on the right-hand side (“React Native: Development”) appear as an overlay.
If you then click on the Debug JS Remotely item in that menu overlay, a browser window like this will pop open:
This is the tool you use to debug your running application. You debug it the same way you would debug any web application: open source files, set breakpoints, inspect application state, add watch variables, etc. The debugger will behave exactly as it would when debugging a real web application, with the exception of the Elements tab.
Since our application has no HTML and no DOM, the Elements tab is of no use to us. What is useful to us is the Sources tab. From there, we can open up any of the source code for our React Native application and interact with it in the same way you would debug a normal web application.
Taking a Look at Your New Application
Now that we have seen how you can create, run and debug your new React Native application, let’s take a look at the source code that was generated by running react-native init. Open up the directory that contains your application and take a look at its contents.
A couple things should jump out at you:
There is a node_modules directory. This is because React Native applications typically use npm for dependency management. An empty project like the one we just created will have two important top-level dependencies, react and react-native. This is enough to get started. As you build out your application, you will typically add more dependencies to this list using npm install --save-dev packageName.
There is an android directory and an ios directory. These directories contain the code necessary to bootstrap our application on both Android and iOS. At the bottom, a React Native application is literally a native application and uses native code to start itself. This code lives in the Android and iOS directories. The Android bootstrapping code is written in Java (the native tongue of the Android world), and the iOS bootstrapper is written in Objective-C (the native tongue of the Apple world).
If you want to learn more about how this code works, the relevant entrypoint files are:
Android
android/app/src/main/java/com/Chat/MainApplication.java
iOS
ios/Chat/main.m
For the most part, you won’t have to do much editing of the native Java and Objective-C code. You are going to be writing your code in JavaScript or TypeScript. The Java and Objective-C code is used only to configure and bootstrap the application.
There are two index.js files: index.ios.js and index.android.js. These serve as the JavaScript application entry points. By default, the only differences in these files is the text that is displayed to the user.
One note about how these files are loaded. When you import or require() a module or file in your application, React Native will search for matching files called myModule.android.js and myModule.ios.js, depending on which platform the application is currently running on, and if neither is found, it will fall back to myModule.js. This offers a simple way to fork your code for Android and iOS. Ideally, you will be writing generic code that is capable of executing on either platform, but in the event that you need to have different code for Android and iOS, this provides an easy mechanism to do so. (The only exception to this pattern is index.android.js and index.ios.js, which are referred to specifically from the Java and Objective-C code in android/ and ios/, respectively.)
Let’s Get Started!
At this point, we have a working application that runs on both Android and iOS. You are able to run the application in an emulator. But the application isn’t of much use; it just displays a text message to the user and does not have any other functionality. The purpose of this blog post is to walk you through the development of a realtime chat application built using the PubNub API. So let’s get started.
Git Repositories
The first thing I wish to note is the reference implementation (master branch). This is a completed version of the chat application. If you are having trouble following along with this series of blog posts, or if you wish to inspect the working, finished application code, you can always go to that GitHub repository and browse through it yourself (or run it inside of an emulator).
Our first branch, rev0
Each of the revN (0-4) branches in our repository is a complete working application in and of itself, and each branch adds progressively more functionality to the application. This blog post walks through the first branch, rev0. The application in this branch should just display Hello World inside of the simulator window. More usefully, we add the configuration and dependencies that will be required to implement features in subsequent blog posts. This way you won’t have to be doing npm install commands at each step of the process, you can just focus on the application code itself.
We have already covered how you can create a new application from scratch. Now that you understand how to do it, we can skip to the code that we have already created for this project. To clone it, use these commands:
Dependencies
The project uses a couple of important third-party libraries. Here, we go through them one by one and explain their purpose.
Draper
In a standard web application, you typically style your HTML elements using CSS. But since React Native applications are built around native controls instead of HTML, CSS is not available as a styling mechanism. React Native does include a StyleSheet concept which is roughly analogous to a CSS stylesheet, but differs in some important ways:
A React Native StyleSheet is defined right inside your JavaScript code, not in a separate stylesheet file.
There is no concept of selectors. In order to apply styles to a control, you must explicitly do so by passing a style prop to the element you wish to style. Therefore you cannot, for example, style all Slider elements a particular way (like you would using a CSS selector). You must create a StyleSheet instance and assign it to the style prop in each place that you wish to use use a Slider component.
In the same way that there are no selectors in React Native stylesheets, there are no class names, either. I can’t create a component and assign it a className prop and then style all components with that class name. I must explicitly style each component by explicitly passing in the styles in each usage of that component.
Units of measurement are different in React Native stylesheets than they are in CSS. In CSS, we have a diverse array of sizing units available: em, rem, vw, vh, px, %, and so forth. None of these units are available in React Native. In React Native, the only unit that is available to us is called a density-independent pixel. But this lack of units ends up being less important than you might believe, since React Native styles are all based around the concept of the flex-box. Flex box styling allows you great flexibility (ha!) in styling your components, regardless of the size of the display that they are eventually rendered on (tablet, phone, phablet, etc.). More details on how to do component layouts are available here and here.
One way in which React Native stylesheets do mimic CSS stylesheets is in the property names that you use to style components. For example, to add a left margin in CSS, I might use margin-left: 5. In React Native, I would use the property {marginLeft: 5}. (If you are familiar with jQuery, you are probably familiar with these property names: they are almost identical to the property names you would use with the jQuery.css() function.)
With all that in mind, you can see how styling your React Native components can become cumbersome. You are creating large StyleSheet instances that duplicate a lot of common properties and clutter your code up with styling information that has little to do with the functionality you wish to implement. For this reason, we use Draper in our project. Draper allows you to use a much more concise syntax when describing the styling of your components. Instead <MyComponent style={{margin: 1, padding: 1, opacity: 0.8}} /> of doing , I can do <MyComponent style={[s.m1, s.p1, s.o80]} /> instead. This is really the central advantage of Draper. It is loosely modeled on the popular basscss CSS library.
In summary, Draper allows you to avoid creating stylesheets (for the most part). Instead you just compose together the styles that make sense for your particular component. It offers a slightly cleaner way of styling your React Native components. Some people may prefer to create custom stylesheets for each component, but Draper is a nice clean alternative to doing so. (There are other directions you could take, too. If you really miss CSS and wish to use it in your React Native application, you may want to look at the react-native-extended-stylesheet package, which allows you to write stylesheets that are much closer to your old CSS stylesheets.)
Some good example Draper usages are provided in its readme file.
React
If you are not familiar with the React library and the way it is typically used in web applications, start with this blog post. It is important to understand the core React concepts before moving on to React Native. React Native shares a lot of concepts and code with core React. The primary difference is the execution context that the app runs in, and the method in which it renders itself. In a web application, the JavaScript of course runs inside of a web browser. In a React Native application, the JavaScript runs inside of a V8 context that itself lives inside of a real Java / Objective-C application.
Another point of contrast is how components are defined. React components are defined using HTML tags and are eventually rendered to the DOM. (This is what you see in the Elements tab in Chrome Developer Tools.) In React Native, no HTML tags are used; instead, we use React Native components that essentially provide a JavaScript interface to real native platform controls (for example, TouchableHighlight (a button), ScrollView, ListView, and so forth).
For the most part, React Native controls are “lowest common denominator” implementations that behave the same way on Android and iOS. To get around this issue of only being able to provide functionality that exists in both Android and iOS, many controls have platform-specific properties and methods that can be used to configure their behavior on Android and iOS. In cases where this it is not possible to have a common control that works on both platforms (because the underlying implements are too different), React Native provides platform-specific controls that can be used only on Android or iOS (depending on the control).
Redux
Our application is going to use Redux to manage its application state. We won’t go into too much detail explaining Redux since it has already been covered in prior blog posts. Suffice it to say that Redux is a great tool to simplify and and delegate management of your application state. We also use redux-thunk (for managing asynchronous operations that can produce multiple actions) and redux-logger (for logging state changes to the console and converting ImmutableJS structures to plain JavaScript objects, which are much easier to read in the console output).
React Native Material Kit
We use the React Native Material Kit for providing some higher-level UI elements than would otherwise be available. This project has a slightly more complex installation process than any of the other libraries we depend upon in this project, so if you are going to add it to your own project, make sure to follow the instructions carefully.
This material kit provides a React Native implementation of material design. The reason we use this library is because it provides very nice UI components that are easy to use, well-designed, and provide a nice, slick user experience. It is the React Native equivalent of React Material UI.
React Native Vector Icons
We will use some material icons provided by react-native-vector-icons.
SpinKit
We are going to need to display some loading indicators in our application. Remember: since we can’t use CSS to style a React Native application, nice clean CSS animations are not available to us. In a regular web project, we would just copy some CSS and HTML from SpinKit and use that for our loading animations. But in a React Native application, we must use React Native animations to provide this functionality. We need to use a library that was specifically built for React Native applications. For this purpose, we’ve selected react-native-spinkit.
ImmutableJS
We are going to use immutable data structures for storing our Redux application state. ImmutableJS is a tremendously useful library that integrates very well with Redux.
PubNub
PubNub has provided a fantastic JavaScript API that we can use in our application, both on the NodeJS/server-side (for managing permissions) and on the client side (for managing the realtime communication).
Libraries required for the ‘backend’ server
As part of our project, we have a NodeJS application which is responsible for authenticating users; creating PubNub communication channels; and granting permissions to those channels for our users.
This code has a couple of dependencies which are unrelated to React Native and are actually not used in the React Native application. Because they are not really a “core” part of the mobile application, I won’t go into great detail on each of these libraries, but if you are curious, go ahead and follow these links:
isomorphic-fetch
express
node-persist
passport
passport-github2
Babel (also for the NodeJS application)
React Native already natively supports Babel and itself lists Babel as a dependency. We therefore don’t have to do anything special in order to use cool new ES6/ES2015 constructs in our React Native code — that’s taken care of for us. But as part of this project, we are going to have to write a bit of server-side NodeJS code which will be responsible for authentication, retrieval of friends lists, and a few other bits. And we want to be able to use ES6 constructs in our server-side code.
Babel has an excellent writeup that goes into great depth on the features that are available using these new JavaScript language standards.
Into the Code
The first piece of code that we want to take a look at is index.js, our application entry point.
Let’s inspect this piece of code. First we are importing Provider from react-redux and wrapping our View in it. This allows all of our descendent components to connect to our Redux store later. But since our application contains only a View and Text component at this early stage, it’s not being used for anything — we just want to get it configured so that we can move on to actual functionality in subsequent blog posts.
You may also notice this line, which appears only in the application entry point:
This code essentially allows us to export our Chat component to the AppRegistry, which provides the ultimate entry point for all React Native applications. React Native boots AppRegistry, and AppRegistry in turn creates our Chat component.
Now if we bounce over to the store/configure-store.js file, we see this bit of code:
The essential purpose of configureStore is to be a factory for our Redux store. When we call this function with an initialState object, we expect to get back a Store object that is placed in the correct initial state, based on our initialState argument.
Then there is a bit of code dealing with module.hot. What is this? Basically, it is a little bit of code that allows us to support hot reloading. Hot reloading is a hack that allows us to update our application code without reloading the application. It’s not a hundred percent reliable; there are many cases where hot reloading doesn’t do what you expect it to do, which forces you to reload your application. But in many cases it speeds up your edit-run cycle by automatically updating the code without an application reload.
Basically, you can attach a module.hot handler anywhere in your code that you wish to be able to accept new code during execution. In this case we want to replace our application reducer when a new one becomes available. Using the replaceReducer function allows us to do this without losing our current application state (again saving you some time having to navigate back to the spot in your application where you were before you changed your code).
Alright, the next thing we are going to take a look at is connectionReducer in reducers/connection.js. Since there is no functionality in our application yet at this stage, we just have an empty reducer which always returns the state value it is provided.
Eventually, this reducer is going to be responsible for managing our connection to the PubNub service:
Connecting, disconnecting and reconnecting on network failures;
Sending and receiving messages;
Sending and receiving presence information.
Next to the connection.js file, we also have an index.js file, which combines all of our reducers (just one for now) into a complete application reducer:
Each reducer corresponds to a key in your application state. More details on how this works are available in the Redux documentation, which is ample.
Stylesheets
React Native applications do not use CSS. As I mentioned earlier, we have installed a third-party library called Draper that we are going to use to provide most of our application styling. But we will still have to define a few style elements such as background and foreground colors, icon sizes, and a few other little elements.
We’ve created a folder called styles/ with a file called index.js. Calling the file index.js allows us to just import directly from the styles/ directory in our code.
First, we’ve added some import statements for Draper and for React Native StyleSheet:
The style declarations look somewhat like CSS, and often times you can use the same property names as you would in CSS, but that is not always the case.
We combine our stylesheet with Draper so that we can reference all of the Draper classes as well as our own classes in one uniform way:
Note that we are using some ES6 syntax here (the “spread operator”) to easily combine together these objects into one export.
Conclusion
At this stage, we have a working, if useless, application shell. If you run the application using the instructions from this section, you should see a white window with some Hello World text inside of it. That concludes this blog post. In the next post, we will cover the implementation of all the actual chat functionality in rev1!
Appendix A
To run the Android device emulator, first we must verify that you have at least one virtual device configured. The simplest way to accomplish this is through the Android AVD Manager UI. To run it, open a terminal and type this command:
This should open a new window that looks like this:
Except on your screen, your device list is likely to be empty. Remedy this by clicking the button marked Create, then you’ll see this dialog pop open:
The particular option you select for Device is not all that important. More important are Target and CPU/ABI. Make sure to select a target that includes the Google APIs. Also make sure to select a device with a screen dimension that is appropriate. Later on, it will make sense to create a variety of virtual devices, one for each type of device that you wish to target. But for now we can make do with one virtual device.
Once you are done filling out the form, press the OK button. This should close the dialog and drop you back to the main window (the list of virtual devices). Now select your new virtual device (the one you just created), and press the Start button. You should see a progress dialog pop open, followed by a screen that looks like your phone or tablet device:
Now you are good to go!
The post Getting Started with React Native for iOS and Android appeared first on PubNub.