How to make a mobile app, using JS React Native way

How to make a mobile app, using JS React Native way

Hey! I have worked with Front-end for 4 years now. I’m fond of JavaScript and modern rap. If it sounds weird to you, please, don’t bother reading further.

I started my career in IT from PHP and layout. Then I encountered server support on Nginx and Apache, operation with SQL database, Yii2 and Symfony. Then I saw jQuery, and it looked like magic to me (at this point, Frontend developers are laughing out loud). Since that moment, I focused on frontend and decided to become a JS developer. Now I work with ReactJS, Redux, React Native (as you could guess); have some experience in monstrous CMS and Java, and the lead of other developers. When working at AB Soft, I dived in the world of Front-end and JS.

If you want to engage yourself with app development, you’d better focus on Swift, Kotlin, Objective-C, Java. In other words, on something more native. Or you can go for the platform you prioritize for development (as it would be cheaper to develop for, let say, Android). But! If you navigate the IT ocean on the JS boat and its hyping frameworks, welcome! Why wouldn’t you give a try to React Native?

Disclaimer. I have to disappoint you: you won’t write Snapchat on it.

ReactNative has not moved to the versions of zero, which is a common practice for anything that has “React” in its title.

At the same time, you have loads of libraries in npm, which will help you solve various problems (but not all of them - I’ll cover that further on). GitHub also holds many libraries that realize the components, functions, UI, routing, and more for your app. What is more, it has quite a lively community. All these aspects can contribute to making development much more effortless.

The easiest path is to use Expo

Expo is a framework (a framework for a framework, go figure) and platform which makes the life of a novice React Native developer easier.

  1. It allows for developing and testing an app without utilizing Xcode or Android SDK and their ecosystems.
  2. It provides a kick-off kit with a ready-made app and a couple of screens. You can tinker on that and fill in a gap in your CV.
  3. Expo has an extended and clearly put documentation (in English).
  4. It offers a convenient testing system for its code and UI apps that do not require creating a set-up file. You can open your app following a link.
  5. This framework also provides a vast number of out-of-the-box tools and API for working with device modules (accelerometer, camera, file control system, notifications etc.). You can find a complete list here.
  6. Expo also offers its servers and environment for assembling the app and its further compilation into a native executable file.
  7. It manages your certificates and signatures in Play Market and Apple Store automatically.

Expo has its limitations:

  1. It does not support many native libraries (written on Objective-C, Swift, Kotlin, etc.).
  2. It is hardwired on a specific React Native version.
  3. Your app will have a relatively large size.
  4. If you decide to disconnect your app from Expo (for instance, to use a couple of native modules), it’s gonna be a sweaty piece of work.

Summary: If you only started to familiarize yourself with RN and want to enjoy a working app on your device, use Expo.

Before you start

You’d better have some pre-installed software:

●     The latest stable version of Node.js (download);

●     Control system of Git versions (download).

Now, we install a command-level interface for Expo.

npm install -g expo-cli

If you are lucky enough to have an Apple-made computer, you’d better additionally install a watchman. If you want to test your app on a physical device, install Expo app on it: iOS or Android. You can also use emulators as a testing platform. Figure out how to install them by yourself. iOS (for iOS emulator, you will need macOS) and Android.

“Let’s start coding already!” an impatient reader would say and will have a point.

Let’s write a small Pinterest-like app. It will have a breakdown by categories, gallery, images, and API-calls.

We run a command expo cli to create a blank project.

expo init

Further, you should enter some parameters for the initial setup of the project.

When selecting a template, pick a blank template. If you are familiar with TS, you can apply a TypeScript template. But we’re here not for typifying stuff!

Then we give the name to the project and write its text ID. If you have Yarn installed, Expo will offer to use it instead of nmp. I will use npm-based examples to minimize a “tech zoo” in this article

Expo will create a folder with the project with the title you indicated in slug and install necessary dependencies. If you have done everything right, running a command npm run start will enable your console to show the information that the app has already been built and offer a Qr-code to open it in the Expo app. A browser will open a relevant page, containing a live reload console and UI to run emulators.

If you want to run an app on the emulator at once, you can use the commands npm run ios and npm run android, respectively.

The full list of commands is in  ./package.json and here.

The simulator is expected to show you something like that:

Screens

Some of the pseudo architecture. Before you start writing an app, you should think through what screens it will comprise. A screen is a component serving as a container for everything it is going to display.

Our app will have 2 screens:

  1. Main with the list of categories of all images.
  2. The other is the gallery of images in a specific category.

Let’s create a Screens folder. It will hold the files with the screen components.

Before we start writing code for UI components, let me tell you about the best bookmaker (just kidding!) ... about the library with cross-platform components for RN.

It is a set of basic styled components that are most commonly used in the app interface. Instead of writing lots of styled code, you can use a ready-made solution. Of course, it won't open all possible user interface components, but it is quite instrumental.

npm install native-base

First comes the main screen CategoriesList.

It will display the list of image categories. In the real app, you will request this list from your API. But for now, we will keep it in  ./data folder along with other utility files.

data/categories.js

const list = [ { name: 'Architecture', alias: 'architecture', collection: 'https://firebasestorage.googleapis.com/v0/b/learn-rn-bb59c.appspot.com/o/architecture.json?alt=media&token=b5583dd0-2dbd-4cb9-a91f-76f8e7a0538a' }, { name: 'Food', alias: 'food', collection: 'https://firebasestorage.googleapis.com/v0/b/learn-rn-bb59c.appspot.com/o/food.json?alt=media&token=8b97fcc5-b802-459a-9080-9bd3d49032a6' }, { name: 'Abstract', alias: 'abstract', collection: 'https://firebasestorage.googleapis.com/v0/b/learn-rn-bb59c.appspot.com/o/abstract.json?alt=media&token=c7012570-af3f-4f9b-acd9-ad1a9827d837' }, { name: 'Pets', alias: 'pets', collection: 'https://firebasestorage.googleapis.com/v0/b/learn-rn-bb59c.appspot.com/o/pets.json?alt=media&token=f866e7c7-ed4b-46e9-b7c7-ef4b2242b245' } ]; export default list;

The property of collection is a link to a JSON file with a set of images from the site. Later, we will turn to it via API and display those beautiful images.

screens/CategoriesList.js

import React from 'react'; import { List, ListItem, Text } from 'native-base'; import { ScrollView } from 'react-native'; import list from '../data/categories'; class Categorieslist extends React.Component { constructor(props) { super(props); this.state.categories = list; } state = { categories: [] } render() { return ( { this.state.categories.map((item) => { return ( {item.name} ) }) } ); } } export default Categorieslist;

As you can see, the main difference from React is, at first glance, in jsx tags. We don’t use HTML tags here (that comprise our React components in most cases). Instead, we apply native components provided by React Native library. They compile into native platform presentations.

A few words about ready-made components we use as a UI basis.

<ScrollView /> — is a container for displaying content that you probably have to scroll.

 <List /> and <ListItem /> — is a list and a list element respectively from Native Base library. They are already styled in line with iOS and Android guidelines.

The name of the tag <Text /> speaks for itself.

List of categories

To enjoy the result of your hard work, let’s remove all excessive details from App.js and import your screen there.

import React from 'react'; import CategoriesList from './screens/CategoriesList'; export default function App() { return ( <CategoriesList /> ); }

If you did not switch off the npm run command, you could already see the result on the emulator’s display. If you did, then restart it: npm run ios or npm run android.

If you’ve installed Expo client on your device and want to open your app just scan QR code from the terminal. The display is supposed to show you this:

Do you have it like that? Good for you! If not, then compare your code with the one in this article.

Designing an images grid layout

Pinterest has a very nice grid layout with images. In common terms, it is known as Masonry. Good folks have already sorted it out as a npm package. But we won’t spend too much time on it. Instead, we’ll soak ourselves in the work and styling of native components and NativeBase components.

Eventually, we will pass arrays with simple images to our gallery component. It will look like an image grid with rounded edges (according to the latest Apple guideline, it’s really fancy now). 

We’ll store the gallery component in the Components folder. It will be responsible for rendering our image array only. We'll later turn it into the HOC that will pass the data in props and functions to it to execute when interacting with it. It is how we split up logic and display data to a certain extent.

components/ImageGrid.js

import React from 'react'; import {Card, CardItem} from 'native-base'; import {View, ScrollView, StyleSheet, Dimensions, Image} from 'react-native'; /** * Getting a screen object via API RN * to use the width for further calculations */ const window = Dimensions.get('window'); const imagesWidth = window.width - 20; export default class ImageGrid extends React.Component { render() { const { list } = this.props; return ( <ScrollView> {list.map((item) => { const imageRatio = imagesWidth/item.width; return( <View key={item.url} style={styles.cardWrapper}> <Card style={styles.card}> <CardItem style={styles.cardItem}> <Image source={{uri: item.url}} style={{ ...styles.image, height: item.height * imageRatio }} /> </CardItem> </Card> </View> ); })} </ScrollView> ); } } const styles = StyleSheet.create({ cardWrapper: { borderRadius: 20, marginBottom: 10 }, card: { borderRadius: 20, marginLeft: 10, marginRight: 10 }, cardItem: { paddingLeft: 0, paddingRight: 0, paddingTop: 0, paddingBottom: 0 }, image: { height: 300, width: '100%', borderRadius: 20, width: imagesWidth } });

Styling

We import StyleSheet from React Native. It is the class that provides access to abstraction of styling (as CSS). Using Create we create the object containing the links to a specific set of styles. The link names correlate with the name of the object props. The styles themselves are assigned to the components, utilizing style props. We can also pass an object to style.

<Text style={{color: #ccc}}>Some text</Text>

As you can see, the names of styling props look very similar to those we use in CSS. If you work with the web, you are familiar with all of that.

The list of supported styles is scattered across the React Native documentation. But the world is no good without good people - they collected all of them in a single cheat noteYou must have paid attention to Card и CardItem components. We take these components from the NativeBase library not to lose any sleep over styling and positioning of image elements. These components can also hold various content sets: texts, titles, buttons, background images, etc. Read this doc to get more details. We pass  button={true} parameter in CardItem that allows the card to act as a button.

Images

Image component is something to be spoken about separately. It is styled in the same manner as other components, has a mandatory parameter source, which indicates the path of the image we want to display. There are two ways to do it.

  1. To pass require (‘image/path.png’) in source for local images. However, if you try to dynamically generate the line inside of require, it won’t work: RN will keep failing at this point.
  2. Indicate an object of the type {uri: ‘https://remote.image/path.png’}. In this case, you only indicate the URL of a remote image your app will upload. What is more, uri can pass the line of base64 format. Yet, if the line is long, at the compiling level, the Expo sever may just give up compiling your app, referring to too big a size of js-file.

You’d better not specify a 100% height of the image; instead, go with a specific number. If the image is located in the remote server, use the native method of getSize ensuring the preload of the image file.

Navigation

To implement navigation between two screens, let’s use the react-navigation package.

npm install react-navigation

Our project, being Expo-based, needs additional dependencies to start navigation.

expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view npm install react-navigation-stack

The navigation in the apps differs from that of the browsers in terms of its arrangement. The app stores the history of the entire routing together with the parameters and even partially app states. The elements themselves are arranged following the stack principle. Moving to the new screen is similar to placing the screen over the stack. The “Back” button, featuring both iOS and Android interfaces, controls the stack element.

We are going to create a separate route for navigation. It will describe which screen allies with a specific route and which route is used by default. It is provided by createStackNavigator method: it takes the collection as parameters, with each object describing an individual route. In our example, we use the following parameters in the route objects:

  1. Screen — the screen component for the route;
  2. navigationOptions — navigator settings where you can indicate the title, the “Back” button name, and other options. You can also use callback as this property. It takes parameters from the navigator, forms dynamically, and returns the values we need (we will use it intensively).

Using the other parameter, it takes the object with the values for navigation settings. For now, we will use it to opt for the by-default screen with the help of initialRouteName prop.

In general, the only mandatory value for a collection element is a screen prop.

./navigation.js

import { createStackNavigator } from 'react-navigation-stack'; import { createAppContainer } from 'react-navigation'; import CategoriesList from './screens/CategoriesList'; import GridScreen from './screens/GridScreen'; const Stack = createStackNavigator({ MainList: { screen: CategoriesList, navigationOptions: { title: 'Photo Categories' } }, ImagesGrid: { screen: GridScreen, navigationOptions: ({ navigation }) => { return { title: navigation.state.params.title }; } } }, { initialRouteName: 'MainList' }); export default createAppContainer(Stack);

Our app will only have two screens: the general list and the screen to display the content of the selected collection

Route MainList will call the component CategoriesList; employing navigationOptions we will specify the screen title. Route ImagesGrid, in its turn, will call the screen component GridScreen.

If you’d like to familiarize yourself with all navigator’s features, read this documentation.

createStackNavigator returns the component. But we can’t simply use it in the app. It asks for createAppContainer which will connect everything we can create, using the React navigation with native API platform. createAppContainer returns the component we are going to use as the root in our project. We need to somehow change App.js for that:

./App.js

import React from 'react'; import Navigator from './navigation'; export default function App() { return ( <Navigator/> ); }

Now, let’s introduce components into navigation.

./screens/CategoriesList.js

import React from 'react'; import { List, ListItem, Text } from 'native-base'; import { ScrollView } from 'react-native'; import categories from '../data/categories'; class CategoriesList extends React.Component { constructor(props) { super(props); this.state.categories = categories; } state = { categories: [] }; render() { const { navigation: { navigate } } = this.props; return ( <ScrollView> <List> { categories.map((item) => { const { collection, alias, name } = item; return ( <ListItem key={alias} onPress={() => navigate('ImagesGrid', { collection, title: name })} > <Text>{name}</Text> </ListItem> ) }) } </List> </ScrollView> ); } } export default CategoriesList;

Then we need to add a way to navigate between our screens to the screen with the categories list.

When in the file navigation.js we passed CategoriesList as an argument, we created HOC (high-order component) around it; in its turn, it passed to the props of this component some data and methods for navigation implementation. We are interested in the method navigation.navigate() in the first place. As you could guess from its name, it navigates between different screens; we specify the screen that will come first in the first argument. The screen name coincides with the object names in the collection we passed to createStackNavigator (navigation.js). Utilizing the second parameter, we can pass the object with additional data that will be passed to createStackNavigator and the following open screen. Now, using the second argument, we will pass the collection ID and screen title.

Let’s create the screen of collection review where the logic of navigation and image loading will be implemented.

Disclaimer. I realize that from the viewpoint of the modern on-trend youth-oriented frontend, which largely uses redux and something like saga for manipulation of the app state, addressing API is located further from presentations in the code. So, if you suddenly decided to create a dead-serious app, you’d better take its architecture ultimately seriously (to the extent the word "serious" can be used when referring to JS apps :) ). Do some reading on one-way data flow, Flux, Redux, etc.

./screens/GridScreen.js

import React from 'react'; import { ActivityIndicator, StyleSheet, View } from 'react-native'; import ImageGrid from '../components/ImageGrid'; export default class GridScreen extends React.Component { state = { isLoaded: false, imagesList: [] } async componentDidMount() { /** * navigation – we pass react-navigation to props. * navigation.state.params hold our extra data, * we passed in the main screen */ const { navigation } = this.props; try { const response = await fetch( navigation.state.params.collection, { method: 'GET', redirect: 'follow'} ); const data = await response.json(); this.setState({ imagesList: data, isLoaded: true }); } catch (e) { console.log(e); } } render() { if (!this.state.isLoaded) { return ( <View style={styles.loaderContainer}> <ActivityIndicator size="small" style={styles.loader} /> </View> ); } return (<ImageGrid list={this.state.imagesList}/>); } } const styles = StyleSheet.create({ loader: { flex: 1, justifyContent: "center", alignItems: "center" }, loaderContainer: { flex: 1 } });

Previously, we passed a collection parameter through the navigate method on the CategoriesList screen. On this screen, we can extract it out of props. Collection is an url to receive an array in json. We will parse it, get the images, and display them on the screen.

componentDidMount method is async. Using it, we will upload json with the information and links to images, process them, and display the content based on the data. There is nothing new here for someone working in React, but I’ll explain it just in case. Our app is in a loading state when the screen has just been opened, and we haven't requested data via API yet. state.isLoading = true. In this case, the component displays the loading spinner (a.k.a. Native component ActivityIndicator). When the loading is about to complete, we refresh the state with the parsed images array and a prop isLoaded = false. The component is changed, but this time it has the array it can pass to ImageGrid component so that it could display the images. Mind the styles of loader and loaderContainer - yes, RN has flex we are all fond of so much. Such styling combinations make it possible to display the object exactly in the middle of the screen.

If you did everything right, after running npm run ios or npm run android in the emulator, you would see something like that:

componentDidMount method is async. We will be uploading here json with information and links to the images.

As you can see, it’s a no-brainer to build things on React Native. Of course, our app is quite simple. But I believe that RN works perfectly well for content apps or those that do not require serious calculations.

At this point, 50% are likely to give up reading and are adding a line in their CV (the other 40% have probably done it even earlier), but there are more things to tell.

Debugging

You have probably noticed that the app on a console gives a different display. You can use the homelike console.log() to display the values of the variables.Besides, Expo provides a powerful (like the son of my mother’s friend from that meme) utility for debugging in the test mode. On mac, press the combination Command+D in the emulator. It will open the window, where you should press Toggle Element Inspector.

Now it is switched into debugging mode that functionally resembles Chrome inspector:

You can look through the component structure and the styles applied, highlight touchable elements, etc. Use the lower panel to switch between the inspector's modes. To close it down, press the key combination Command + D and then again Toggle Element Inspector.

Compiling into a setup file

Let’s focus on compiling in a setup file and release preparation for App Store and Google Play platforms. For that matter, you need to prepare a configuration file app.json. It should specify release data: bundle ID, link to the app icon, the version, etc. Further, it would be best to run the command Expo for expo build:android or expo build:ios. In the process, it will require the information necessary for compiling. When working with iOS apps, Expo suggests managing certificates and electronic signatures used in the distribution processes that are quite convenient (it seems there is no reason to be concerned the Expo developers will steal your credentials - I used to be!)

If your app has no critical defects and a huge-sized file, Expo will provide a download link to the upload file after some time.

All the details are described here. Don’t forget to check the documentation version and your Expo and React Native versions! You have no idea how important it is!

That’s it, ta-daam!

You’ve read it through to the end. Well done! Here is the link to GitHub with the project we’ve been sweating on during all this article.

If you seriously mean to release your app, it will be just the initial stage of the preparations. There are many nuances and requirements you’ll have to comply with. And they vary on various platforms.

That’s about it. Looking forward to your questions, anathemas, and jokes in the comments. If someone is interested in the release process for the Apple Store, feel free to contact me, too. If there are many of those who are interested, I’ll push myself beyond the limits and talk about release preparations.