Using Pusher Beams to notify users of a new release of your iOS app
You will need Xcode and React Native installed on your machine.
In this tutorial, we are going to look into building a todo iOS app with React Native. The most interesting part of this tutorial is the fact that we will be implementing push notifications via Pusher Beams. Every time an updated version of the app is released to the App Store, all devices that have the app installed will get a notification informing them of the available upgrade.
Prerequisites
To follow along in this tutorial you need the following things:
- Xcode installed on your machine. Download here.
- Know your way around the Xcode IDE.
- React Native. Find out how to install it here. You will need to follow the section Building Projects with Native Code.
- Carthage installed on your machine. Install it with
brew install carthage
. - A Pusher Beams account. Create one here.
If you happen not to have
brew
installed, you can do so by running/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
.
Creating the iOS project
We will be naming our project todoApp
. To do that we will be needing to make use of the react-native
CLI tool to create a new iOS project. Open a terminal and run the following command:
$ react-native init todoApp
Depending on your internet connection, the above command should take some time. Once it is done, we are ready to add functionality to our iOS app.
The first thing we need to do is to actually build a useful app - the to-do app. Open up the project in your favorite editor, then create a file called Todo.js
. It will serve as the model for our project. In Todo.js
, you will have to paste the following code:
// todoApp/Todo.js
import { AsyncStorage } from 'react-native';
export default class Todos {
constructor() {
this.tasks = {
items: [],
};
this.all(() => {});
}
// register a callback event passing the items found in the store
// as it's arguments
all = callback => {
AsyncStorage.getItem('pushertutorial', (err, allTasks) => {
if (err !== null) {
return;
}
if (allTasks === null) {
return;
}
this.tasks = JSON.parse(allTasks);
callback(this.tasks.items);
});
};
// saves a new item to the store
save = item => {
this.tasks.items.push(item);
return AsyncStorage.setItem('pushertutorial', JSON.stringify(this.tasks));
};
// deletes an item based off an index from the store.
delete = index => {
this.all(items => {
const tasks = {
items: items.filter((task, idx) => {
return idx !== index;
}),
};
AsyncStorage.setItem('pushertutorial', JSON.stringify(tasks));
});
};
}
The above code uses the default key-value pair storage system bundled with React Native called AsyncStorage to retrieve and save our to-do items. You can read more about AsyncStorage here.
Moving on, we will have to actually make use of the Todo model we created earlier. To do this, you will need to edit the App.js
file already created by React Native during the installation earlier. You should edit App.js
and paste in the following:
// todoApp/App.js
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
FlatList,
AsyncStorage,
Button,
TextInput,
Keyboard,
Platform,
} from 'react-native';
import Todos from './Todo';
export default class TodoList extends Component {
constructor(props) {
super(props);
this.state = {
tasks: [],
text: '',
};
this.todos = new Todos();
this.syncTodos();
}
syncTodos = () => {
this.todos.all(items => {
this.setState({
tasks: items,
text: '',
});
});
};
updateTaskText = text => {
this.setState({ text: text });
};
addTask = () => {
let notEmpty = this.state.text.trim().length > 0;
if (notEmpty) {
let { tasks, text } = this.state;
this.todos.save({ text });
this.syncTodos();
}
};
deleteTask = i => {
this.todos.delete(i);
this.setState({
tasks: this.state.tasks.filter((task, index) => {
return index !== i;
}),
});
};
render() {
return (
<View style={[styles.container, { paddingBottom: 10 }]}>
<FlatList
style={{ width: '100%' }}
data={this.state.tasks}
keyExtractor={(item, index) => item.text}
renderItem={({ item, index }) => (
<View key={index}>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<Text
style={{
paddingTop: 2,
paddingBottom: 2,
fontSize: 18,
}}
>
{item.text}
</Text>
<Button title="X" onPress={() => this.deleteTask(index)} />
</View>
</View>
)}
/>
<TextInput
style={styles.input}
onChangeText={this.updateTaskText}
onSubmitEditing={this.addTask}
value={this.state.text}
placeholder="Add a new Task"
returnKeyType="done"
returnKeyLabel="done"
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
padding: 10,
paddingTop: 20,
},
input: {
height: 40,
paddingRight: 10,
paddingLeft: 10,
borderColor: 'gray',
borderWidth: 1,
width: '100%',
},
});
While the above code is simple and straightforward, I would like to explain deleteTask
. After deleting an item from the database, we remove the app from the local state too. This is to allow a UI update.
You can now run this app by either:
react-native run-ios
- Clicking the play button in Xcode. ( highly recommended ).
At this stage, you should have the following:
Push notifications
You have worked hard into this new release of your app, it wouldn’t make any sense to have just 2% of your existing users making use of the newer release - including critical bug fixes and some UI improvements probably. Sending push notifications to users can be a good way to keep your users informed.
In this section, we will configure and add Pusher Beams to our application to help us deliver push notifications about updates to users who have the app installed.
We will be making use of two packages to achieve this.
- The official iOS SDK. GitHub Repo.
- The React Native bridge for the official iOS SDK. GitHub Repo.
Using the React Native bridge requires the installation of the official SDK.
We will start by installing the official iOS SDK. We will make use of Carthage for this. Carthage makes use of a Cartfile
to track dependencies to install, so we will need to create that file.
$ # Assuming you are at the root directory which is todoApp
$ cd ios
$ touch Cartfile
The next thing to do is to specify the exact dependencies you want installed. This is as easy as pasting the following content in the Cartfile
:
// todoApp/ios/Cartfile
github "pusher/push-notifications-swift"
Once the dependencies have been specified, the next point of action is to actually install them. To do that, you will need to run the below command in a terminal:
# This assumes you are in the todoApp/ios directory
$ carthage update
Once carthage
is done installing, you will need to:
- In Xcode, visit the General settings tab of the application’s target, in the “Linked Frameworks and Libraries” section, drag and drop the
PushNotifications.framework
from the Carthage/Build folder on disk.
A directory called Carthage will be created next to the
**Cartfile**
. You will need to locate**PushNotifications.framework**
in the iOS folder too.
-
On your application targets’ Build Phases settings tab, click the + icon and choose New Run Script Phase. Create a run script in which you specify your shell (ex:
/bin/sh
), add the following contents to the script area below the shell:/usr/local/bin/carthage copy-frameworks
-
Add the path below to
PushNotifications.framework
under “Input Files".$(SRCROOT)/Carthage/Build/iOS/PushNotifications.framework
-
Add the path below to
PushNotifications.framework
under the “Output Files”.$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/PushNotifications.framework
The next step is to now install the React Native bridge which will allow us access native code (the official iOS SDK in this case ) from JavaScript. To do that, you need to run the following command
$ npm install react-native-pusher-push-notifications
- In Xcode, in the project navigator, right click Libraries ➜ Add Files to todoApp.
- Go to node_modules ➜ react-native-pusher-push-notifications and add RNPusherPushNotifications.xcodeproj
- In Xcode, in the project navigator, select your project. Add libRNPusherPushNotifications.a to your project’s Build Phases ➜ Link Binary With Libraries
Open the AppDelegate.m
to register the device for push notifications. Append the following contents to the file:
// todoApp/ios/AppDelegate.m
// Add this to the top of the file where other imports are placed.
#import "RNPusherPushNotifications.h"
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSLog(@"Registered for remote with token: %@", deviceToken);
[[RNPusherPushNotifications alloc] setDeviceToken:deviceToken];
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
[[RNPusherPushNotifications alloc] handleNotification:userInfo];
}
-(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
NSLog(@"Remote notification support is unavailable due to error: %@", error.localizedDescription);
}
As a final step, you will need to add the following to your Header search path **under Build Settings, $(SRCROOT)/../node_modules/react-native-pusher-push-notifications/ios
.
If you receive an error such as “dyld: Library not loaded:”, you should go to Build Settings and set Always Embed Swift Standard Libraries to yes.
Login or create an account to access your Pusher Beams dashboard here. Create a new Pusher Beams instance using the dashboard.
Complete only step one of the iOS setup guide and follow the onscreen instructions to upload (and how to find) your APN key and Team ID. Then press the X to exit the setup guide and you will be returned to your dashboard for that instance. Scroll to the bottom of this page and you will find your Pusher Beams instance ID and secret key, make note of these you will need them later.
As a final step, you will need to enable push notifications capabilities for the project. You will also need to set the correct team and bundle ID as without those, push notifications capabilities cannot be enabled.
You will need to edit the index.js
file to ask the user for permissions to send notifications and also subscribe to the updates topic.
// todoApp/index.js
import { Alert, Linking, AppRegistry, Platform } from 'react-native';
import App from './App';
import { name as appName } from './app.json';
import RNPusherPushNotifications from 'react-native-pusher-push-notifications';
const appUpdateInterest = 'debug-updates';
// Initialize notifications
export const init = () => {
// Set your app key and register for push
RNPusherPushNotifications.setInstanceId(
'YOUR_PUSHER_INSTANCE_KEY'
);
// Init interests after registration
RNPusherPushNotifications.on('registered', () => {
subscribe(appUpdateInterest);
});
// Setup notification listeners
RNPusherPushNotifications.on('notification', handleNotification);
};
// Handle notifications received
const handleNotification = notification => {
if (Platform.OS === 'ios') {
Alert.alert('App update', notification.userInfo.aps.alert.body, [
{ text: 'Cancel', onPress: () => {} },
{
text: 'Update now',
onPress: () =>
// Just open up Apple's Testlight in the app store.
// Ideally we will replace this if the app has been previously released to
// the app store
Linking.openURL(
'itms-apps://itunes.apple.com/ng/app/testflight/id899247664?mt=8'
),
},
]);
}
};
// Subscribe to an interest
const subscribe = interest => {
console.log(interest);
RNPusherPushNotifications.subscribe(
interest,
(statusCode, response) => {
console.error(statusCode, response);
},
() => {
console.log('Success');
}
);
};
init();
AppRegistry.registerComponent(appName, () => App);
The above piece of code is really easy to understand as it all does is configure the PushNotifications library to make use of the key we got from the dashboard earlier. When the device has been registered with Pusher Beams, we subscribe the user to the debug-updates
topic as all notifications for updating the app will be published to that topic.
In handleNotification
, we show an alert dialog that provides the user with two options. One is to cancel, the other is to actually update. Clicking on the option to update the app will take the user to the Apple app store.
Since this is an hypothetical app, we will forward the user to Apple’s Testflight app. You can replace the link to that of a real app if the app already exists on the app store.
Sending push notifications to the device
The bulk of the entire work has been done. All is that is left now is to actually test that push notifications are delivered to the user. To do this, you will need to visit your instance page on the dashboard. You will want to navigate to the Debug console.
You will need to run the app on a real device as push notifications do not work on a simulator.
Once you have filled the above form, click on the Publish Notifications button. You will get an alert on your device in less than a second.
Here is an example of how the app works. You should be able to replicate this behavior on your device.
Conclusion
In this tutorial, we have built a mechanism for informing users of updates to our app with the help of Pusher Beams.
The source code can be found on GitHub.
29 August 2019
by Lanre Adelowo