Beruflich Dokumente
Kultur Dokumente
of Contents
Introduction 1.1
React Native Internals 1.2
Setting up the project 1.3
2
File Structure for routes 2.7.3
DevOps ⚙ 2.8
Android Build setup 2.8.1
iOS Build setup 2.8.2
SVG Icons using react-native-vector-icons 2.9
Custom Icon set 2.9.1
Internationalization 2.10
Adding language toggle feature 2.10.1
Integration with react-navigation 2.10.2
Custom Native Modules 2.11
Android Native Modules 2.11.1
iOS Native Modules 2.11.2
References 3.1
The End 3.2
3
Introduction
https://www.reactnative.guide
4
Introduction
A reference for building production-grade applications which are easy to test, maintain
and extend to multiple platforms. This book is for the Web developers who have already
got their hands dirty with React and ES6 and want to build complex native apps.
We are following a native-first approach while keeping an eye out for potentially extending to
the web. You will eventually see how easy it is port the application to the web by following
conventions.
Our knowledge is based on our experience of working with React Native apps for around 2
years and helping clients launch their apps quicker than ever before.
We will build a Note Taker application while learning the concepts. There is a link to
the code at the end of every chapter. You can also see the app live if you have the
Expo app on your phone.
5
Introduction
Authors
https://www.gitbook.com/book/react-made-native-easy/react-made-native-easy/details
CONTRIBUTIONS
This is an open source book hosted on Github. We will keep updating the contents of the
book as and when it gets outdated. Please feel free to contribute or leave a comment in the
Disqus.
HALL OF THANKS
Reviewer / Proof reading
6
Introduction
Kakul Gupta
Contributors
Will Bowlin
Vishal Vasnani
Aritra Ghosh
7
React Native Internals
The primary difference between RN and Cordova based apps is that Cordova based apps
run inside a webview while RN apps render using native views. RN apps have direct access
to all the Native APIs and views offered by the underlying mobile OS. Thus, RN apps have
the same feel and performance as that of a native application.
At first, it is easy to assume that React Native might be compiling JS code into respective
native code directly. But this would be really hard to achieve since Java and Objective C are
strongly typed languages while Javascript is not! Instead, RN does something much more
clever. Essentially, React Native can be considered as a set of React components, where
each component represents the corresponding native views and components. For example,
a native TextInput will have a corresponding RN component which can be directly imported
into the JS code and used like any other React component. Hence, the developer will be
writing the code just like for any other React web app but the output will be a native
application.
To understand this, let us take a look at the architecture and how React Native works
internally.
Architecture
Both iOS and Android have a similar architecture with subtle differences.
If we consider the big picture, there are three parts to the RN platform:
1. Native Code/Modules: Most of the native code in case of iOS is written in Objective C
or Swift, while in the case of Android it is written in Java. But for writing our React Native
app, we would hardly ever need to write native code for iOS or Android.
2. Javascript VM: The JS Virtual Machine that runs all our JavaScript code. On
iOS/Android simulators and devices React Native uses JavaScriptCore, which is the
JavaScript engine that powers Safari. JavaScriptCore is an open source JavaScript
engine originally built for WebKit. In case of iOS, React Native uses the JavaScriptCore
8
React Native Internals
provided by the iOS platform. It was first introduced in iOS 7 along with OS X
Mavericks.
https://developer.apple.com/reference/javascriptcore.
In case of Android, React Native bundles the JavaScriptCore along with the application.
This increases the app size. Hence the Hello World application of RN would take
around 3 to 4 megabytes for Android.
In case of Chrome debugging mode, the JavaScript code runs within Chrome itself
(instead of the JavaScriptCore on the device) and communicates with native code via
WebSocket. Here, it will use the V8 engine. This allows us to see a lot of information on
the Chrome debugging tools like network requests, console logs, etc.
3. React Native Bridge: React Native bridge is a C++/Java bridge which is responsible for
communication between the native and Javascript thread. A custom protocol is used for
message passing.
In most cases, a developer would write the entire React Native application in Javascript. To
run the application one of the following commands are issued via the CLI - react-native
run-ios or react-native run-android . At this point, React Native CLI would spawn a node
packager/bundler that would bundle the JS code into a single main.bundle.js file. The
packager can be considered as being similar to Webpack. Now, whenever the React Native
app is launched, the first item to be loaded is the native entry point. The Native thread
spawns the JS VM thread which runs the bundled JS code. The JS code has all the
business logic of the application. The Native thread now sends messages via the RN Bridge
to start the JS application. Now, the spawned Javascript thread starts issuing instructions to
the native thread via the RN Bridge. The instructions include what views to load, what
information is to be retrieved from the hardware, etc. For example, if the JS thread wants a
view and text to be created it will batch the request into a single message and send it across
to the Native thread to render them.
[ [2,3,[2,'Text',{...}]] [2,3,[3,'View',{...}]] ]
The native thread will perform these operations and send the result back to the JS assuring
that the operations have been performed.
9
React Native Internals
Note: To see the bridge messages on the console, just put the following snippet onto the
index.<platform>.js file
Threading Model
When a React Native application is launched, it spawns up the following threading queues.
1. Main thread (Native Queue) - This is the main thread which gets spawned as soon as
the application launches. It loads the app and starts the JS thread to execute the
Javascript code. The native thread also listens to the UI events like 'press', 'touch', etc.
These events are then passed to the JS thread via the RN Bridge. Once the Javascript
loads, the JS thread sends the information on what needs to be rendered onto the
screen. This information is used by a shadow node thread to compute the layouts. The
shadow thread is basically like a mathematical engine which finally decides on how to
compute the view positions. These instructions are then passed back to the main thread
to render the view.
2. Javascript thread (JS Queue) - The Javascript Queue is the thread queue where main
bundled JS thread runs. The JS thread runs all the business logic, i.e., the code we
write in React Native.
3. Custom Native Modules - Apart from the threads spawned by React Native, we can
also spawn threads on the custom native modules we build to speed up the
performance of the application. For example - Animations are handled in React Native
by a separate native thread to offload the work from the JS thread.
Links: https://www.youtube.com/watch?v=0MlT74erp60
10
React Native Internals
Your browser does not currently recognize any of the video formats
available.
Click here to visit our frequently asked questions about HTML5 video.
0:00 / 24:13
View Managers
View Manager is a native module that maps JSX Views onto Native Views. For example:
11
React Native Internals
Here when we write <Text /> , the Text View manager will invoke new
TextView(getContext()) in case of Android.
View Managers are basically classes extended from ViewManager class in Android and
subclasses of RCTViewManager in iOS.
Development mode
When the app is run in DEV mode, the Javascript thread is spawned on the development
machine. Even though the JS code is running on a more powerful machine as compared to
a phone, you will soon notice that the performance is considerably lower as compared to
when running in bundled mode or production mode. This is unavoidable because a lot more
work is done in DEV mode at runtime to provide good warnings and error messages, such
as validating propTypes and various other assertions. Furthermore, the latency of
communication between the device and the JS thread also comes into play.
12
React Native Internals
Your browser does not currently recognize any of the video formats
available.
Click here to visit our frequently asked questions about HTML5 video.
0:00 / 30:36
13
Setting up the project
are mentioned on the official website of react-native. It might create a lot of confusion for
someone who is starting with RN to decide which one to use.
Expo: It is a third-party framework which provides you with a lot of cool features like
sharing your app with anyone while you are developing it and showing live code
changes on a real device by just scanning a QR code. We do not recommend using this
if your app uses a lot of third party native modules or you wish to hack around the native
code since you don't get access to native code out of the box. Refer to this page to
know more: Why not Expo? Also, it supports only Android 4.4 and above, which can
be a big turn off if your user base has a large number of Android 4.1 - 4.3 users.
14
Setting up the project
react-native init: This provides you with a very basic setup of the application including
native ios and android folders. This allows you to change the native code if required.
You would use native Android/iOS simulators or devices to run your application. You
can run the dev version of the application with react-native run-ios (it will open the
iOS simulator and run your app on it). Here we will need to setup everything from
scratch. But on the contrary, we get full access to native code and have control over
almost everything that is going on in our application. Also, it is easier to upgrade react-
native and other core libraries using this boilerplate as compared to others. Hence any
critical updates can be integrated much more easily. Thus, we recommend this
boilerplate for any long term production application.
15
See it in action!
16
Project Structure
Project structure
We will be creating a note-taking App in React Native. Let's call it NoteTaker . By the end of
this book, we will be able to build and deploy the NoteTaker to the Android Play Store and
the Apple App Store for users to download. We will begin with the standard React Native
boilerplate and as we progress through the concepts we will keep updating it. Also, at the
end of the book, we will show how to extend our app originally written in Android and iOS to
any other platforms, for example, web (because we just love web). So, let us begin.
Boilerplate
To create a new project run react-native init <project-name> . Example: react-native init
notetaker . We will use this as our base project in this book. Once the project setup is
.
├── .babelrc
├── .buckconfig
├── .flowconfig
├── .gitattributes
├── .gitignore
├── .watchmanconfig
├── android
├── ios
├── node_modules
├── __tests__
│ ├── index.android.js
│ └── index.ios.js
├── app.json
├── index.android.js
├── index.ios.js
├── package.json
└── yarn.lock
or
17
Project Structure
Note that for react-native run-android to work, you should have an open Android emulator
or an Android device with USB Debugging enabled connected to your system via a USB
cable.
If all goes well you should see the following screen on your iOS or Android emulator.
If you noticed there are two entry point files index.ios.js and index.android.js . Hence,
when we run the command react-native run-ios , the file index.ios.js serves as our
entry point.
Note: The above is applicable only to React Native 0.49 and below. Later versions
have only one entry point index.js
Under the hood, when we run the command react-native run-ios , the iOS native project
inside ios directory starts compiling. Along with the native project, react-native packager
kicks in on another terminal window and runs on port 8081 by default. The packager is
18
Project Structure
responsible for the compilation of JavaScript code to a js bundle file. When the native project
is successfully launched on the simulator, it asks the packager for the bundle file to load.
Then all the instructions inside the js code are run to successfully launch the app.
19
Customizing the project structure
Create a file index.js at app/index.js . This file will serve as the common entry point for
both Android and iOS projects.
Note: In the newer versions of react-native, there is only one index file for both Android and
iOS called index.js. Hence if you are using react-native 0.50+ then change only one file.
index.ios.js
index.android.js
app/index.js
20
Customizing the project structure
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
As you can see, now we have a single entry point to the code base. Hence both react-
native run-android and react-native run-ios will eventually run the same code
app/index.js
If you run the code now in iOS simulator, you should see
21
Customizing the project structure
Directory structure
Now, let's create a few directories inside /app that will help us structure our code such that
it is modular and easier to maintain.
cd app
mkdir assets components config pages redux
mkdir routes styles utils
22
Customizing the project structure
.
├── __tests__
│ ├── index.android.js
│ └── index.ios.js
├── app
│ ├── assets
│ ├── config
│ ├── styles
│ ├── utils
│ ├── components
│ ├── pages
│ ├── routes
│ ├── redux
│ └── index.js
├── app.json
├── index.android.js
├── index.ios.js
├── package.json
└── yarn.lock
app/assets - This is where all the images, videos, etc will go in.
app/config - This is where configurations for the app will go in. For example, your
app/utils - This is where all the services/utility files such as HTTP utility to make API
components will only do layouting and won't contain any states or business logic inside
them. All the data to these components will be passed in as props. This concept will be
explained in detail further in the book.
app/pages - This directory will hold all the smart components. Smart components are
those components which contain business logic and states in them. Their job is to pass
the props to the dumb components after all the business logic has been executed.
app/routes - This is where we will keep all our app's routing logic. This will contain the
23
Customizing the project structure
{
.....
.....
.....
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest",
"ios": "react-native run-ios",
"android": "cd android && ./gradlew clean && cd .. && react-native run-android
"
},
.....
.....
.....
}
From now on we will type yarn ios or yarn run ios from the command line to run the app
in iOS simulator. Similarly, for Android, we will type yarn android or yarn run android . In
case of Android, we are doing an additional step ./gradlew clean . This allows us to clear
any build cache that Android created before making an apk. This solves a lot of cache
related issues in case of Android during development.
24
Customizing the project structure
25
Creating basic components and writing platform-specific code
TextArea component
Create two files
26
Creating basic components and writing platform-specific code
This will be our TextArea UI component that we will use to enter our text.
Also, let's add our UI screen which will contain the TextArea component.
27
Creating basic components and writing platform-specific code
And finally let's add the Home screen to the main app.
app/index.js
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, { Component } from 'react';
import Home from './components/Home/Home.component';
If you run the app now, you should have something that looks like this
28
Creating basic components and writing platform-specific code
Platform-specific code
Now, as we can see we have different outputs in Android and iOS.
29
Creating basic components and writing platform-specific code
You should see that both Android and iOS TextAreas look the same now.
30
Creating basic components and writing platform-specific code
Basically, under the hood, while importing/requiring a file during bundling the js, say
TextArea.component , using the syntax
,the Android React Native packager looks for the files in the following order
Note: Platform specific code can also be written by making use of the Platform module
from react-native.
31
Creating basic components and writing platform-specific code
Notice that here only a single TextArea.component.js file is present for both Android and
iOS. This will also produce similar results.
But we prefer to use the first method where we create separate files with .android.js and
.ios.js extensions.
1. In the case of different extensions we have different files, hence only the code needed
for running our logic is bundled into the application. For example, let's assume you
decide that you will use a custom library for TextArea only for Android. Now if we import
that library into TextArea.android.js , only in the case of the Android bundling will a new
library be added, iOS will remain the same. Hence, it reduces our code size when
bundling.
2. In practice, we have noticed that Platform module method is error prone as we have to
manually check for Platform.OS wherever there are subtle differences in the output.
32
Creating basic components and writing platform-specific code
33
Conventions and Code Style
Conventions
Every team is composed of developers who follow different conventions. Hence, we often
find that code written by a developer is not easily understood by another developer on the
same team. Not having a proper convention creates dependencies on individuals and
makes the project difficult to understand by a newcomer. Tight dependencies in a
software development project can affect the velocity of the team.
The best way to solve this is by deciding and following code conventions.
Code conventions can be as easy as following 4 spaces instead of tabs, or always ending a
statement with a semicolon, etc. It could also be something more complex like not allowing
setState() to be invoked on componentWillMount of a React Component.
So if you want your team to code like this guy, set and follow conventions.
34
Conventions and Code Style
35
ESLint: The guardian of code conventions ⚔
ESLint allows us to maintain consistent code style throughout the project. Thus, any
developer on the team can easily understand the code written by another developer.
This can exponentially increase the team's velocity and avoid dependencies. It can
reduce human errors and can act as a guardian that maintains code conventions.
Examples
Apart from code conventions, ESLint also spots common mistakes made by developers. For
example,
var a = 1, b = 2, c = 3, e = 4;
The above code will compile fine. But as soon as you execute it, it will throw a runtime
exception ReferenceError
test()
Installation
It is pretty easy to setup ESLint for a project.
36
ESLint: The guardian of code conventions ⚔
This would install ESLint and other useful plugins as your dev dependencies.
{
"name": "testapp",
"version": "0.0.1",
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
....
....
....
"lint": "eslint app/",
"lint:fix": "eslint app/ --fix"
},
"dependencies": {
....
....
npm run lint will run the ESLint and show a list of errors that need to be fixed. npm run
lint:fix will run ESLint and attempt to correct the errors it is able to fix automatically.
37
ESLint: The guardian of code conventions ⚔
Some of these plugins also support features like lint on save. Thus, ESLint attempts to run
eslint --fix <current_file> the moment you save (cmd+s) a file. This fixes all auto-
fixable lint errors such as incorrect indentation spaces, adding semicolons at the end of a
line, etc.
38
ESLint: The guardian of code conventions ⚔
The important area in the above configuration is the rules section. This section controls all
the code conventions followed in the project.
The complete list of all the available rules is present here: http://eslint.org/docs/rules/
It can be pretty overwhelming at first to decide which rules should go in. Hence we can start
with
39
ESLint: The guardian of code conventions ⚔
These should cover all the basic rules needed to start your project.
"rules": {
"react/no-did-mount-set-state": 2,
"react/no-direct-mutation-state": 2,
"react/jsx-uses-vars": 2,
"no-undef": 2,
"semi": 2,
"react/prop-types": 2,
"react/jsx-no-bind": 2,
"react/jsx-no-duplicate-props": 2,
....
....
....
},
Rest of the rules can be added based on what conventions the team decides to follow.
Recommendations
We would suggest adding more of auto-fixable rules as the corrections suggested by these
rules can be fixed by the editor with an ESLint plugin on file save. This would reduce the
time that a developer would spend fixing lint errors than writing actual code.
We also recommend using ESLint for spacing/tabs instead of other methods like
.editorconfig . This way, all the code conventions can be configured via a single utility
(eslint) and the fixes can be made by the editor itself on save.
40
Github Pre-push/Pre-commit Hooks
Pre hooks run a shell command which you specify and if the command fails with exit status
1, the process will get terminated. We store the shell commands inside the .git/hooks
directory of the root of the project. So imagine that you won't be able to push the code if
your tests are failing or your js files are not properly linted. That sounds interesting
right?
Your next question might be, how do you make sure that everyone adds those
commands to the pre-push hooks folder?
Don't worry, we have that covered as well. Let me introduce you to an awesome NPM
module called husky .
Using Husky
41
Github Pre-push/Pre-commit Hooks
Just run npm install --save-dev husky or yarn add --dev husky
After installing these node-modules, all you need to do is specify which command you would
like to execute before pushing/committing your code. And this can be specified within
package.json . It will make sure to add the commands to your local git hooks folder while
We used husky's prepush hook to make sure that every line of code which is being pushed
is linted and all the tests pass.
If you look at the package.json file of our boilerplate, you would find this snippet:
{
"scripts": {
"prepush": "npm run lint && npm run test",
"postinstall": "rm -rf .git/hooks/pre-push && node node_modules/husky/bin/install.
js && rm -rf .git/hooks/pre-commit"
},
"devDependencies": {
"husky": "^0.14.3"
}
}
As you can see, we are doing npm run lint and npm run test every time the developer
tries to push something. If any of the commands fail, the developer would not be allowed to
push, greatly reducing the chances of build failure/PR check failure in CIs.
42
Github Pre-push/Pre-commit Hooks
#!/bin/sh
#husky 0.14.3
command_exists () {
command -v "$1" >/dev/null 2>&1
}
has_hook_script () {
[ -f package.json ] && cat package.json | grep -q "\"$1\"[[:space:]]*:"
}
...
...
This code is autogenerated by the node module so we don't have to worry about it. If you
are not able to find this file, it means that your hooks were not properly installed. Please
uninstall and install the module again or do npm run postinstall .
Note: In the case of Windows machines, make sure that you have bash(Cygwin).
Prepush hooks will fail if you use Command Prompt
43
Environment Variables
Environment variables
Since react-native combines native platform code with JS, you might find it hard to
accomplish small tasks like passing environment variables. But don't worry, we have you
covered here as well.
The app will contain all the env configs inside environment/ folder. The CI will replace the
contents of env.config.js with environment/prod.config.js before starting the build
process.
44
Environment Variables
The command is pretty simple. All we need to do is copy the contents of one file to another.
cp app/config/env/prod.env.js app/config/env.config.js
Head over to TravisCI chapter to know how and where to put the above-mentioned
command.
45
Speed up development with some and ES7 features
By using this feature, you can define class members ( state for eg.) without the need of
constructor .
Before:
After:
46
Speed up development with some and ES7 features
Before:
After:
You might say that there is nothing new in this. But if used well, it can make your React code
much more beautiful and clean. Let me show you the code before and after using spread
operator.
Before
47
Speed up development with some and ES7 features
After
48
Testing and Debugging
Testing
With the growing complexities of applications, unit testing becomes mandatory. Since we are
writing code in JS, we can utilize most of the testing frameworks/libraries available for
React/web apps without requiring many changes.
We recommend using the Jest Framework plus some additional utilities like Enzyme to
make a developer's life easier.
We all know that it is difficult for a QA and a dev to live in harmony. Devs do not like it
when the QA team finds bugs in their work and tells them to make changes. If your code
has a good test coverage, there are fewer chances of finding bugs. That's a win-win
situation for both sides right?
All the test frameworks have a describe block where you define what the method is
supposed to do, just how you would write comments for your code.
You will start asking these questions to yourself while coding: How will I test this code?,
How do I make sure that each method I write can be tested? If you already ask these
questions while writing code, then you are among a small minority of developers.
If your code is tested, there are 99% chances that it is modular, which means that you
can easily implement changes in the future.
There will be cases when you will be required to change a function's logic, e.g., a
currency formatter function which returns a string. One way of debugging it would be to
go through the UI and check if the desired output is showing up. Another smart way
49
Testing and Debugging
would be to fire your test cases, change the test results according to your desired output
and let the test fail. Now change the logic inside your method to make the failed test
pass.
Many of us give the excuse that writing test cases will increase the development time
(even I was of the same opinion). But believe me, it doesn't. During the course of
development, almost half of the time is spent in debugging/bug fixing. Writing unit tests
can decrease this time from 50% to less than 20%.
50
Jest setup
Jest setup
For all those who have not heard about Jest, have a quick look here:
https://facebook.github.io/jest/
Jest is used by Facebook to test all JavaScript code including React applications. One
of Jest's philosophies is to provide an integrated, "zero-configuration" experience. We
observed that when engineers are provided with ready-to-use tools, they end up writing
more tests, which in turn results in more stable and healthy codebases.
Minimal configuration
Watches only changed files
Fast
Snapshot testing (explained later)
Coverage out of box
"scripts": {
"test": "jest --verbose --coverage",
"test:update": "jest --verbose --coverage --updateSnapshot",
"test:watch": "jest --verbose --watch",
"coverage": "jest --verbose --coverage && open ./coverage/lcov-report/index.html",
}
test : It will go through all the test files and execute them. This command will also be
components. If the snapshot is not there, it will create it for you. We will discuss
snapshots in detail in coming chapters.
coverage : As the name suggests, this command will generate a coverage report.
Testing conventions:
It is highly recommended to have conventions for test files as well. Here are the conventions
we follow:
51
Jest setup
Jest recommends having a __test__ folder in the same directory as the file to be
tested.
The naming convention for test files is <testFileName>.test.js . If you are writing tests
for abc.component.js , then the test filename would be abc.component.test.js .
In each expect , we first mention the function name which is to be tested.
//counter.util.js
const counter = (a) => a + 1;
//__test__/counter.util.test.js
describe('counter: Should increment the passed value', () => {
...
});
Jest configuration:
As we read in the documentation, Jest is indeed very easy to set up. You do not need a
separate config file. The configuration is so simple that it can fit inside package.json .
"jest": {
"preset": "react-native",
"cacheDirectory": "./cache",
"coveragePathIgnorePatterns": [
"./app/utils/vendor"
],
"coverageThreshold": {
"global": {
"statements": 80
}
},
"transformIgnorePatterns": [
"/node_modules/(?!react-native|react-clone-referenced-element|react-navigation)"
]
}
preset : The preset is a node environment that mimics the environment of a React
Native app. Because it doesn't load any DOM or browser APIs, it greatly improves
Jest's startup time.
cacheDirectory : It helps you greatly improve the test speed. It does so by creating a
cache of compiled modules so that it doesn't have to compile the node_modules every
time we run tests.
52
Jest setup
coverage reports.
coverageThreshold : Defines the threshold limit for all the tests to pass. If the coverage
is less than the defined limit, the tests would fail. This helped us in keeping code
coverage high throughout development.
transformIgnorePatterns : All the npm modules which need to be transpiled are added
Note: Make sure to add cache and coverage in your gitignore file.
53
Snapshots
Snapshots
This feature makes testing presentational components a lot easier. With a single line, you
can test all your presentational components (their render method only). There is no need to
write test cases for each component returned by render method.
__tests__/someComponent.component.test.js
__tests__/snaphots/someComponent.component.js.snap
54
Snapshots
As you can see above, the snap contains every possible property of the UI which is being
returned by the render method.
It is expected that all snapshots are part of the code that is run on CI and since new
snapshots automatically pass, they should not pass a test run on a CI system. It is
recommended to always commit all snapshots and to keep them in version control.
55
Snapshots
the snapshot to make the test pass. All that is needed to update the test case is one
command: jest --updateSnapshot and he is done.
We recommend creating an npm script for updating snaps. As you can see in the
package.json of our boilerplate, it contains a command called "test:update". This command
goes through all the test cases and will update the snap whenever it is required.
56
Testing stateful components using Enzyme
What if your component contains some class methods? What if your component contains
state?
What's Enzyme?
Enzyme is a JavaScript testing utility for React. You will mostly be using the shallow utility
from Enzyme. Shallow utility helps us in rendering a component and allowing us access to
the class methods/state of the component.
package.json
{
...,
"jest": {
...,
"setupTestFrameworkScriptFile": "./node_modules/jest-enzyme/lib/index.js",
"setupFiles": ["enzyme-react-16-adapter-setup"]
}
}
57
Testing stateful components using Enzyme
Now, our component is rendered and we can access props/state/methods using wrapper .
Here is how you can access them:
As you can see, you can access everything a component possesses using shallow utitity.
You can also have a look at the example test case in our boilerplate code here.
Example
Let's take an example of a component with state and class methods. We will write test cases
for the methods including a snapshot test. The example includes testing class methods,
state, and props.
58
Testing stateful components using Enzyme
59
Mocking RN modules
Most of the React Native specific utilities are automatically mocked for us since we are using
react-native preset. If you install any other modules which aren't automatically mocked, say
a react-native module which has a dependency on NativeModules, you would need to mock
the module manually.
├── config
├── __mocks__
│ └── react-native-exception-handler.js
├── utils
│ ├── __mocks__
│ │ └── language.util.js
│ └── language.util.js
└── node_modules
As you can see above, you can mock both nodemodules and your custom defined JS files.
Whenever you import utils/language.util file, Jest will automatically read it from
`_mocks/language.util.js`.
If you would like to un-mock a file and read the real source code instead of mocking it, you
could do that using jest.unmock('module_path') function in the test case.
Note: You can also use the automock feature of Jest by using a function called
jest.genMockFromModule . But this might not work for some of the modules which return
nested methods and it will only mock the functions which a module exports.
Example of automock:
Let's say you want to mock the functions exposed by a Node.js module called react-native-
exception-handler
60
Mocking RN modules
Since it is a nodemodule, we would need to put the mock file inside `_mocks/react-native-
exception-handler`. Refer to the tree above to know where this file will lie.
To automock the module, our file would look something like this:
__mocks__/react-native-exception-handler.js
module.exports = mockedModule;
If we want to mock this module manually, we need to know all the functions the module
exports and mock them individually. Our file would look something like this:
__mocks__/react-native-exception-handler.js
export default {
setJSExceptionHandler: jest.fn(),
getJSExceptionHandler: jest.fn(),
setNativeExceptionHandler: jest.fn()
};
Then in the test file, we will say that we want to mock a module by doing:
jest.mock('module_path');
NPM modules inside the mock folder residing adjacent to the node_modules are
mocked by default. Hence we do not need to mock them separately.
To know more about the manual mocking in Jest, please visit here.
61
Styling
Example
To add padding and border to a span in CSS we will write:
.button {
padding: 10px;
text-align: : center;
border: 1px solid black;
}
Resulting in :
Test
In React Native, there is no concept of pixels or classes. Instead, our sizes will be specified
in "units" which will then be translated to pixels based on the pixel density of the screen
automatically by RN. If we write the same View in a React Native StyleSheet, it would look
something like this:
62
Styling
styles.js
...
...
...
<View style={styles.button}></View>
Just like web, it is very easy to go wrong with CSS and end up with a style code that is
unmanageable. Hence, we would like to introduce a few basic guidelines in the next
chapters, so that we can get the most from our styles.
63
Theme Variables
Theme Variables
In general, every app should have well defined font sizes, colors, spacing, etc. This is done
so that the app looks consistent across screens. Now this can be achieved by maintaining a
convention across the app. For example, the devs and UX designers can decide fontSize
as:
16 - Large
14 - Medium
12 - Small
styles.js
Even though this convention is good and will help you maintain consistency in small,
medium and large text sizes across the app, it does have a few fundamental flaws:
Business/marketing team may come up with a change in the requirement that the large
font size needs to be 18 instead of 16.
Now as a developer, you will need to make changes in the entire app and replace every
instance of fontSize:16 with fontSize:18, which kind of sucks!.
A new developer who joins the team might not be aware of all the conventions followed
by the team and may create a component with fontSize other than 12, 14 or 16, thus
accidentaly overlooking the code standard/convention.
64
Theme Variables
app/styles/theme.style.js
export default {
FONT_SIZE_SMALL: 12,
FONT_SIZE_MEDIUM: 14,
FONT_SIZE_LARGE: 16,
PRIMARY_COLOR: 'rgb(30, 147, 242)',
SECONDARY_COLOR: 'rgb(238, 167, 2)',
FONT_WEIGHT_LIGHT: 200,
FONT_WEIGHT_MEDIUM: 600,
FONT_WEIGHT_HEAVY: 800
};
styles.js
Now our theme file dictates the size of fonts and the primary color, etc.
If our business team now tell us to change the font sizes, we can change the theme
variables at one place and it gets reflected in the entire app.
This will enable us to write multiple theme files which in turn adds basic theming support
to our app. For example we can write two theme files - one for a light theme and one for
a dark theme and give our app users the option to switch between the themes.
65
Theme Variables
app/style/theme.style.js
export default {
PRIMARY_COLOR: '#2aabb8',
FONT_SIZE_SMALL: 12,
FONT_SIZE_MEDIUM: 14,
FONT_SIZE_LARGE: 16,
FONT_WEIGHT_LIGHT: '200',
FONT_WEIGHT_MEDIUM: '500',
FONT_WEIGHT_BOLD: '700',
BACKGROUND_COLOR_LIGHT: '#f0f6f7',
CONTAINER_PADDING: 20
};
app/components/TextArea/TextArea.component.style.js
app/components/Home/Home.component.js
66
Theme Variables
app/components/Home/Home.component.style.js
67
Theme Variables
68
Theme Variables
69
Common Styles/Mixins
Common styles
In React Native, each component is styled using inline styles. This means that it becomes
slightly tricky to share styles as you can in web.
.btn {
padding: 10;
border: '1px solid black';
}
Now if we want to apply this class to two different divs we will do so as follows:
70
Common Styles/Mixins
...
...
...
<View>
<View style={[styles.btn, styles.firstBtn]}>First button</View>
<View style={[styles.btn, styles.secondBtn]}>Second button</View>
</View>
This solves the problem only if the style objects are in the same component because in RN
we do not import styles from other components (each component has its own style). But in
web, we could have just reused the class anywhere (since css is global).
To solve the problem of reusable styles in React Native, we introduce another file named
app/style/common.style.js This is where we will write our mixins/common styles.
Hence, if all the buttons in our app have a similar style we can write a style with similar
properties inside the common.style.js
app/style/common.style.js
And we can just import this in our component style files and reuse them directly like this:
styles.js
71
Common Styles/Mixins
...
...
...
<View>
<View style={styles.firstBtn}>First button</View>
<View style={styles.secondBtn}>Second button</View>
</View>
This way our mixins/common style file will provide us the base styles which are common
across the app and we write component specific styles in the component style file. This
allows significant style reuse and avoids code duplication.
app/components/Home/Home.component.style.js
72
Common Styles/Mixins
app/styles/theme.style.js
export default {
PRIMARY_COLOR: '#2aabb8',
FONT_SIZE_SMALL: 12,
FONT_SIZE_MEDIUM: 14,
FONT_SIZE_LARGE: 16,
FONT_WEIGHT_LIGHT: '200',
FONT_WEIGHT_MEDIUM: '500',
FONT_WEIGHT_BOLD: '700',
BACKGROUND_COLOR_LIGHT: '#f0f6f7',
CONTAINER_PADDING: 20,
TEXT_INPUT_PADDING: 10
};
73
Common Styles/Mixins
If you notice, even though we have a theme file, our style code has a lot of duplicated code.
This is primarily because we are repeating our styling for text input and also for the heading.
74
Common Styles/Mixins
If you see our style code looks much more concise and we are resuing the styles for similar
components with slight style changes. Hence, we import our base styles for the components
from common.style.js and add our custom styles later on top of it. This way we reduce our
75
Common Styles/Mixins
We see no change in the output but our code becomes much cleaner.
76
Common Styles/Mixins
77
Separating styles from component
Button.component.js
This will produce a nice looking button component. But we suggest that you move the styles
to a different file Button.component.style.js .
Button.component.js
78
Separating styles from component
Button.component.style.js
This makes the component code much cleaner. The style is present in its own separate
file.
This allows you to write two different style files for Android and iOS if required. Thus,
you can keep the same functionality but the button can look different based on the
requirement for the platform.
For example:
Button.component.js
79
Separating styles from component
Button.component.style.ios.js
Button.component.style.android.js
80
Separating styles from component
Thus by simply moving the styles into a separate file, we could achieve a style code that
behaves exactly the way we needed on different platforms. Also, we could reuse the
component logic.
Conclusion
In Web, we have lots of production grade tools like Sass, Less, etc which allow us to write
modular, scoped CSS which is easier to manage. These tools then take care of building all
our style code into one cohesive stylesheet for our entire application. In React Native, we
must think of styling in a slightly different manner. By doing some pre-planning and
organization before writing the code for the components, we can reduce code duplication
and unnecessary confusions. It takes a bit of getting used to, but styling in React Native is
as powerful as CSS for the web and is the fastest way to build multi-platform native
applications.
81
Redux
Redux is a predictable state container for JavaScript apps. It helps you write
applications that behave consistently, run in different environments (client, server, and
native), and are easy to test. On top of that, it provides a great developer experience,
such as live code editing combined with a time traveling debugger.
In a nutshell, Redux is a state container. That means, it will contain all our runtime
application state or data.
Store - The store contains a global state for the entire app. It is basically the manager of
the application state.
Actions - These are the commands you pass to the store along with some data to
modify the stored state.
Reducers - Reducers are functions that the store calls whenever an action arrives. The
reducers determine what the new state will be based on the action and the action
payload they receive.
React state is stored locally within a component. When it needs to be shared with other
components, it is passed down through the props. This means that all the components which
need the state data need to be children of the component holding the value. But in case of
Redux, the state is stored globally in the Redux store. Components subscribe to the store to
get access to the value. This centralizes all data and makes it very easy for a component to
get the state it needs, without surrounding components being affected.
So, does this means that Redux is great and we should use it for all our app state
management?
NO!
82
Redux
While Redux is helpful in some cases, it will create unnecessary indirections for simpler and
trivial use cases.
Consider that we have a text input. And since we are using Redux, we decide to use Redux
to store all the changes in the text field in the Redux store. In Redux, for changing the state
on text input, we will need to create an Action, write a reducer and then subscribe our
component to the store so that it re-renders on every state change. This is bad! Why do we
need to complicate things so much?
Dan Abramov - The creator of Redux says you might actually not need Redux unless you
have a plan to benefit from this additional indirection. In his blog at
https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367, he clearly
states that since Redux introduces an additional indirection to managing the state of the
application, we should use it only if it benefits us.
Now, let's see when we should use Redux and when React's state is good enough.
One way to do this is based on the duration the data has to be stored. As Tyler Hoffman
explains in his blog post: https://spin.atomicobject.com/2017/06/07/react-state-vs-redux-
state/,
Different pieces of state are persisted for different amounts of time. We can categorize data
into:
83
Redux
returned from the server needs to be stored and will be used by a completely unrelated
profile screen. Now, if the data is stored in some global location, it will be far easier to
access this data. Such type of use cases clearly fits Redux.
84
Redux setup
app/components/Home/Home.component.js
...
...
...
onChangeText={this.setTitle} value={this.state.title} />
<Text style={styles.textAreaTitle}> Please type your note below </Text>
<TextArea style={styles.textArea}/>
<View style={styles.bottomBar}>
<View style={styles.bottomBarWrapper}>
<Text style={styles.saveBtn}>Save</Text>
<Text style={styles.characterCount}>{20} characters</Text>
</View>
</View>
</View>
);
}
app/components/Home/Home.component.style.js
85
Redux setup
app/styles/theme.style.js
...
...
BACKGROUND_COLOR_LIGHT: '#ebe9e9',
...
...
};
Now our app should have a bottom bar with a character count and a save button.
86
Redux setup
But if you look at the app now, there is no way for us to get the character count from the
TextArea component and use it as the text for the character count text view. To do this we
will need to move the state present inside the TextArea component and place it in the
Home component. This is because all the components that need access to a state have to
be children of the component holding the state.
app/components/TextArea/TextArea.component.js
87
Redux setup
app/components/Home/Home.component.js
88
Redux setup
The character count should now update whenever you enter text on the text field.
89
Redux setup
By moving the state from the child component to the parent, we were able to access it in
multiple children components.
Therefore, to provide access to the data that needs to be accessed by multiple components,
we need to have the state in the enclosing parent component. Following this principle, if we
keep on moving the state to the parent component, we will end up with the state in the
topmost level component.
Redux builds on top of similar principles. It keeps a global store to which the components
which need access to the data can subscribe. Additionally, it provides a mechanism by
which these components can re-render whenever the data in the store changes.
Now, since we understand how Redux is helpful, let's setup Redux for our app.
Setup
Let's begin by installing a few packages.
yarn add redux react-redux redux-promise redux-thunk
90
Redux setup
or
npm install --save redux react-redux
Additionally, you can also install your preferred Redux middleware like redux-thunk , etc.
The comments on the code specify how to do that.
app/redux/store.js
module.exports = {
initStore
};
app/redux/reducers/root.reducer.js
});
91
Redux setup
app/redux/actions/index.actions.js
app/redux/reducers/test.reducer.js
Modify
app/redux/reducers/root.reducer.js
app/index.js
92
Redux setup
app/App.container.js
At this point, we should a have a Redux store with an initial test state from the test reducer.
93
Redux setup
Now open up the debug menu on the iOS simulator by pressing cmd+ctrl+z or on Android
emulator by using cmd+m .
This should run the app JS code in react-native-debugger and if all goes well we should see
something like this on the console panel:
This implies that our Redux store is successfully initialized with the test reducer.
NOTE: If your tests fail due to the error window not defined , then add a mock file
__mocks__/react-native.js
var rn = require('react-native');
global.window = global;
module.exports = rn;
This will initialize a dummy window variable when tests are run in node environment.
94
Redux setup
95
Presentational VS Containers
Presentational components
Presentational components are those components whose only job is to render a view
according to the styling and data passed to them. Essentially, they do not contain any
business logic. That's why they are sometimes also called dumb components . This means
that they don't have direct access to Redux or other data stores. Data is passed to them via
props.
96
Presentational VS Containers
Container components
Container components are those React components which have access to the store. These
components make API calls, do processing and contain the business logic of the app.
Container components shouldn't have the view logic or presentational logic. The job of
container components is to compute the values and pass them as props to the
presentational components. Hence, these components are sometimes also referred to as
Smart Components.
97
Presentational VS Containers
In the above example, if you notice, our Container component does not do any kind of
layouting or styling. It only manages the business logic. This helps us separate the concerns
"Styling/Layouting" and "Business Logic".
Less code duplication. Because you are forced to move all the layout components out
as separate presentational components, you can now directly reuse them instead of
copy-pasting the code in every page.
Presentational components are essentially your app’s View layer. Hence, you can
change the styling without touching the app's logic.
Better separation of concerns. You understand your app and your UI better by writing
components this way.
Better reusability. You can use the same presentational component with completely
different state sources, and turn those into separate container components that can be
further reused.
Back to Code
98
Presentational VS Containers
Let's organize our project to include presentational and container components pattern.
First, let's add a new reducer to manage our content (title and text).
app/redux/actions/index.actions.js
Notice the use of createAction . As the comment says, we are essentially replacing:
with
app/redux/reducers/content.reducer.js
99
Presentational VS Containers
const defaultState = {
text: '',
title: ''
};
app/redux/reducers/root.reducer.js
app/pages/Home.page.js
100
Presentational VS Containers
HomePage.propTypes = {
setTitle: PropTypes.func,
setText: PropTypes.func,
title: PropTypes.string,
text: PropTypes.string
};
If you notice the job of Home Page is just to fetch data and provide logical functions to the
view layer.
app/components/Home/Home.component.js
101
Presentational VS Containers
Home.propTypes = {
setTitle: PropTypes.func,
setText: PropTypes.func,
title: PropTypes.string,
text: PropTypes.string
};
Finally, let's modify our app container to include the page instead of the component.
app/App.container.js
102
Presentational VS Containers
Although the app looks exactly the same, it is working in a slightly different manner.
We now have a clear demarcation between the view and logic layer. Hence, we will
know exactly where to look in case there is either a UI or logical bug.
We are not importing react-native at all in any part of the whole code base except in
103
Presentational VS Containers
app/components/ . This means porting the project to any other platform like Electron is
as simple as rewriting the app/components directory. The logical layer remains intact.
The code is easier to maintain as the flow of data to the view layer happens via props.
104
Navigation
Navigation
Navigation in react-native is pretty different, especially if you are coming from a web
background. It follows native standards for navigation. For example, it uses the concept of
screens, stacks, push/pop, which most web developers have not used for routing.
React-native has a bunch of options for routing. The different options have been mentioned
here: https://facebook.github.io/react-native/docs/navigation.html. We have found react-
navigation to be the most stable among all of them.
Why react-navigation?
The official react-native documentation recommends using react-navigation. It says,
"You will probably want to use React Navigation." .
Both Android and iOS routes can be handled by one single configuration file without any
platform specific configuration.
Provides multiple types of navigators out of the box. E.g.: StackNavigator, TabNavigator,
DrawerNavigator.
Redux integration is available. It's very easy to integrate with the Redux store. The
benefit of this is that you can navigate by just dispatching an action and passing the
route name. It makes route management much easier. After integration, all you need to
do is: dispatch(NavigationActions.navigate({routeName})) .
105
Using React-navigation
Using React-Navigation
Using react-navigation is pretty simple once you understand how it works. Unfortunately,
their official documentation lacks some explanations. But there is no need to worry. We will
try to fill in the gaps in the official documentation in this chapter. The official documentation is
available at https://reactnavigation.org/docs/getting-started
106
Using React-navigation
React-navigation offers a bunch of navigators predefined for our use. An app might contain
all the available navigators. Let's try to understand all three of them one-by-one.
StackNavigator: This is the most common navigator of all. You will use it for most react-
native applications. We just explained how stacks work in navigation. You define a
StackNavigator by passing the routes object inside the StackNavigator function from
react-navigation. Each of the screens gets mounted only when you navigate to that
particular screen and gets un-mounted only when you go back or manually reset the
navigation state.
TabNavigator: This is also quite a common user interface where we have a bunch of
tabs and can swipe between them. We have seen this in Facebook, Twitter, and many
other popular apps. React-navigation supports this navigator out of the box. The way of
defining a TabNavigator is pretty similar to a StackNavigator. The only difference is that
the routes you define in TabNavigator get mounted all at once. So you need to be a little
careful when managing navigation to/from a different stack.
DrawerNavigator: This navigator gives us the side-menu which we see in most mobile
applications nowadays. You can obviously customize everything in your drawer since it
is all written in JavaScript. You can set up the DrawerNavigator by writing only 4 lines of
code. The DrawerNavigator comes with a default UI which supports an icon and a title
per list item, which looks quite elegant. You can also define your own component to
show up as a drawer if you want to have a custom UI and actions for your side-menu.
More info can be found here.
Creating a router
Install react-navigation using npm or Yarn.
Creating a router is pretty easy. You just define a page component (which will be a container
component) and then import it in your router.js file.
Each of the navigators accepts an object during initialization whose syntax is as follows:
107
Using React-navigation
routeName is the name associated with the current screen. It will be used for
navigation/analytics tracking.
Example routes/index.js
Each of the navigators returns a React component which is supposed to be added to the
root level of the app.
Example App.container.js
108
Using React-navigation
When we define a React component in our router file, it adds a few properties to the
component, which are:
static navigationOptions: We can use this to define our headers, title, etc. However, we
recommend defining this in router.js because we will be importing pages to our router
and defining header UI/title in the container component is not a good idea.
109
Using React-navigation
110
Integrating with redux store
As you can see, the library maintains its own store and it logs every action in the dev mode.
The library also has the flexibility to be integrated with the Redux store of your application.
While the integration is completely optional, we highly recommend doing this if your app is
already using Redux because of the following reasons:
1. It gives us a lot more flexibility to control the route state at every point in time. Consider
a case where you want to call a function on every route change, for example for screen
tracking. You can use redux-middleware in such a scenario.
2. It makes the navigation much more readable and cleaner. You just have to dispatch an
action with the routeName to which you want to navigate.
111
Integrating with redux store
Integration with the Redux store is pretty easy. Let's continue with the integration in our
NoteTaker app using three simple steps.
reducers/index.js
route in Navigator and params and returns an action which is needed to update the
navigation state. In redux language, we need to call this action to navigate to a
route.
Output of this statement: Object {type: "Navigation/NAVIGATE", routeName: "home",
action: Object}
This is the action that we get for the path 'home'. This becomes the initial route of our
navigator.
navigating to the initial route, now we have to update the state of the Navigator to
actually navigate to the route. So we pass the action and the current state of the
navigator to getStateForAction and it returns the new updated state.
Output of this statement:
112
Integrating with redux store
113
Integrating with redux store
//example routes
import AuthRoutes from './auth.routes';
//example component
import Home from '../Components/Home';
render() {
const addListener = createReduxBoundAddListener('root');
const { dispatch, nav } = this.props;
return (
<Router
navigation={addNavigationHelpers({ dispatch, state: nav, addListener })}
/>
);
}
}
RouterWithNavState.propTypes = {
dispatch: PropTypes.func,
nav: PropTypes.object
};
After the integration, you will be able to see the navigation state and actions inside your
debugger's store.
114
Integrating with redux store
Let's refactor our Home.page.js file to dispatch an action on About button click instead of
using this.props.navigation.
Home.page.js
As you can see above, we are dispatching an action to navigate to about page. You might
be wondering what's NavigationActions.navigate . Well, it's just an action creator which
dispatches an action with type "Navigation/NAVIGATE".
115
Integrating with redux store
You can access the passed params in the page using this.props.navigation.state.params
inside the page file.
About.page.js
Gotchas
Here are some gotchas which you might face:
You only have access to the routes defined in the current stack while navigating. If you
try to navigate to a sibling stack from a nested page, you will face an error. To achieve
this, you would need to goBack to the index screen of the current stack and then go to
the screen where you want to navigate or use reset action creator.
If you wish to add analytics/screen tracking, use redux-middleware defined here.
Otherwise, you could also use the redux-ga-screen-tracker npm module, which does
Google analytics screen tracking automatically.
116
Integrating with redux store
117
File Structure for routes
This file can become unmanageable in a very short time. Hence we split the files into
multiple files, each defining its own stack and importing styles from a separate file.
routes/index.js
118
File Structure for routes
As we can see, the current file defines two stacks, AboutRoutes, and the main Router.
Let's create a new file which will contain just the screens required for About page and import
it in the routes/index.js file.
routes/index.js
119
File Structure for routes
routes/about.routes.js
We can further modularize it such that all the navigation config data comes from a different
file. This way, there will be a single file containing navigation config for all the routes. We
can even reuse some of the configs.
120
File Structure for routes
config/routes.config.js
routes/about.routes.js
The router file looks much cleaner now, defining just the routes and their title. In future, if we
need to change the config, we need not go inside each route file and search for its config.
121
File Structure for routes
122
DevOps ⚙
DevOps
Whenever we start a new project, after setting up the boilerplate, best practices suggest that
we set up the continuous deployment so that the team can easily trigger and receive test
builds of the latest app code.
This also makes it easier for the testing team to receive test builds without continuously
bugging the developers.
Lint and tests with coverage should run whenever a pull request is raised.
Every time changes are merged to the master branch, we want the CI process to build
the code, run lint and tests, and then build and publish apps to be distributed to the
internal testing team.
Each test build should have the same version and build number for Android and iOS
builds, corresponding to each version of the code.
An email should be sent out to all testers whenever a new build is available.
Developers should also have the option to manually build on their local machines
quickly.
Ability to pass environment variables to the scripts so that we can generate different
builds for different development environments like staging, preprod, and production.
The following chapters would show step by step on how to achieve these.
123
Android Build setup
BUILD_NAME - The name that will be used by testers to identify the build, for example:
'1.1.1', '1.0-alpha', etc.
BUILD_NUMBER - A unique integer number identifying the build. This is used by
Android to identify which build is the updated build. This should be an integer number,
for example, 1, 111, 111, etc.
ANDROID_APP_ID - This is the unique app identifier which is used to identify the app
uniquely in the Google Play Store or can be used to identify if the build is dev, preprod
or prod. These app ids may look like this: com.app.notetaker-dev, com.app.notetaker-
alpha.
ANDROID_KEYSTORE_FILE - This is the keystore file used to sign the app.
ANDROID_KEYSTORE_PASSWORD - This is the keystore password used while
creating the keystore.
ANDROID_KEY_ALIAS - This is the alias used to create the keystore.
ANDROID_KEY_PASSWORD - This is the password set for the key.
Release variants
Ideally, every app has three release variants just like a typical backend application:
Dev build - The app which connects to the staging/dev backend environment. This can
also include additional libraries like TestFairy.
Preprod build - The app which points to the preprod backend environment. This is
usually very similar, if not identical to the production app.
Prod build - The final apk which should be released to the Play Store.
Hence, we would need three different key stores for three different variants.
124
Android Build setup
Note these down somewhere and keep the keystore file safe.
Once you publish the app on the Play Store, you will need to republish your app under
a different package name (losing all downloads and ratings) if you want to change the
signing key at any point. So backup your keystore and don't forget the passwords.
Make sure the keystore file is git ignored so that you don't check the file into git
scripts/android/builder.sh
#!/bin/bash
set -e
cur_dir=`dirname $0`
android/app/build.gradle
125
Android Build setup
...
def enableProguardInReleaseBuilds = false
...
...
def appID = System.getenv("ANDROID_APP_ID") ?: "com.notetaker"
def vCode = System.getenv("BUILD_NUMBER") ?: "0"
def vName = System.getenv("BUILD_NAME") ?: "1.0.local"
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
applicationId appID
minSdkVersion 16
targetSdkVersion 22
versionCode Integer.parseInt(vCode)
versionName vName
ndk {
abiFilters "armeabi-v7a", "x86"
}
}
...
...
...
signingConfigs {
release {
if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) {
storeFile file(MYAPP_RELEASE_STORE_FILE)
storePassword MYAPP_RELEASE_STORE_PASSWORD
keyAlias MYAPP_RELEASE_KEY_ALIAS
keyPassword MYAPP_RELEASE_KEY_PASSWORD
}
}
}
buildTypes {
release {
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rule
s.pro"
signingConfig signingConfigs.release
}
}
...
...
...
1. clean
2. build a release apk
126
Android Build setup
Also, all the important parameters are passed via environment variables. This ensures that
we can:
2. Setup the environment variables in CI platform so that the CI can build the apk without
manual intervention. Keep in mind that typically every CI provides a unique build
number that you can pass to the script for BUILD_NUMBER.
Windows users
If you are running Windows 10 64bit or higher you can enable Ubuntu bash shell on your
systems and gain access to the full bash command line and run the script there. More on
that here: https://www.howtogeek.com/249966/how-to-install-and-use-the-linux-bash-shell-
on-windows-10/
Alternatively, you could install Cygwin on your system and run the scripts mentioned.
127
iOS Build setup
Note: Builds work only for Mac users. Since Apple requires that all the builds be
made on Xcode itself, iOS apps can only be built on a Mac machine.
BUILD_NAME - The name that will be used by testers to identify the build, for example:
'1.1.1', '1.0-alpha', etc.
BUILD_NUMBER - A unique integer number identifying the build. This is used by iOS to
identify which build is the updated build. This should be an integer number. For
example: 1, 111, 111, etc.
IOS_APP_ID - This is the unique app identifier which is used to identify the app
uniquely in the App Store or it can be used to identify if the build is dev, preprod or prod.
App ids may look like this: com.app.notetaker-dev, com.app.notetaker-alpha.
IOS_CERTIFICATE - This is the certificate file used to sign the app.
IOS_CERTIFICATE_KEY - This is the password used while creating the certificate.
IOS_PROVISION_PROFILE - This is the provision profile needed to build the app. This
file mentions the capabilities/devices that are allowed to run the app.
IOS_EXPORT_OPTIONS_PLIST - This is the options file needed to specify parameters
for the build.
IOS_SCHEME - The scheme which should be used to build the IPA. Typically, we will
have different schemes per environment. For example, we can have a local, a preprod
and a production scheme.
IOS_CONFIGURATION - This is the setting which specifies if the build is DEBUG or
RELEASE.
PROJECT_NAME - This is the name of the project. For example, if your project name
inside ios folder says SampleProject.xcworkspace or SampleProject.xcodeproj, then
PROJECT_NAME=SampleProject .
Release variants
Ideally, every app has three release variants just like a typical backend application:
Dev build - The app which connects to the staging/dev backend environment. This can
also have additional libraries like TestFairy.
Pre-Prod build - The app which points to the preprod backend environment. This is
usually very similar, if not identical to the production app.
128
iOS Build setup
Prod build - The final IPA which should be released to the App Store.
Hence, we would need three different schemes for three different variants.
1. Getting the certificates and provisioning profiles from the Apple developer account.
2. Adding the certificate to the default keychain and placing the provisioning profile at the
location ~/Library/MobileDevice/Provisioning\ Profiles/
3. Archiving the project - Think of it as an executable zip of the project that can be run
using Xcode.
4. Exporting the IPA - Think of it as exporting the archive to a format recognized by an
iPhone.
scripts/ios/exportOptions/exportOptions-dev.plist
129
iOS Build setup
Make sure you put all the above files in the gitignore.
4. Create the script: The below script will create a new keychain ios-build and will store
the certificate in the keychain. Also, it will make ios-build the default keychain so that
Xcode picks up the certificate from it. Then it will copy the provisioning profile to the
correct directory so that Xcode can pick it up.
scripts/ios/keychain.sh
130
iOS Build setup
#!/bin/bash
set -e
cur_dir=`dirname $0`
if [ $keychainCount == 0 ] ; then
echo "Create ios-build keychain"
# Create a custom keychain
security create-keychain -p "ios-build-password" ios-build.keychain
fi
# Add it to the list
security list-keychains -d user -s ios-build.keychain
echo "Making the ios-build keychain default, so xcodebuild will use it for signing"
5. Create the script that does the build. This script will run the xcodebuild to first archive
the project. Then it will generate the IPA file which can be used for installing the app
onto an iPhone.
scripts/ios/builder.sh
131
iOS Build setup
#!/bin/bash
set -e
cur_dir=`dirname $0`
WORKING_DIR=`pwd`;
cd $cur_dir/../../ios
echo "Setting version to ${BUILD_NUMBER}, ${BUILD_NAME}"
xcrun agvtool new-version -all ${BUILD_NUMBER}
xcrun agvtool new-marketing-version ${BUILD_NAME}
cd $WORKING_DIR
# or if you are not using xcodeproj and are using xcworkspace to build.. use the b
elow code:
#SIGN
# Issue : "No applicable devices found."
# Fix: https://stackoverflow.com/questions/39634404/xcodebuild-exportarchive-no-ap
plicable-devices-found
unset GEM_HOME
unset GEM_PATH
2. Now, run the build script to build the ipa file like this: PROJECT_NAME='NoteTaker'
IOS_APP_ID='com.notetaker.app.ios' BUILD_NUMBER=11 BUILD_NAME=1.1.1 IOS_SCHEME='local'
IOS_CONFIGURATION='RELEASE' IOS_EXPORT_OPTIONS_PLIST='exportOptions-dev.plist' sh
132
iOS Build setup
./scripts/ios/builder.sh
The build should take a couple of minutes and you can find the final ipa file at
ios/build/Products/IPA/
133
SVG Icons using react-native-vector-icons
Icons in react-native
Those who come from a web development background know the power of SVG icons. We
prefer using SVGs (for icons, images) because of the following reasons:
All the fonts are scalable and you can style them just like SVG.
It returns a React component which accepts name, etc. as prop. After integration, the
usage will be as simple as <Icon name="rocket" size={30} color="#900" />
You can pass custom style.
It also supports a couple of other components which might be useful. Example: button
with an icon ( <Icon.Button /> )
You can also create your own icon set if you want to use custom icons.
Installation is a 2 step process if you wish to use the icons provided by FontAwesome or
other similar font libraries. ( yarn add and react-native link )
If you wish to use custom fonts (made in SVG), please read the next chapter.
134
Custom Icon set
icons which come bundled with it or if you want to add your own icons. It supports Fontello
and IcoMoon to create custom fonts. We used IcoMoon to convert our SVGs to a config
which is readable by the library.
or
Create a resources folder where we will keep our custom font file (.ttf).
That's it, you are done with the setup. Now we need to get the TTF file and place it in the
resources/fonts folder that we just created.
2. Remove the current set (if there is one) and create a new empty set and give your
preferred name (remember to give the same name everywhere).
135
Custom Icon set
4. Select the files which you want to export. Select all if you want to export all the icons.
136
Custom Icon set
5. After the selection, click Generate Font. This will download a zip file to your system.
6. The zip file will contain a selection.json file and a fonts folder containing a .ttf file. We
only need these two files to use fonts in react-native.
7. Put the font file (.ttf) in the resources/fonts folder and add the following script to the
package.json:
137
Custom Icon set
"rnpm": {
"assets": [
"resources/fonts"
]
}
This script will copy the font files to both Android and iOS folders. After this, whenever
we want to update the fonts, we will do react-native link react-native-vector-icons
and the fonts will be copied/updated automatically to both Android and iOS projects.
8. Put the JSON file (selection.json) in your app and create a file called CustomIcon.js.
Import the selection.json in CustomIcon.js.
9. That's it! To use a font simply import the file as a React component and pass the icon
name and size (optional) or even style.
It is not recommended to change the filename of .ttf font file after the setup/native linking.
The filename gets written in project.pbxproj and Info.plist and the file gets copied to
android/app/src/main/assets/fonts/ once you run the link command. If you wish to change
the filename, you would need to take care of changing the above 2 files as well, and
removing the unused icon from the android folder which might cause problems if not done
properly.
138
Custom Icon set
You can easily change/delete the contents of the font file. The tool only needs the
selection.json file, which defines the font configuration. The steps for the same are as
follows:
3. Edit/delete the icon using the tools on top. (Adding an icon is the same as step 3
mentioned above)
4. After the editing is complete, generate a new font file by following the steps 5 and 6
mentioned above. Once you have the new font file and the new selection.json file, place
them in their appropriate locations in the app and do react-native link react-native-
vector-icons .
This will let us convert any SVG image to a font which is scalable, platform
independent and easy to style. What else can you ask for, right?
139
Custom Icon set
140
Internationalization
Internationalization
For application developers, internationalizing an application means abstracting all of the
strings and other locale-specific things like date, currency etc.
We will create a file called en.js and hi.js containing all the strings in a flat JSON
format. Our presentational components will import the strings from one of these files
depending on the current language. Both the language files will contain the same keys at
any point in time.
Note that the only feature this module gives us is setting the device locale as default app
locale. If you do not need this feature, you may skip installing the native module and use the
i18n-js module instead.
3. Create language js files containing language strings in flat JSON format. We will follow
the convention of <PAGENAME>_contentTypeInCameCase . The reason for this convention is
that it will be easier for the testers/devs to know if a translation is missing considering
we will be having guess mode enabled (explained later).
Example: config/language/en.js
141
Internationalization
export default {
HOME_noteTitle: 'Note Title',
HOME_pleaseTypeYourNote: 'Please type your note below',
HOME_startTakingNotes: 'Start taking notes',
HOME_save: 'Save',
HOME_characters: 'chacters',
ABOUT_us: 'About Us',
ABOUT_theApp: 'About the app',
ABOUT_theCreators: 'About the Creators',
ABOUT_theAppDesc: 'About the app',
ABOUT_theCreatorsDesc: 'About the Creators',
};
4. Create a utility file which will export a translate function and some other utility functions.
utils/language.utils.js
142
Internationalization
import I18n from 'react-native-i18n'; // You can import i18n-js as well if you do
n't want the app to set default locale from the device locale.
import en from '../config/language/en';
import hi from '../config/language/hi';
I18n.translations = {
hi,
en
};
/* translateHeaderText:
screenProps => coming from react-navigation (defined in app.container.js)
langKey => will be passed from the routes file depending on the screen.(We will
explain the usage later int the coming topics)
*/
export const translateHeaderText = (langKey) => ({screenProps}) => {
const title = I18n.translate(langKey, screenProps.language);
return {title};
};
5. Setup is done. Now let's import the translations from the utility file instead of hardcoding
it in the components.
Instead of
we will write
143
Internationalization
After following the above 5 steps, we will have an internationalization framework setup and
all our strings coming from a single language config file.
Let's change the defaultLocale of the app from en to hi in language.utils.js and see if
our framework setup works.
TADA! Our setup works like a charm and we can see everything in Hindi
144
Internationalization
145
Adding language toggle feature
Let's start by adding a toggle button in our Home component. The button should show the
current language and should toggle the language onPress.
For now, we will create a button in Home.component.js which will accept currentLanguage
and toggleLanguage functions as props.
Home.component.js
reducers/userPreferences.reducer.js
146
Adding language toggle feature
2. We need to make sure that the language in Redux store is always in sync with the
language in i18n module. In other words, we need an action creator which changes
the language in both i18n module and Redux store.
We will use redux-thunk middleware for this. You can use redux-saga to achieve this as
well. To know more about thunks, go here.
redux/thunks/index.thunks.js
Home.page.js
147
Adding language toggle feature
That's all! You should now be able to see a toggle button on the Home page showing the
current language and be able to toggle the language on press.
148
Adding language toggle feature
Don't worry about the header text not being changed. We will fix it in the coming chapter.
149
Integration with react-navigation
The current language is Hindi . Everything changes in the UI except the header.
Answer: Yes we are, but how will the router know that it has to re-render?
App.container.js
150
Integration with react-navigation
However, change in screenProps will not cause re-rendering of Router. Have a look at the
issue here to know why.
To fix this, we would use a function instead of an object for our navigationConfig (have a look
at Header Configuration topic here). ScreenProps are passed to this function and we can
use them to get the currentLanguage and then translation.
Instead of
navigationOptions: {
title: translate('HOME_startTakingNotes')
}
we will do
Doing this will cause the header text to change dynamically based on the currentLanguage.
Let's create a utility for this so that we don't have to rewrite this function.
language.utils.js
Now let's use this utility in all our routes file. Example usage:
routes/index.js
navigationOptions: translateHeaderText('HOME_startTakingNotes')
151
Integration with react-navigation
152
Custom Native Modules
Solution
The beauty of React Native is that it is designed to be a bridge between native code and
web technologies. Thus, all you need to do is simply integrate the native iOS and Android
SDKs into the respective native projects and expose the methods and variables via a
React Native module called: NativeModules .
The module which talks to the native libs and can be accessed via JS is called Wrapper
module. We will discuss how to create custom wrapper modules that can be used to invoke
native code from Javascript in the following chapters.
On a side note, if the requirement was to integrate a native module that doesn't need to
interact with the Javascript layer then we can safely integrate the native modules in the
respective native projects for iOS and Android and not create a React Native bridge. One
such use case can be crash reporters which need to send a crash report if an app crashes.
Here you only integrate the crash reporter on the native code base of iOS and Android and
not expose any method to the Javascript side. The initialization would also happen when the
native app starts.
153
Android Native Modules
Before we continue, remember that if you want to write code for Android, you need to open
the android project in Android Studio. This is because Android Studio is built for Android
development and it will help you resolve all the trivial errors that otherwise would take too
much time to resolve.
custom native module file. The custom native module class should extend
ReactContextBaseJavaModule . After this, we will have to implement the getName()
method.
The getName method basically contains the name by which the module will be exported
to the JS.
In order to expose a method from a native Java module to Javascript, just write a
method and add @ReactMethod annotation on top of it.
154
Android Native Modules
android/app/src/main/java/com/notetaker/device/DeviceModule.java
package com.notetaker.device;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
NativeModules has a key named 'Device'. This is basically the same name we exported
using the method getName . And getDeviceName is exported because of @ReactMethod .
3. But creating a module file is not enough. Before a native module can be used we need
to register the module. To do this we create another Java class
155
Android Native Modules
android/app/src/main/java/com/notetaker/device/DevicePackage.java
package com.notetaker.device;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContex
t) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
//We import the module file here
modules.add(new DeviceModule(reactContext));
return modules;
}
// Backward compatibility
public List<Class<? extends JavaScriptModule>> createJSModules() {
return new ArrayList<>();
}
}
4. The last step in the registration process is to instantiate our DevicePackage class.
android/app/src/main/java/com/notetaker/MainApplication.java
156
Android Native Modules
...
...
import com.notetaker.device.DevicePackage;
...
...
...
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
...
...
new DevicePackage() //Add your package here
);
}
};
...
...
...
app/index.js
157
Android Native Modules
Note that if you try to build for iOS now, the build will fail as we have not implemented
the Device module in iOS yet.
158
iOS Native Modules
Before we continue, just a reminder that if you want to write code for iOS, please open the
iOS project on Xcode. This is because Xcode is built for iOS development and it will help
you resolve the trivial errors that otherwise would take up too much time.
2. Create a header file Device.h by following the steps File -> New -> File -> Header
File and then name the file Device.h and choose the targets. Create a new folder
ios/Device/Device.h
159
iOS Native Modules
#import <React/RCTBridgeModule.h>
4. Now let's create the corresponding implementation file Device.m in the same location.
ios/Device/Device.m
#import "Device.h"
@implementation Device
RCT_EXPORT_MODULE();
@end
ios/Device/Device.m
160
iOS Native Modules
#import "Device.h"
#import <UIKit/UIKit.h>
@implementation Device
//export the name of the native module as 'Device' since no explicit name is menti
oned
RCT_EXPORT_MODULE();
@end
NativeModules has a key named 'Device'. This is basically the same name exported by
RCT_EXPORT_METHOD.
app/index.js
Just add
161
iOS Native Modules
162
References
References
http://reactkungfu.com/2015/07/why-and-how-to-bind-methods-in-your-react-
component-classes/
https://medium.com/differential/managing-configuration-in-react-native-cd2dfb5e6f7b
http://slides.com/rahulgaba/react-native
https://medium.com/the-react-native-log/comparing-the-performance-between-native-
ios-swift-and-react-native-7b5490d363e2
https://www.youtube.com/watch?v=8qCociUB6aQ
https://github.com/jondot/awesome-react-native
https://blog.expo.io/good-practices-why-you-should-use-javascript-whenever-possible-
with-react-native-26478ec22334
https://blog.joinroot.com/mounting-react-native-components-with-enzyme-and-jsdom/
163
The End
164