Implement tag-based push notifications to notify users when new tweets are posted
Register a Twitter app and use the Twitter API to obtain a list of recent tweets
Use an event source to periodically check for new tweets
Create a WatchKit app that displays the list of tweets and handles notifications
Pass data between the iOS and WatchKit app
Creating the iOS App
Start by creating an iOS project with a Single View Application. Choose Swift for the language.
We want to display tweets that will get updated frequently, so I've decided to show tweets that contain '#AppleWatch' since that's both relevant to the project and a very active hashtag right now.
In our iOS app, we'll use a TableView to display the list of tweets. We'll also have a refresh button for this table. We want to allow the user to subscribe and unsubscribe from the push notifications, so we''ll have a button for that too.
So, add those 3 items to the storyboard. We're also going to have a label for the table.
Create IBActions for the buttons and IBOutlets for the buttons and table view:
At this point we can run the app on the iPhone simulator and see that tapping the button multiple times changes the text back and forth from "Start receiving..." to "Stop receiving..."
Adding the MobileFirst Adapter
Now we’ve got to set up the MobileFirst portion of the application. Using MobileFirst Studio, create a MobileFirst Project with a Native iOS API. Then create a MobileFirst HTTP Adapter:
You should now have the following project structure in Studio:
Now we're going to integrate the MobileFirst SDK into the iOS application:
Copy the WorklightAPI folder and worklight.plist file from the MobileFirst project into the Xcode project.
Inside the Xcode project Build Settings, in the Swift Compiler - Code Generation section, set Objective-C Bridging Header to "WorklightAPI/include/WLSwiftBridgingHeader.h"
In the Linking section, set Other Linker Flags to "-ObjC"
Inside Build Phases, in the Link Binary With Libraries section, add the following frameworks:
Now it's time for the fun stuff!
The next step is to establish a connection to the server. To do this, we need our ViewController to implement WLDelegate protocol. So, add that to the class definition:
Now we will get an error saying that ViewController does not conform to protocol 'WLDelegate' because we need to implement certain methods that are required for WLDelegate. Command+Click on WLDelegate and we can see the method definitions that we need to add to our ViewController:
/**
* @ingroup main
*
* A protocol that defines methods that a delegate for the WLClient invokeProcedure method should implement,
* to receive notifications about the success or failure of the method call.
*/@protocolWLDelegate<NSObject>/**
*
* This method will be called upon a successful call to WLCLient invokeProcedure with the WLResponse containing the
* results from the server, along with any invocation context object and status.
*
* @param response contains the results from the server, along with any invocation context object and status.
**/-(void)onSuccess:(WLResponse*)response;/**
*
* This method will be called if any kind of failure occurred during the execution of WLCLient invokeProcedure.
*
* @param response contains the error code and error message, and optionally the results from the server,along with any invocation context object and status.
**/-(void)onFailure:(WLFailResponse*)response;@end
Now we can add those methods in Swift. We'll also print to the console in both methods just to show where we are:
Now that we conform to the WLDelegate protocol, we can use the WLClient API to connect to the server. Add the following line to the viewDidLoad() method:
Let's test what we have so far and make sure we can connect to the server:
In the MobileFirst Studio project, right-click the app name and select Run As->Deploy native API
Right-click the adapter name and select Run As->Deploy MobileFirst Adapter
Run the Xcode app.
You should see a lot of output in the console including the "onSuccess" log.
Push Notifications
Now we can implement the tag subscription. Push notifications in iOS requires an APNS p12 certificate to be added to the MobileFirst project.
Inside the application-descriptor.xml file, provide the certificate password and the subscription tag:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<nativeIOSAppid="TwitterFeeds"platformVersion="7.0.0.00.20150402-2001"bundleId="com.TwitterFeeds"version="1.0"xmlns="http://www.worklight.com/native-ios-descriptor"><displayName>TwitterFeeds>/displayName>
<description>TwitterFeeds>/description>
<accessTokenExpiration>3600>/accessTokenExpiration>
<userIdentityRealms>>/userIdentityRealms>
<tags><tag><name>sample-tag1>/name>
<description>A sample tag 1>/description>
</tag></tags><pushSenderpassword="password"/></nativeIOSApp>
Next is implementing the iOS portion of the subscription. In the same way we had to use WLDelegate to connect to the server, we'll need to use WLOnReadyToSubscribeListener to subscribe to the tag. To conform to its protocol, we have to implement the OnReadyToSubscribe() method. When we reach that method, we'll know that the application is ready to subscribe, so we can enable the subscribe button in there. Prior to that, it should be disabled. We will also need to initialize the WLPush instance inside the viewDidLoad() method:
classViewController:UIViewController,WLDelegate,WLOnReadyToSubscribeListener{...overridefuncviewDidLoad(){super.viewDidLoad()toggleSubscriptionButton.enabled=falseWLClient.sharedInstance().wlConnectWithDelegate(self)WLPush.sharedInstance().onReadyToSubscribeListener=selfif(isSubscribed){toggleSubscriptionButton.setTitle("Stop receiving notifications",forState:UIControlState.Normal)}else{toggleSubscriptionButton.setTitle("Start receiving notifications",forState:UIControlState.Normal)}}...funcOnReadyToSubscribe(){NSLog("ready to subscribe")toggleSubscriptionButton.enabled=true}...}
We also have to make changes to the AppDelegate. Inside the didFinishLaunchingWithOptions method, we need to initialize the push instance. We also need to implement the didRegisterForRemoteNotificationsWithDeviceToken method to set the device token:
To test that the app is ready to subscribe, run the app on a device, and make sure the subscribe button becomes enabled. Also make sure the console contains the "ready to subscribe" log.
Next, we need to implement the actual subscribing. Inside the toggleSubscription method, we'll use the WLPush API to subscribe to (and unsubscribe from) the tag:
If we run the app on a device and click the subscribe button, we'll see a log message saying "Successfully subscribed to tag sample-tag1" in the console. If we click again, we'll see "Successfully unsubscribed from tag sample-tag1" as well.
Using the Twitter API
Visit the Twitter Application Management site to set up a Twitter app (you must have a Twitter account to do this).
In Application-only authentication, Follow Step 1 under Issuing application-only requests to obtain your encoded credentials. Add them to the worklight.properties file in your MobileFirst project:
The rest of the work will be done in the adapter. Following Step 2, we're going to obtain an access token. Inside TwitterAdapter-impl.js, we'll have an access token variable that will be null initially and then updated:
varaccessToken=null;functiongetTwitterAccessToken(){WL.Logger.info("getTwitterAccessToken");if(accessToken!=null){WL.Logger.info("getTwitterAccessToken :: returning existing accessToken");returnaccessToken;}WL.Logger.info("getTwitterAccessToken :: getting a new accessToken");varrequestOptions={method:"POST",headers:{Authorization:"Basic "+WL.Server.configuration["my.twitter.app.credentials"],},path:"oauth2/token",body:{content:"grant_type=client_credentials",contentType:"application/x-www-form-urlencoded;charset=UTF-8"}};varresponse=WL.Server.invokeHttp(requestOptions);if(response.statusCode==200){WL.Logger.info("getTwitterAccessToken :: got a new accessToken");accessToken=response.access_token;}else{WL.Logger.info("getTwitterAccessToken :: failed to get a new accessToken");accessToken=null;}returnaccessToken?"Bearer "+accessToken:accessToken;}
For Step 3, we're going to make a request using the value of the Bearer token as the Authorization header. The path will specify that we only want tweets containing #AppleWatch. We're going to get only the 10 most recent tweets:
The shouldRetry boolean is to prevent the code from retrying to get tweets more than once if it is unsuccessful. Without this, we could get an infinite loop.
We want to notify the user when a new tweet arrives. So we need to keep track of the latest tweet, get a new one, and compare the two. If they're different, we send a push notification to our tag and update the latest tweet:
We want to be able to check for new tweets periodically, so we're going to this with an event source. For testing purposes, we'll use an interval of 30 seconds. A more practical interval would be 30 minutes (1800 seconds):
To check that this all works, visit this URL in your browser: http://{server}:{port}/context/invoke?adapter=TwitterAdapter&procedure=getTweets¶meters=["10"]
You should see a ton of JSON output, but if you look closely you will see that the tweets are there. You can put the output into a JSON prettifier and see it more clearly.
The next step is to get the tweets into the TableView in our iOS app in the ViewController. We're going to add variables for a cell identifier and a tweets array:
To use the TableView, we need to conform to the UITableViewDelegate and UITableViewDataSource protocols.
For the cellForRowAtIndexPath method, we're going to display only the main content of the tweet, which has the key "text" within the tweets array (you can see this key in the JSON output from invoking the adapter procedure).
Now to actually populate the table - we want to do this both in our iOS app and on the Apple Watch.
Creating the WatchKit App
Add a WatchKit App target to the iOS project. On the watch, we're only going to show the table of tweets. We don't need any buttons, but we do want to put a label inside the table to display the tweet:
Add a new class to the WatchKit Extension folder called FeedRowController that is a subclass of NSObject. This class will hold the content for each row of the table. In the storyboard, select the TableRowController from the Interface Controller Scene and set its class and identifier to FeedRowController:
Add an IBOutlet for the label. You will need to add the import WatchKit statement.
We want to keep all of the logic that retrieves the tweets in one place. To keep things simple, we will do all of this in a separate class. Add a new class to the application folder called RequestManager that is a subclass of NSObject. This class will make the request to the adapter to get the tweets and send the response with a completion handler. We will send this data back and forth between the iOS app and the WatchKit app by using WatchKit's openParentApplication and handleWatchKitExtensionRequest methods.
Since we are making an adapter request, the RequestManager class will need to conform to the WLDelegate protocol and implement onSuccess and onFailure methods. We aren't going to do anything inside these methods except print to the console so we know that we've reached those methods.
The openParentApplication method will be called inside willActivate. We will create a dictionary specifying the action we want to take which will be passed to the iOS app. The tweets will be received in the reply block and used to populate the table.
The handleWatchKitExtensionRequest method will be implemented inside the AppDelegate. This method is going to identify the action we specified in the dictionary that is passed through the userInfo parameter and respond accordingly by calling the retreiveTweets method and sending the results to the WatchKit app through the reply block.
For our purposes we are only performing one action, which is getting the tweets. If we wanted to handle other actions, we would specify those in the actionDictionary we pass to openParentApplication and process them with additional if statements in handleWatchKitExtensionRequest.
We're going to add a loadTweets method in ViewController that will call the retreiveTweets method and reload the table data. This method will be called on two occasions: when a successful connection is established and when the refresh button is tapped:
The last thing we need to do is ensure that our WatchKit app can handle receiving notifications. To do this, we just have to uncomment the didReceiveRemoteNotification method inside NotificationController:
1
2
3
4
5
6
7
8
overridefuncdidReceiveRemoteNotification(remoteNotification:[NSObject:AnyObject],withCompletioncompletionHandler:((WKUserNotificationInterfaceType)->Void)){// This method is called when a remote notification needs to be presented.// Implement it if you use a dynamic notification interface.// Populate your dynamic notification interface as quickly as possible.//// After populating your dynamic notification interface call the completion block.completionHandler(.Custom)}
We're done! To test that the notifications are received on the Watch, run the app on an iPhone (with the MobileFirst native API and adapter deployed), then close the app and lock the iPhone. Put the Watch on (the Watch needs to be worn so that the wearer gets notification there and not the phone) and wait the 10 seconds or so for a new tweet to arrive. This doesn't require you to do any manual checking since we check for new tweets with the event source. When you get the notification on the watch, tap the app icon to launch the WatchKit app.
Inclusive terminology note: The Mobile First Platform team is making changes to support
the IBM® initiative to replace racially biased and other discriminatory language in our code and content
with more inclusive language. While IBM values the use of inclusive language, terms that are outside of
IBM's direct influence are sometimes required for the sake of maintaining user understanding. As other
industry leaders join IBM in embracing the use of inclusive language, IBM will continue to update the
documentation to reflect those changes.