The latest version of this text can be found at https://info340.github.io/.
Chapter 23 Special Topics: React Native
As a special topic, this chapter provides a brief introduction to React Native, a framework for using React to build mobile applications (e.g., apps for Android or iOS). The React Native framework provides a set of build tools that allow you to compile React code (written in JavaScript) into native mobile code (Java for Android; Objective-C for iOS). This allows you to utilize your existing knowledge of client-side web development to also create mobile apps as if you had written them in their normal development language! Moreover, the same React code can be converted into apps for both iOS and Android (as well as for the web), with you only needing to adjust any platform-specific features. Three platforms for the price of one!
- This chapter’s explanation mirrors the official tutorial for React Native; see that for more details.
23.1 Getting Setup
The easiest way to start a new React Native application is to use the create-react-native-app
program. This works almost identically to the create-react-app
program you know and love, except it will scaffold you a React Native application and provide build scripts utilizes for testing and developing native apps.
To use this program install it globally, and then execute it to create a new React Native project in the current folder:
create-react-native-app MyNativeApp
This scaffolding will include a number of configuration files (see the User Guide for details), but you’ll mostly be interested in App.js
, whose default export will be the “root” component of your application (mobile apps are usually designed around a View component that is shown, rather than an index.js
style script to execute).
create-react-native-app
doesn’t currently work with npm
version 5.0
or greater. Until this is fixed, the best solution is to use yarn to install and manage react native applications. Note that installing yarn
may cause issues with npm
that you’d need to resolve: proceed with caution if under a deadline! Alternatively, you can downgrade to an older version of npm
using npm install -g npm@4
Running React Native Apps
There are a few different ways to “run” and test your application as you’re developing it, depending on the platform (Android or iOS) you’re targeting:
For either platform, you can test your React Native code on a physical device by using the Expo app. This is a separate mobile application that “connects” to a server run by the
create-react-native-app
build scripts, displaying updates to your app in real time. In order to use Expo, install the app on your phone, and then usenpm start
to begin the development server. The server will show a QR code in the command line that you can scan to have your phone connect to the server (assuming they are on the same wireless network); this will run your developed app on the phone, and even automatically refresh it when you save changes to the file!It is also possible to run your React Native app on virtual devices (e.g., emulators) for either platform. These are “virtual” phones that run on your own computer, allowing you to develop and test mobile apps without needing a specific device.
Note that you will need to have the appropriate development environment installed and set up for each platform: Android Studio for Android, and Xcode for iOS. Xcode only runs on MacOS.
For Android, you will need to create and start up an emulator with Android Studio: go to
Tools > Android > AVD Manager
to open up the Android Virtual Device Manager. You can then choose “Create Virtual Device…” in order to launch the wizard to specify a new emulator.You can then install and run an app on the emulator (using Expo) via the
npm run android
command. You can access the development menu on the device from the notification, or by hittingcmd-m
.For iOS, you can start up the Simulator program (it is found inside
Xcode.app/Contents/Developer/Applications
. If you right-click on theXcode.app
program and select “Show Package Contents”, you will be able to navigate to it). You will then be able to install and run an app on Simulator (using Expo) via thenpm run ios
command. You can access the development menu on the device by hittingcmd-d
.
Finally, Expo Snack allows you to develop, test, and run simple React Native apps entirely online! This can be a good way to test out the design or to share code snippets with others.
23.2 React Native Apps
Writing React Native code uses the same process and techniques as writing normal React code: you define Components that render
Views, which themselves are made up of more Components! These components can be passed in props and track state just like in React. In fact, if you look at the default App.js
file created by create-react-native-app
, you’ll see that it’s just basic React code.
However, instead of eventually rendering HTML elements (such as <div>
or <button>
), React Native apps render one of the framework’s built-in components. Each of these components is able to be “compiled” into a an appropriate “native” version. For example, a <View>
is compiled into a <div>
element on the web, an android.View
element on Android, and a UIView
element on iOS.
- Just as React components almost always return a
<div>
with some content nested inside of it, React Native components almost always return a<View>
with some content nested inside of it.<View>
elements are particularly important when styling your app; see below.
Other basic components include:
<Text>
components represent displayed text (similar in purpose to a<p>
, though they get compiled into inline<span>
elements on the web). All displayed text must be inside one of these elements; you can’t have a “text node” directly under a<View>
like you can in HTML.<Text>
elements also support cascading styling.<Image
> components are used to display images (similar to a<img>
). You specify which image to display by passing in asource
prop. This can be a remote reference (e.g.,https://domain.com/picture.png
), or a local image. In order to refer to a local image file, you should import it using Node’srequire()
method, specifying the path to the image file relative to the Component. This will the build tools to load the file as a data URI when rendering the image, and ensuring that the asset is packaged with the native app correctly.<Image source={ require('./path/to/picture.png') }/>
Additionally, you should always specify an
accessibilityLabel
prop specifying how the image should be read to screen readers (yes, they exist for mobile devices! Blind people also use phones).
Styling React Native
You customize the appearance of React Native components by specifying styling properties in their style
prop, similar to React inline styling. Note that Android and iOS don’t support CSS, so you don’t utilize className
in React Native.
However, because it’s often useful to organize styles into groups and give them labels, React Native provides a Stylesheet.create()
similar to that used by Aphrodite. Defining a stylesheet helps with code organization, as well as providing a more efficient native implementation (since you don’t need to duplicate style objects)—and efficient matters a lot more on resource-constrained mobile devices!
Although you specify style properties similar to CSS properties (e.g., with the same property names), React Native stylesheets do not use CSS! In particular, styles do not normally cascade: specifying the fontSize
for a <View>
will not cause that property to be applied to multiple nested <Text>
elements. This feature is missing because styling does not normally cascade in Android and iOS, and React Native can run more effectively by not needing to traverse the element tree to check if each and every property is defined by a parent. Moreover, this means that each component can be better isolated (developed as a stand-alone, drop-in piece of an application), because there is no chance of accidentally inheriting some styling.
- However, nested
<Text>
components will inherit from their parents as a convenience, allowing you to easily style parts of a text block (e.g., to make some text highlighted).
You can specify an element’s size by setting it’s width
and height
style properties. These properties should be assigned unitless numbers (you don’t include px
or rem
). The value measures the number of density-independent pixels, which is a pixel-value that scales based on the resolution (dots-per-inch, or dpi
) of the device. This allows the sizing to be consistent on “retina” displays.
Any elements that are not given a fixed size—as well as any element positioning—is primarily performed with Flexbox properties! The Flexbox framework allows you to provide a layout that will be consistent across different screen sizes. The “root” element (usually a <View>
) of a component is rendered as a “flex item”, but each <View>
can also be made into its own “flex container” in order to specify the direction (which defaults to a vertical “column”), size, or spacing of its content:
//declare a stylesheet
const styles = StyleSheet.create({
outer: {
flex: 1; //this View should fill vertical space
flexDirection: 'row'; //children should be layed out horizontally
}
inner: {
flex: 1; //take up equal extra space
}
})
//an example app
export default class App {
render() {
return (
<View style={styles.outer}>
<Text style={styles.inner}>Item 1</Text>
<Text style={styles.inner}>Item 2</Text>
<Text style={styles.inner}>Item 2</Text>
</View>
)
}
}
Interaction
React Native apps can be made interactive using a similar process to regular React apps: you specify an event handler which can be used to modify the state and re-render the component. However, The events that you listen for are slightly different with React Native. For example, a <Button>
element accepts an onPress
property (instead of onClick
):
<Button onPress={() => this.handlePress()}>Press me!</Button>
- Note that the callback is not passed any parameters, so you don’t have an
event
to work with.
In order to get text input, you use a <TextInput>
component (which is a lot like an <input type="text">
element in HTML). This element can be made to be a controlled input just like with normal React, though you would use the onChangeText
prop to listen for text changes (the callback function will be passed in the updated text):
<TextInput
placeholder="Type something!"
value={this.state.inputValue}
onChangeText={ (newText) => this.setState({inputValue:newText}) }
/>
Lists and Data
Information applications often need to display lists of data values (e.g., a list of tasks to complete). While it is possible to map()
an array variable to an array of <View>
elements to render, Android and iOS support more specific techniques that allow for better responsiveness and efficiency when displayed on mobile devices. For example, these components will automatically render only a “portion” of the list that is currently visible on the small screen, loading new Views into memory only when the user scrolls down to see them. This allows the user to smoothly “flick” through a list of items.
You can create such an optimized list in React Native by rendering a <FlatList>
component. This component takes two main properties: data
, which is an array of data values to “map” into Views; and renderItem
, which is a function that does the “mapping” (similar to the render
function for react-router
):
<FlatList
data={myDataArray}
renderItem={ (args) => <Text>{args.item.text}</Text> }
/>
Each element in the
data
array must contain akey
property that React uses to keep track of each item in the list (though you can pass a function that extracts a value as the key to thekeyExtractor
prop).The actual “data item” will be found in the
item
property of the callback function’s arguments (theindex
property will contain the index of that item). However, it’s common to use object destructuring to instead only pass the the specific property of the parameter object:function renderListItem({item}) { //param is `item = args.item` return <Text>{item.text}</Text>; //can access item directly } function renderWithIndex({item, index}) { //gets two params: args.item and args.index return <Text>{index} - {item.text}</TEXT>; }
If you would like to download data from the internet to display using React Native, you use the fetch()
API just like you’ve used in the web!
export default class App extends Component {
constructor(props){
super(props);
this.state = {data:[]}
}
componentDidMount() {
fetch(dataURI)
.then((res) => res.json())
.then((data) => {
this.setState({data: data});
})
.catch((err) => console.error)
}
render() {
return (
<View>
<FlatList data={this.state.data}
renderItem={({item}) => <Text>{item.text}</Text>}
/>
</View>
)
}
}
Overall, React Native is simply another way of building (but not implementing!) React applications, and makes heavy use of many of the modern frameworks and techniques (e.g., Flexbox, fetch) discussed throughout this course