├── .watchmanconfig
├── .gitignore
├── assets
├── images
│ ├── icon.png
│ └── splash.png
└── fonts
│ └── Inconsolata-Regular.ttf
├── .babelrc
├── constants
└── Colors.js
├── README.md
├── components
└── BodyText.js
├── app.json
├── package.json
├── api
└── registerForPushNotificationsAsync.js
├── navigation
├── RootNavigation.js
└── MainTabNavigator.js
├── App.js
└── screens
├── ChecklistScreen.js
├── CodeScreen.js
└── NotificationsScreen.js
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/**/*
2 | .expo/**/*
3 | .DS_Store
4 |
--------------------------------------------------------------------------------
/assets/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sameesiddiqui/expo-notifications-example/HEAD/assets/images/icon.png
--------------------------------------------------------------------------------
/assets/images/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sameesiddiqui/expo-notifications-example/HEAD/assets/images/splash.png
--------------------------------------------------------------------------------
/assets/fonts/Inconsolata-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sameesiddiqui/expo-notifications-example/HEAD/assets/fonts/Inconsolata-Regular.ttf
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["babel-preset-expo"],
3 | "env": {
4 | "development": {
5 | "plugins": ["transform-react-jsx-source"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/constants/Colors.js:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 | blue: '#2188FF',
4 | darkBlue: '#056ECF',
5 | offWhite: '#F8F8F9',
6 | offBlack: '#3a3a3a',
7 | gray: '#adadad'
8 | }
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # expo-notifications-example
2 | Demo app to show off local and push notifications in Expo.
3 | ---
4 | Wondering how to use notifications in Expo? Want to see them in action?
5 |
6 | [Check out the demo app](https://expo.io/@samee/expo-notifications) so you can play around with examples,
7 | understand how they work, and how to integrate them in your own app.
8 |
--------------------------------------------------------------------------------
/components/BodyText.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Text, StyleSheet } from 'react-native'
3 | import Colors from '../constants/Colors'
4 |
5 | export default class BodyText extends React.Component {
6 | render () {
7 | return (
8 |
9 | {this.props.children}
10 |
11 | )
12 | }
13 | }
14 |
15 | const styles = StyleSheet.create({
16 | text: {
17 | color: Colors.offBlack,
18 | fontSize: 16
19 | }
20 | })
21 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "expo-notifications",
4 | "description": "Local and push notifications using expo apis",
5 | "slug": "expo-notifications",
6 | "privacy": "public",
7 | "sdkVersion": "22.0.0",
8 | "version": "1.0.0",
9 | "orientation": "portrait",
10 | "primaryColor": "#2188FF",
11 | "icon": "./assets/images/icon.png",
12 | "splash": {
13 | "image": "./assets/images/splash.png",
14 | "resizeMode": "cover",
15 | "backgroundColor": "#2188FF"
16 | },
17 | "packagerOpts": {
18 | "assetExts": ["ttf"]
19 | },
20 | "ios": {
21 | "supportsTablet": true
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "expo-notifications",
3 | "version": "0.0.0",
4 | "description": "Local and push notifications using expo apis",
5 | "author": "Samee Siddiqui",
6 | "license": "",
7 | "main": "node_modules/expo/AppEntry.js",
8 | "scripts": {
9 | "test": "node ./node_modules/jest/bin/jest.js --watch"
10 | },
11 | "jest": {
12 | "preset": "jest-expo"
13 | },
14 | "dependencies": {
15 | "@expo/samples": "2.1.1",
16 | "expo": "^22.0.0",
17 | "react": "16.0.0-beta.5",
18 | "react-native": "https://github.com/expo/react-native/archive/sdk-22.0.1.tar.gz",
19 | "react-navigation": "^1.0.0-beta.15"
20 | },
21 | "devDependencies": {
22 | "jest-expo": "^22.0.0"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/api/registerForPushNotificationsAsync.js:
--------------------------------------------------------------------------------
1 | import { Notifications } from 'expo'
2 |
3 | // this is the expo push server, normally you wouldn't directly POST
4 | // here, instead if you need to send a notification immediately it's better
5 | // to use local notifications.
6 | // The typical case will POST to your server, and then there will be some
7 | // server-side code to handle sending the notification. You can see an example
8 | // of this in the code tab, or online on the expo push notification guide
9 | const PUSH_ENDPOINT = 'https://expo.io/--/api/v2/push/send'
10 |
11 | export default (async function registerForPushNotificationsAsync (title, body, data) {
12 | // Get the token that uniquely identifies this device
13 | let token = await Notifications.getExpoPushTokenAsync()
14 |
15 | // POST the token to our backend so we can use it to send pushes from there
16 | return window.fetch(PUSH_ENDPOINT, {
17 | method: 'POST',
18 | headers: {
19 | Accept: 'application/json',
20 | 'Content-Type': 'application/json'
21 | },
22 | body: JSON.stringify([
23 | {
24 | to: token,
25 | title,
26 | body,
27 | data
28 | }
29 | ])
30 | })
31 | })
32 |
--------------------------------------------------------------------------------
/navigation/RootNavigation.js:
--------------------------------------------------------------------------------
1 | import { Notifications, Permissions } from 'expo';
2 | import React from 'react';
3 | import { Alert } from 'react-native'
4 | import { StackNavigator } from 'react-navigation';
5 |
6 | import MainTabNavigator from './MainTabNavigator';
7 |
8 | const RootStackNavigator = StackNavigator(
9 | {
10 | Main: {
11 | screen: MainTabNavigator,
12 | },
13 | },
14 | {
15 | navigationOptions: () => ({
16 | headerTitleStyle: {
17 | fontWeight: 'normal',
18 | },
19 | }),
20 | }
21 | );
22 |
23 | export default class RootNavigator extends React.Component {
24 | componentDidMount() {
25 | this.getiOSNotificationPermission()
26 | // this._notificationSubscription = this._listenForNotifications();
27 | }
28 |
29 | componentWillUnmount() {
30 | this._notificationSubscription && this._notificationSubscription.remove();
31 | }
32 |
33 | render() {
34 | return ;
35 | }
36 |
37 | // android permissions are given on install
38 | async getiOSNotificationPermission () {
39 | const { status } = await Permissions.getAsync(Permissions.NOTIFICATIONS)
40 | if (status !== 'granted') {
41 | return
42 | }
43 | this._notificationSubscription = Notifications.addListener(this._handleNotification)
44 | }
45 |
46 | _handleNotification = ({ origin, data, remote }) => {
47 | console.log(remote)
48 | let type = remote ? 'Push' : 'Local'
49 | let info = `${type} notification ${origin} with data: ${JSON.stringify(data)}`
50 | setTimeout(() => Alert.alert('Notification!', info), 500)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/navigation/MainTabNavigator.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Platform } from 'react-native'
3 | import { Ionicons } from '@expo/vector-icons'
4 | import { TabNavigator, TabBarBottom } from 'react-navigation'
5 |
6 | import Colors from '../constants/Colors'
7 |
8 | import NotificationsScreen from '../screens/NotificationsScreen'
9 | import CodeScreen from '../screens/CodeScreen'
10 | import ChecklistScreen from '../screens/ChecklistScreen'
11 |
12 | export default TabNavigator(
13 | {
14 | Notifications: {
15 | screen: NotificationsScreen,
16 | },
17 | Code: {
18 | screen: CodeScreen,
19 | },
20 | Checklist: {
21 | screen: ChecklistScreen,
22 | },
23 | },
24 | {
25 | navigationOptions: ({ navigation }) => ({
26 | tabBarIcon: ({ focused }) => {
27 | const { routeName } = navigation.state
28 | let iconName
29 | switch (routeName) {
30 | case 'Notifications':
31 | iconName = 'ios-notifications'
32 | break
33 | case 'Code':
34 | iconName = 'ios-code'
35 | break
36 | case 'Checklist':
37 | iconName = 'ios-checkmark-circle-outline'
38 | }
39 | return (
40 |
46 | )
47 | },
48 | }),
49 | tabBarComponent: TabBarBottom,
50 | tabBarPosition: 'bottom',
51 | animationEnabled: false,
52 | swipeEnabled: false,
53 | }
54 | )
55 |
--------------------------------------------------------------------------------
/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Platform, StatusBar, StyleSheet, View } from 'react-native';
3 | import { AppLoading, Asset, Font } from 'expo';
4 | import { Ionicons } from '@expo/vector-icons';
5 | import RootNavigation from './navigation/RootNavigation';
6 |
7 | export default class App extends React.Component {
8 | state = {
9 | isLoadingComplete: false,
10 | };
11 |
12 | render() {
13 | if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) {
14 | return (
15 |
20 | );
21 | } else {
22 | return (
23 |
24 | {Platform.OS === 'ios' && }
25 | {Platform.OS === 'android' &&
26 | }
27 |
28 |
29 | );
30 | }
31 | }
32 |
33 | _loadResourcesAsync = async () => {
34 | return Promise.all([
35 | Asset.loadAsync([
36 | ]),
37 | Font.loadAsync([
38 | Ionicons.font,
39 | { 'terminal': require('./assets/fonts/Inconsolata-Regular.ttf') },
40 | ]),
41 | ]);
42 | };
43 |
44 | _handleLoadingError = error => {
45 | console.warn(error);
46 | };
47 |
48 | _handleFinishLoading = () => {
49 | this.setState({ isLoadingComplete: true });
50 | };
51 | }
52 |
53 | const styles = StyleSheet.create({
54 | container: {
55 | flex: 1,
56 | backgroundColor: '#fff',
57 | },
58 | statusBarUnderlay: {
59 | height: 24,
60 | backgroundColor: 'rgba(0,0,0,0.2)',
61 | },
62 | });
63 |
--------------------------------------------------------------------------------
/screens/ChecklistScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Text,
4 | View,
5 | StyleSheet,
6 | TouchableOpacity
7 | } from 'react-native'
8 | import { WebBrowser } from 'expo'
9 | import BodyText from '../components/BodyText'
10 | import Colors from '../constants/Colors'
11 |
12 | export default class ChecklistScreen extends React.Component {
13 | static navigationOptions = {
14 | title: 'Checklist',
15 | }
16 |
17 | render () {
18 | const localNotifUrl = 'https://docs.expo.io/versions/latest/sdk/notifications.html#local-notifications'
19 | const pushNotifUrl = 'https://docs.expo.io/versions/latest/guides/push-notifications.html'
20 |
21 | return (
22 |
23 |
24 |
25 | For in depth guides and full parameter lists check out the expo docs.
26 |
27 |
28 | this._openLink(localNotifUrl)}
30 | style={{marginTop: 15}}>
31 | Local Notifications:
32 |
33 |
34 |
35 | 1. Create your notification object (title and body are required){'\n'}
36 | 2. Create scheduling object (if you want to send it in the future){'\n'}
37 | 3. iOS only: ask for permission to send notifications{'\n'}
38 | 4. iOS only: set up listener to handle the notification{'\n'}
39 |
40 |
41 | this._openLink(pushNotifUrl)}
43 | style={{marginTop: 15}}>
44 | Push Notifications:
45 |
46 |
47 |
48 | 1. Get the unique token that identifies the device (use Notifications.getExpoPushTokenAsync){'\n'}
49 | 2. Send the push notification using the expo push api{'\n'}
50 | 3. Respond to the notification being received or selected by the user{'\n'}
51 |
52 |
53 |
54 | )
55 | }
56 |
57 | async _openLink (url) {
58 | await WebBrowser.openBrowserAsync(url)
59 | }
60 | }
61 |
62 | const styles = StyleSheet.create({
63 | container: {
64 | flex: 1,
65 | backgroundColor: Colors.offWhite,
66 | },
67 | contentContainer: {
68 | margin: 15
69 | },
70 | link: {
71 | fontSize: 20,
72 | color: Colors.blue
73 | }
74 | })
75 |
--------------------------------------------------------------------------------
/screens/CodeScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ScrollView, StyleSheet, Text, View } from 'react-native';
3 | import Colors from '../constants/Colors'
4 |
5 | export default class CodeScreen extends React.Component {
6 | static navigationOptions = {
7 | title: 'Code',
8 | };
9 |
10 | render() {
11 |
12 | return (
13 |
14 |
15 | {this._localInstructions()}
16 | {this._pushInstructions()}
17 |
18 |
19 | );
20 | }
21 |
22 | _localInstructions = () => {
23 | const notification = {
24 | title: 'this is your title!',
25 | body: 'and your body',
26 | data: { iWantData: 'yesPlease' },
27 | ios: {
28 | sound: true
29 | },
30 | android: {
31 | sound: true
32 | }
33 | }
34 |
35 | return (
36 |
37 |
38 | Scheduling local notifications
39 |
40 |
41 | Creating a local notification object is easy:
42 |
43 |
44 |
45 |
46 | {'const notification = ' + JSON.stringify(notification, null, '\t')}
47 |
48 |
49 |
50 |
51 | Then just schedule and send!
52 |
53 |
54 |
55 |
56 | {'const scheduleOptions = {' + '\n\t' + '"time": Date.now() + 5000\n}'}
57 | {`\n\n`}
58 | {'Notifications.scheduleLocalNotificationAsync(' + '\n\t' + 'notification' +
59 | '\n\t' + 'scheduleOptions\n)'}
60 |
61 |
62 |
63 | )
64 | }
65 |
66 | _pushInstructions = () => {
67 | const fetchObj = `{
68 | method: 'POST',
69 | headers: {
70 | Accept: 'application/json',
71 | 'Content-Type': 'application/json'
72 | },
73 | body: JSON.stringify({
74 | token: {
75 | value: token,
76 | },
77 | user: {
78 | username: 'Steve Jobs',
79 | }
80 | })
81 | }`
82 |
83 | const notification = `{
84 | to: usersPushToken,
85 | sound: 'default',
86 | body: 'This is a test notification',
87 | data: { withSome: 'data' },
88 | }`
89 |
90 | return (
91 |
92 |
93 | Push notification process
94 |
95 |
96 | Getting push notification tokens is simple too:
97 |
98 |
99 |
100 |
101 | let token = await Notifications.getExpoPushTokenAsync()
102 |
103 |
104 |
105 |
106 | Send the token to your server so you know the device to send the notification to.
107 |
108 |
109 |
110 |
111 | {'const yourServer = https://reallyCoolSite.com/push_token'}
112 | {`\n\n`}
113 | {'fetch(yourServer, ' + fetchObj + ')'}
114 |
115 |
116 |
117 |
118 | Lastly, use the token you stored and send a notification from your server code!
119 |
120 |
121 |
122 |
123 | {"import Expo from 'expo-server-sdk'"}
124 | {`\n`}
125 | {'let expo = new Expo()'}
126 | {`\n\n`}
127 | {'let notification = ' + notification}
128 | {`\n`}
129 | {'expo.sendPushNotificationsAsync(notification)'}
130 |
131 |
132 |
133 | )
134 | }
135 | }
136 |
137 | const styles = StyleSheet.create({
138 | container: {
139 | flex: 1,
140 | // paddingTop: 15,
141 | backgroundColor: Colors.offWhite,
142 | },
143 | codeBlock: {
144 | backgroundColor: '#eaeaea',
145 | flex: 1,
146 | marginTop: 10,
147 | marginBottom: 10
148 | },
149 | code: {
150 | fontFamily: 'terminal',
151 | padding: 10
152 | }
153 | });
154 |
--------------------------------------------------------------------------------
/screens/NotificationsScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | ScrollView,
4 | StyleSheet,
5 | Text,
6 | TouchableOpacity,
7 | View,
8 | TextInput
9 | } from 'react-native';
10 | import { WebBrowser, Notifications } from 'expo';
11 | import registerForPushNotificationsAsync from '../api/registerForPushNotificationsAsync'
12 | import Colors from '../constants/Colors'
13 |
14 | export default class NotificationsScreen extends React.Component {
15 | constructor (props) {
16 | super(props)
17 | this.state = {
18 | title: 'You didn\'t set a title!',
19 | body: 'Why not enter text into the body?',
20 | data: {
21 | thisIsYourData: 'you left this empty'
22 | }
23 | }
24 | this._localNotification = this._localNotification.bind(this)
25 | this._pushNotification = this._pushNotification.bind(this)
26 | }
27 |
28 |
29 | static navigationOptions = {
30 | // header: null,
31 | title: 'Notifications'
32 | };
33 |
34 | render() {
35 | return (
36 |
37 |
40 |
41 |
42 |
43 | Send yourself local and push notifications!
44 |
45 |
46 | Note: iOS will not display a notification when the app is in focus. You can handle
47 | this in a few ways. In this app a simple alert is shown with the notification data.
48 |
49 |
50 | {this.setState({title: text})}}
54 | />
55 |
56 | {this.setState({body: text})}}
60 | />
61 |
62 |
67 |
68 |
71 |
72 | Send a local notification in 5 seconds
73 |
74 |
75 |
76 |
79 |
80 | Send a push notification
81 |
82 |
83 |
84 |
85 |
86 |
87 | );
88 | }
89 |
90 | _setDataValue = (dataValue) => {
91 | this.setState({
92 | data: {
93 | thisIsYourData: dataValue
94 | }
95 | })
96 | }
97 |
98 | async _localNotification () {
99 | const notification = {
100 | title: this.state.title,
101 | body: this.state.body,
102 | data: this.state.data,
103 | ios: {
104 | sound: true
105 | },
106 | android: {
107 | sound: true
108 | }
109 | }
110 |
111 | const scheduleOptions = {
112 | time: Date.now() + 5000
113 | }
114 |
115 | Notifications.scheduleLocalNotificationAsync(
116 | notification,
117 | scheduleOptions
118 | )
119 | }
120 |
121 | _pushNotification () {
122 | registerForPushNotificationsAsync(
123 | this.state.title,
124 | this.state.body,
125 | this.state.data
126 | )
127 | console.log(this.state.title, this.state.body, this.state.data)
128 | }
129 |
130 | }
131 |
132 | const styles = StyleSheet.create({
133 | container: {
134 | flex: 1,
135 | backgroundColor: Colors.offWhite,
136 | },
137 | contentContainer: {
138 | paddingTop: 30,
139 | },
140 | textInput: {
141 | height: 60,
142 | margin: 15
143 | },
144 | button: {
145 | backgroundColor: '#2188FF',
146 | padding: 15,
147 | alignItems: 'center',
148 | borderRadius: 10,
149 | margin: 15
150 | },
151 | text: {
152 | alignItems: 'center',
153 | color: Colors.offWhite,
154 | fontSize: 16
155 | },
156 | noteText: {
157 | fontSize: 12,
158 | color: Colors.gray,
159 | marginLeft: 15,
160 | marginRight: 15
161 | },
162 | headerText: {
163 | color: Colors.offBlack,
164 | fontSize: 16,
165 | marginLeft: 15,
166 | marginRight: 15
167 | }
168 | })
169 |
--------------------------------------------------------------------------------