├── .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 | --------------------------------------------------------------------------------