Bootcamp Notes – Day 10 (Tues) – React Native: Week 4: Local Notifications, Social Sharing, and Picking an Image
Local Notifications, Social Sharing, and Picking an Image
Local Notifications
Notifications are a common way of drawing attention by adding an icon to the notification bar and a message to the notification drawer.
- Local notifications are generated from the client side
- Push notifications are generated from the server side (but still use the same mechanism to display the notification on the client.)
- The Expo Notifications APi provides access to both push (remote) notifications and local notifications.
- Local notifications can be sent immediately, or at a scheduled time, either once or on a recurring basis
- Example use case: An application that sends a notification to a user at a specific, recurring time, such as a reminder to exercise morning
- Or a calendar-based app that sends an event reminder, or a notification about something happening in the app.
EXPO NOTIFICATIONS APi
- Notifications.setNotificationHandler(..) – sets what to do with a notification that is received when the app is in foreground (default: does not show it)
- Notifications.scheduleLocalNotificationAsync(…) – schedules a local notification to fire immediately or in the future, optionally repeating at a given interval
- There are several other methods available from the Notifications APi, for generating push notifications, canceling a scheduled notification, etc
IN ADDITION
We will need to request permission to generate notifications from the underlying operating system:
- Notifications.getPermissionsAsync()
- Notifications.requestPermissionsAsync()
Local Notifications
Use the Expo Notifications API to generate local notifications, as well as get permissions from the device to send the local notifications.
- Install Expo Notifications: expo install expo-notifications@~0.3.3
ReservationComponent.js
Overview: Social Sharing
Mobile devices – and the internet itself – exists for us to share information with each other, and mobile devices have many ways to allow us to do that: email, text messaging, sharing information on social media accounts, etc
In the following exercises, we’ll look at two examples of how to share socially from within our app: the Expo SDK’s MailComposer APi to send emails, and React Native’s own Share APi to share to various apps.
The expo MailComposer APi allows you to fire up the MailComposer for your default email application on your device using a method called composeAsync. It takes one argument options which is an object with the following properties that can be set.
MailComposer.composeAsync(options)
options object:
- recipients (array) — an array of email addresses
- ccRecipients (array) — an array of email addresses
- bccRecipients (array) — an array of email addresses
- subject (string) — email subject
- body (string) — email body
- isHtml (boolean) — wheter the body contains HTML tags
- (for formatting purposes – not working perfectly in Android)
- attachments (array) — an array of file URi’s to attach
The composeAsync is an asynchronous method and it returns a promise that resolves to an object with the status property “sent“, “saved”, or “cancelled” for iOS. Android does not return this information and will always resolve to the status property “sent“.
REACT NATIVE SHARE APi
In the social sharing exercise, we will use the share APi from React Native. The share APi also has just one method and it is called share and it takes two arguments: content and options, which are both objects.
Share.share(content, options)
content object properties:
- message
- url
- title
(required to have message OR url, or both)
options object contains miscellaneous config properties such as tintColor for iOS
Sending Email
- Install and configure the Expo MailComposer API, along with a RNE Button component, to open the default email client on the device to a new email with the recipient email, subject, and body fields filled in.
- Install MailComposer: expo install expo-mail-composer
ContactComponent.js
Social Sharing
- Set up the React Native Share API to share information to social media accounts.
CampsiteInfoComponent.js
Picking an Image
Mobile devices today are typically equipped with a camera, as well as an image gallery app for images saved to the device. The camera can be used for more than taking photos, ex. scanning a barcode or QR code. Expo SKD offers serveral APi’s to access the camera as well as access and manipulate saved images.
EXPO IMAGEPICKER APi
Provides access to system Ui to select image/video from the device’s library, or take a new photo or video as your selection:
ImagePicker.launchImageLibraryAsync(options)
ImagePicker.launchCameraAsync(options)
options object properties include:
- mediaTypes: what type of media to pick, defaults to images
- allowsEditing: boolean value, whether to present image editor after it is picked, defaults to false
- aspect: the aspect ration to maintain if use is allowed to edit image
- quality: quality of image compression and more
ImagePicker.launchCameraAsync(options)
ImagePicker.launchImageLibraryAsync(options)
- The launchCameraAsync method will return a promise.
- This promise is fulfilled when the user either takes a photo and confirms the choice, or cancels the phot taking operation.
- Resolved promise: If a photo was taken, returns object with media uri, width and height, media type, and more
- If no photo was taken because the operation was cancelled, the resolve object will only have one property, a cancelled property set to true — can check for this and respond appropriately.
EXPO IMAGEMANIPULATOR APi
- use to alter an image
- apply transforms: resize, rotate, flip, or Crop
- configure compression level, format (JPEG or PNG)
- whether to include image data in BASE64 format
EXPO CAMERA APi
- Provides a React component that renders a preview for the device’s front or back camera
- You can adjust zoom, autofocus, white balance, flash mode
- You can take a photo, or start and stop video recording
- detect faces, scan a barcode, and more
Picking an Image
- Learn to use the ImagePicker API from Expo to obtain an image for use in the app by taking a photo with the mobile device’s camera.
- Implement tabbed navigation.
- Add a user registration form.
- Install expo-image-picker, react-navigation-tabs, and expo-permissions: expo install expo-image-picker@8 react-navigation-tabs expo-permissions@9
LoginComponent.js
import React, { Component } from 'react';
import { View, StyleSheet, ScrollView, Image } from 'react-native';
import { Input, CheckBox, Button, Icon } from 'react-native-elements';
import * as SecureStore from 'expo-secure-store';
import * as ImagePicker from 'expo-image-picker';
import * as Permissions from 'expo-permissions';
import { createBottomTabNavigator } from 'react-navigation-tabs';
import { baseUrl } from '../shared/baseUrl';
class LoginTab extends Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: '',
remember: false
};
}
static navigationOptions = {
title: 'Login',
tabBarIcon: ({tintColor}) => (
<Icon
name='sign-in'
type='font-awesome'
iconStyle={{color: tintColor}}
/>
)
}
handleLogin() {
console.log(JSON.stringify(this.state));
if (this.state.remember) {
SecureStore.setItemAsync(
'userinfo',
JSON.stringify({
username: this.state.username,
password: this.state.password
})
).catch(error => console.log('Could not save user info', error));
} else {
SecureStore.deleteItemAsync('userinfo').catch(
error => console.log('Could not delete user info', error)
);
}
}
componentDidMount() {
SecureStore.getItemAsync('userinfo')
.then(userdata => {
const userinfo = JSON.parse(userdata);
if (userinfo) {
this.setState({username: userinfo.username});
this.setState({password: userinfo.password});
this.setState({remember: true})
}
});
}
render() {
return (
<View style={styles.container}>
<Input
placeholder='Username'
leftIcon={{type: 'font-awesome', name: 'user-o'}}
onChangeText={username => this.setState({username})}
value={this.state.username}
containerStyle={styles.formInput}
leftIconContainerStyle={styles.formIcon}
/>
<Input
placeholder='Password'
leftIcon={{type: 'font-awesome', name: 'key'}}
onChangeText={password => this.setState({password})}
value={this.state.password}
containerStyle={styles.formInput}
leftIconContainerStyle={styles.formIcon}
/>
<CheckBox
title='Remember Me'
center
checked={this.state.remember}
onPress={() => this.setState({remember: !this.state.remember})}
containerStyle={styles.formCheckbox}
/>
<View style={styles.formButton}>
<Button
onPress={() => this.handleLogin()}
title='Login'
icon={
<Icon
name='sign-in'
type='font-awesome'
color='#fff'
iconStyle={{marginRight: 10}}
/>
}
buttonStyle={{backgroundColor: '#5637DD'}}
/>
</View>
<View style={styles.formButton}>
<Button
onPress={() => this.props.navigation.navigate('Register')}
title='Register'
type='clear'
icon={
<Icon
name='user-plus'
type='font-awesome'
color='blue'
iconStyle={{marginRight: 10}}
/>
}
titleStyle={{color: 'blue'}}
/>
</View>
</View>
);
}
}
class RegisterTab extends Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: '',
firstname: '',
lastname: '',
email: '',
remember: false,
imageUrl: baseUrl + 'images/logo.png'
};
}
static navigationOptions = {
title: 'Register',
tabBarIcon: ({tintColor}) => (
<Icon
name='user-plus'
type='font-awesome'
iconStyle={{color: tintColor}}
/>
)
}
getImageFromCamera = async () => {
const cameraPermission = await Permissions.askAsync(Permissions.CAMERA);
const cameraRollPermission = await Permissions.askAsync(Permissions.CAMERA_ROLL);
if (cameraPermission.status === 'granted' && cameraRollPermission.status === 'granted') {
const capturedImage = await ImagePicker.launchCameraAsync({
allowsEditing: true,
aspect: [1, 1]
});
if (!capturedImage.cancelled) {
console.log(capturedImage);
this.setState({imageUrl: capturedImage.uri});
}
}
}
handleRegister() {
console.log(JSON.stringify(this.state));
if (this.state.remember) {
SecureStore.setItemAsync('userinfo', JSON.stringify(
{username: this.state.username, password: this.state.password}))
.catch(error => console.log('Could not save user info', error));
} else {
SecureStore.deleteItemAsync('userinfo').catch(
error => console.log('Could not delete user info', error)
);
}
}
render() {
return (
<ScrollView>
<View style={styles.container}>
<View style={styles.imageContainer}>
<Image
source={{uri: this.state.imageUrl}}
loadingIndicatorSource={require('./images/logo.png')}
style={styles.image}
/>
<Button
title='Camera'
onPress={this.getImageFromCamera}
/>
</View>
<Input
placeholder='Username'
leftIcon={{type: 'font-awesome', name: 'user-o'}}
onChangeText={username => this.setState({username})}
value={this.state.username}
containerStyle={styles.formInput}
leftIconContainerStyle={styles.formIcon}
/>
<Input
placeholder='Password'
leftIcon={{type: 'font-awesome', name: 'key'}}
onChangeText={password => this.setState({password})}
value={this.state.password}
containerStyle={styles.formInput}
leftIconContainerStyle={styles.formIcon}
/>
<Input
placeholder='First Name'
leftIcon={{type: 'font-awesome', name: 'user-o'}}
onChangeText={firstname => this.setState({firstname})}
value={this.state.firstname}
containerStyle={styles.formInput}
leftIconContainerStyle={styles.formIcon}
/>
<Input
placeholder='Last Name'
leftIcon={{type: 'font-awesome', name: 'user-o'}}
onChangeText={lastname => this.setState({lastname})}
value={this.state.lastname}
containerStyle={styles.formInput}
leftIconContainerStyle={styles.formIcon}
/>
<Input
placeholder='Email'
leftIcon={{type: 'font-awesome', name: 'envelope-o'}}
onChangeText={email => this.setState({email})}
value={this.state.email}
containerStyle={styles.formInput}
leftIconContainerStyle={styles.formIcon}
/>
<CheckBox
title='Remember Me'
center
checked={this.state.remember}
onPress={() => this.setState({remember: !this.state.remember})}
containerStyle={styles.formCheckbox}
/>
<View style={styles.formButton}>
<Button
onPress={() => this.handleRegister()}
title='Register'
icon={
<Icon
name='user-plus'
type='font-awesome'
color='#fff'
iconStyle={{marginRight: 10}}
/>
}
buttonStyle={{backgroundColor: '#5637DD'}}
/>
</View>
</View>
</ScrollView>
);
}
}
const Login = createBottomTabNavigator(
{
Login: LoginTab,
Register: RegisterTab
},
{
tabBarOptions: {
activeBackgroundColor: '#5637DD',
inactiveBackgroundColor: '#CEC8FF',
activeTintColor: '#fff',
inactiveTintColor: '#808080',
labelStyle: {fontSize: 16}
}
}
);
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
margin: 10
},
formIcon: {
marginRight: 10
},
formInput: {
padding: 8
},
formCheckbox: {
margin: 8,
backgroundColor: null
},
formButton: {
margin: 20,
marginRight: 40,
marginLeft: 40
},
imageContainer: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-evenly',
margin: 10
},
image: {
width: 60,
height: 60
}
});
export default Login;
Additional Resources:
- Expo – Notifications
- MDN – Making asynchronous programming easier with async and await
- MDN – async function
- MDN – await operator
- developers.google.com – Async functions
- JavaScript.info – Async/Await
- Expo – MailComposer
- RNE – Button, Icon
- React Native – Share
- BAMTech – Sharing Content with React Native
- xpo – ImagePicker
- Expo – Permissions
- React Navigation – createBottomTabNavigator
- React Navigation – Guides – Tab navigation