Core APIs, Part 1
So far we've primarily used React components to interact
with the underlying native APIs -- we've used components
like View
, Text
, and
Image
to create native UI elements on the screen.
React provides a simple, consistent interface for APIs which create visual components. Some APIs don't create UI components though: for example, accessing the Camera Roll, or querying the current network connectivity of the device.
React Native also comes with APIs for interacting with these
non-visual native APIs. In contrast with components, these
APIs are generally imperative functions: we must call
them explicitly at the right time, rather than returning
something from a component's render
function
and letting React call them later. React Native simply
provides us a JavaScript wrapper, often cross-platform, for
controlling the underlying native APIs.
Building a messaging app
In this chapter, we'll build the start of a messaging app (similar to iMessage) that gives us a tour of some of the most common core APIs. Our app will let us send text, send photos from the camera roll, and share our location. It will let us know when we are disconnected from the network. It will handle keyboard interactions and the back button on Android.
To try the completed app:
-
On Android, you can scan the following QR code from within the Expo app:
-
On iOS, you can navigate to the
messaging/
directory within our sample code folder and build the app. You can either preview it using the iOS simulator or send the link of the project URL to your device as we mentioned in the first chapter.
We can send text messages, images, and maps:
We can choose images from our device camera roll:
And we can view images fullscreen:
We'll use the following APIs:
-
Alert
- Displays modal dialog windows for simple user input -
BackHandler
- Controls the back button on Android -
CameraRoll
- Returns images and videos stored on the device -
Dimensions
- Returns the dimensions of the screen -
Geolocation
- Returns the location of the device, and emits events when the location changes -
Keyboard
- Emits events when the keyboard appears or disappears -
NetInfo
- Returns network connectivity information, and emits events when the connectivity changes -
PixelRatio
- Translates from density-independent pixels to density-dependent pixels (more detail on this later) -
StatusBar
- Controls the visibility and color of the status bar
We'll just be focusing on the UI, so we won't actually send messages, but we could connect the UI we build to a backend if we wanted to use it in a production app.
Initializing the project
Just as we did in the previous chapters, let's create a new app with the following command:
$ expo init messaging --template blank@sdk-33 --yarn
Once this finishes, navigate into the
messaging
directory.
In this chapter we'll create the
utils
directory ourselves, so there's no
need to copy over the sample code. We do however want to
install a few additional node modules. Install
expo-constants
, expo-permissions
,
and react-native-maps
using the following
command:
$ yarn add expo-constants expo-permissions react-native-maps
The app
Let's start by setting up the skeleton of the app.
We'll do this in App.js
. After that,
we'll build out the different parts of the screen, one
component at a time. We'll tackle keyboard handling last,
since that's the most difficult and intricate.
We'll follow the same general process as in the previous chapters: we'll start by breaking down the screen into components, building a hardcoded version, adding state, and so on.
The app's skeleton
If we look at the app from top to bottom, these are the main sections of the UI:
-
Status
- The device generally renders a status bar, the horizontal strip at the top of the screen that shows time, battery life, etc -- but in this case, we'll augment it to show network connectivity more prominently. We'll create our own component,Status
, which renders beneath the device's status bar. -
MessageList
- This is where we'll render text messages, images, and maps. -
Toolbar
- This is where the user can switch between sending text, images, or location, and where the input field for typing messages lives. -
Input Method Editor (IME) - This is where we can render a
custom input method, i.e. sending images. We'll build
an image picker component,
ImageGrid
, and use it here. Note that the keyboard is rendered natively by the operating system, so we will trigger the keyboard to appear and disappear at the right times, but we won't render it ourselves.
In this chapter we'll be building top-down. We'll
start by representing the message list, the toolbar, and the
IME with a placeholder View
. By starting with a
rough layout, we can then create components for each section,
putting each one in its respective View
. Each
section will be made up of a few different components.
Open App.js
and add the following skeleton:
import { StyleSheet, View } from 'react-native';
import React from 'react';
export default class App extends React.Component {
renderMessageList() {
return (
<View style={styles.content}></View>
);
}
renderInputMethodEditor() {
return (
<View style={styles.inputMethodEditor}></View>
);
}
renderToolbar() {
return (
<View style={styles.toolbar}></View>
);
}
render() {
return (
<View style={styles.container}>
{this.renderMessageList()}
{this.renderToolbar()}
{this.renderInputMethodEditor()}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
},
content: {
flex: 1,
backgroundColor: 'white',
},
inputMethodEditor: {
flex: 1,
backgroundColor: 'white',
},
toolbar: {
borderTopWidth: 1,
borderTopColor: 'rgba(0,0,0,0.04)',
backgroundColor: 'white',
},
});
When you save App.js
, the app should reload on
your device and you'll see the following:
Awesome, a blank screen with a small gray line through the
middle! Now we can start building out the different sections
of the screen. The App
component will orchestrate
how data is populated, and when to hide or show the various
input methods -- but first, we need to start creating the
different components in the UI.
Now's a good time to create a new directory,
components
, within our main
messaging
directory. We'll put the UI
components we build in the components
directory.
Network connectivity indicator
Since we're building a messaging app, network connectivity is relevant at all times. Let's let the user know when they've lost connectivity by turning the status bar red and displaying a short message.
StatusBar
Many apps display the default status bar, but sometimes we want to customize the style, e.g. turning the background red.
The status bar works a little differently on iOS and Android. On iOS the status bar background is always transparent, so we can render content behind the status bar text. On Android, we can set the status bar background to transparent, or to a specific solid color. If we use a transparent status bar, we can render content behind it just like on iOS -- unlike on iOS, by default the status bar text is white and there's typically a semi-transparent black background. If we choose a solid color status bar, our app's content renders below the status bar, and the height of our UI will be a little smaller. In our app, we'll use a solid color status bar, since this will let us customize the color.
To use a solid color status bar, we need to open up
app.json
and add the following to the
expo
object (although you can skip this if
you're not using an Android):
"expo": {
// ...
"androidStatusBar": {
"barStyle": "dark-content",
"backgroundColor": "#FFFFFF"
}
}
Let's restart the packager with
npm run start
to make sure this change takes
effect.
If we had used
react-native init
instead ofexpo init
, we wouldn't need to do this. Expo handles the status bar specially. You can check out the guide on configuring the status bar for more detail.
On both platforms, we can set the status bar text color by
using the built-in StatusBar
component and
passing a barStyle
of either
light-content
(white text) or
dark-content
(black text).
There are two different ways we can use
StatusBar
: imperatively and as a component. In
this example we'll use the component approach.
Create a new file Status.js
in the
components
directory now.
Status
styles
Let's first start with the background styles. We need to
create a View
that sits behind the text of the
status bar -- on iOS, rendering the background color of the
status bar is our responsibility, since the operating system
only renders the status bar text.
We'll have two visual states: one where the user is
connected to the network, and one where the user is
disconnected. We'll set the color for each state in
render
, so let's start with the base style
for the status bar:
import Constants from 'expo-constants';
import { StyleSheet } from 'react-native';
// ...
const statusHeight =
(Platform.OS === 'ios' ? Constants.statusBarHeight : 0);
const styles = StyleSheet.create({
status: {
zIndex: 1,
height: statusHeight,
},
// ...
});
The base style status
will give the
View
its height. The View
will have
the same height regardless of whether this component is in the
connected or disconnected state. We use a
zIndex
of 1
to indicate that this
View
should be drawn on top of other content --
this will be relevant later, since we're going to render
a ScrollView
beneath it.
Depending on the component's state
,
we'll then pass a style object containing a background
color (in addition to passing the status
style).
We'll store the network connectivity status in component
state
as state.isConnected
. If
state.isConnected
is true
then the
device is connected to the internet, and if it's
false
then the device is disconnected. We'll
set isConnected
to true by default, since
that's the most likely case, and since it would be a poor
user experience to show a connectivity message when it's
not needed.
Let's try rendering this background View
.
import Constants from 'expo-constants';
import {
NetInfo,
Platform,
StatusBar,
StyleSheet,
Text,
View,
} from 'react-native';
import React from 'react';
export default class Status extends React.Component {
state = {
isConnected: null,
};
// ...
render() {
const { isConnected } = this.state;
const backgroundColor = isConnected ? 'white' : 'red';
if (Platform.OS === 'ios') {
return (
<View style={[styles.status, { backgroundColor }]}></View>
);
}
return null; // Temporary!
}
}
// ...
Notice how we use an array for the View
to apply
two styles: the status
style, and then a style
object containing a different background color depending on
whether we're connected to the network or not.
Let's save Status.js
and import it from
App.js
so we can see what we have so far.
We can now go ahead and render our new
Status
component from App
:
// ...
import Status from './components/Status';
export default class App extends React.Component {
// ...
render() {
return (
<View style={styles.container}>
<Status />
{this.renderMessageList()}
{this.renderToolbar()}
{this.renderInputMethodEditor()}
</View>
);
}
// ...
}
// ...
We shouldn't see anything yet... but to verify that
everything is working, you can temporarily set
isConnected: 'false'
in the
state
of Status
. This will show a
red background behind the status bar text.
Doing this, we should see:
Using StatusBar
The black text on the red background doesn't look very
good. This is where the StatusBar
component comes
in. Let's import it from react-native
and
render it within our View
.
import Constants from 'expo-constants';
import { StatusBar, StyleSheet, View } from 'react-native';
import React from 'react';
export default class Status extends React.Component {
state = {
isConnected: true,
};
// ...
render() {
const { isConnected } = this.state;
const backgroundColor = isConnected ? 'white' : 'red';
const statusBar = (
<StatusBar
backgroundColor={backgroundColor}
barStyle={isConnected ? 'dark-content' : 'light-content'}
animated={false}
/>
);
if (Platform.OS === 'ios') {
return (
<View style={[styles.status, { backgroundColor }]}>
{messageContainer}
</View>
);
}
return null; // Temporary!
}
}
Here we set barStyle
to
dark-content
if we're connected (black text
on our white background) and light-content
if
we're disconnected (white text on our red background). We
set backgroundColor
to set the correct background
color on Android. We also set animated
to
false
-- since we're not animating the
background color on iOS, animating the text color won't
look very good.
Note that the StatusBar
component doesn't
actually render the status bar text. We use this component to
configure the status bar. We can render the
StatusBar
component anywhere in the component
hierarchy of our app to configure it, since the status bar is
configured globally.
We can even render StatusBar
in multiple
different components, e.g. we could render it from
App.js
in addition to Status.js
. If
we do this, the props
we set as configuration are
merged in the order the components mount. In practice it can
be a bit hard to follow the mount order, so it may be easier
to use the imperative API if you find yourself with many
StatusBar
components (more on this later).
Message bubble
Since the red status bar alone doesn't indicate anything about network connectivity, let's also add a short message in a floating bubble at the top of the screen.