Using Aeris SDK for Weather Advisories

Categories:

Editor’s Note: This is part three in a three part series. The goal in this series is to develop a fully functional (and useful) iOS application that is capable of pushing weather alerts based on your location to your iOS device. The series is broken down as follows:

  • Part One – Developing a Push Notification-capable application with Parse and Swift
  • Part Two – Using CoreLocation with Swift
  • Part Three – Integrating in a Weather Service API

In this third part of our three part series we’ll be completing our weather alerts application started in Part One. A warning! The code examples that follow in this post build upon the code covered in both Part 1 and Part 2.

HAMweather

We’re going to make use of the HAMweather service for our weather data. There are a variety of weather services that provide rich APIs for obtaining all manner of weather-related data. Here’s just a few of them:

We chose HAMweather over the others solely on the fact that it provided weather advisory access in its free offering. As a bonus it comes with a nice iOS SDK framework for dealing with its weather API named Aeris.

Signing Up for HAMweather

To get started with HAMweather and Aeris, proceed on over to the signup page and select Aeris API – Developer Free then scroll down and enter the required user information. Of particular interest will be selection of your domain name: if you plan on using the Aeris SDK on the server-side choose the domain under which your servers are registered. In our case, we chose iachieved.it.

Click Next to agree to the user agreement and then Login to login with your newly created username and password. Once you’ve logged in you’re going to want to proceed directly to managing your subscriptions by clicking on the Account tab.

tut_aeris_your_subscriptions

Click on Aeris API – Application/Website Registration to register the application and generate a consumer ID and secret. Pay particular attention to your Application Domain/Identifier field, this needs to match your iOS application bundle identifier, which uses reverse DNS notation. iAchieved.it’s bundle identifier is it.iachieved.weatheralerts, but yours will be based upon your reverse DNS.

tut_aeris_api_application

Click the REGISTER button at the bottom of the page to register the application and get your application keys!

tut_api_registration_success

Now we need to add Aeris API keys to our application. We’re using our ApiKeys.plist file to maintain all of our API keys, and have referred to the Aeris consumer ID and secret as AERIS_CONSUMER_ID and AERIS_CONSUMER_SECRET.

tut_api_keys

If you have a confused look on your face as to why we added these keys to a property list file, go back to Part One and our tutorial on using property lists for API key management.

Obtaining the iOS SDK

We’ll be using the new Aeris 2.0 SDK for iOS, which can be obtained from Github. Download the SDK 2.0 Beta 1 and unzip to a folder.

tut_aeris_frameworks

Add the Aeris.Framework to your project by dragging and dropping it into your weatheralerts project navigator. Ensure that the Copy items if needed and Create groups are selected, and that your weatheralerts target is checked. For our purposes we only need Aeris.Framework, but if we wanted to begin leveraging other features of Aeris we would add some of the other frameworks.

Aeris also requires using the link flag -ObjC. To set the flag go to your project’s Build Settings and type Other Linker in the search box. You will see Other Linker Flags in the Linking section. Add -ObjC:

tut_objc_linker

Adding AFNetworking

Aeris makes uses of the popular AFNetworking networking library for iOS, so we will need to add it to our application first. We’re going to grab the 2.5.0 version, so go to Github and click on the Download Zip button. Note: We could have used Cocoapods to manage the installation of AFNetworking, but there’s additional setup required to get Cocoapods going, and quite frankly, I don’t like the way it takes over my Xcode project organization.

Drag and drop both the AFNetworking and UIKit+AFNetworking (Aeris makes use of AFNetworkActivityIndicatorManager class) folders into your project, again ensuring that Copy items if needed and Create groups are selected and that the files are added to your target.

Using Aeris

Now that we have all of the prerequisites out of the way we can get down to business with actually using Aeris. The first step in accessing the Aeris SDK routines in a Swift-based project will be to import its header files in our application’s bridging header. Add the following to your bridging header (which should be named bridingHeader.h):

[objc]
#import <Aeris/Aeris.h>
[/objc]

The complete bridgingHeader.h file should look something like this now:

[objc]
#ifndef weatheralerts_bridgingHeader_h
#define weatheralerts_bridgingHeader_h

#import <Parse/Parse.h>
#import <Aeris/Aeris.h>

#endif
[/objc]

We’ll now turn to the WeatherServiceController class created in Part 2 of our tutorial. Find the init() routine and ensure it looks like the following:

[objc]
override init() {
super.init()

let aerisConsumerId = valueForAPIKey(keyname: "AERIS_CONSUMER_ID")
let aerisConsumerSecret = valueForAPIKey(keyname: "AERIS_CONSUMER_SECRET")

AerisEngine.engineWithKey(aerisConsumerId, secret: aerisConsumerSecret)

NSNotificationCenter.defaultCenter().addObserver(self,
selector: "locationAvailable:",
name: "LOCATION_AVAILABLE",
object: nil)
}
[/objc]

AerisEngine.engineWithKey() initializes our Aeris framework with the consumer ID and secret that we received when registering our application.

The Aeris SDK employs a notion of loaders that are used to obtain information from the HAMweather service. To obtain a list of advisories in our current location we first have to obtain an Aeris place and then use an advisory loader to retrieve the advisories (if any) for our place.

To create a place, or, AWFPlace, we provide city, state, and country information like so:

[objc]
let place = AWFPlace(city: city, state: state, country: country)
[/objc]

We can at this point load the advisories for our place using an AWFAdvisoriesLoader.

[objc]
let advisoryLoader = AWFAdvisoriesLoader()
advisoryLoader.getAdvisoriesForPlace(place, options: nil) { (advisories, e) -> Void in
}
[/objc]

The getAdvisoriesForPlace function takes our place and returns a list (array) of advisories currently issued for it. Let’s put this all together and place in our WeatherServiceController‘s locationAvailable function:

[objc]
func locationAvailable(notification:NSNotification) -> Void {
let userInfo = notification.userInfo as Dictionary<String,String>

println("WeatherService: Location available \(userInfo)")

let city = userInfo["city"]!
let state = userInfo["state"]!
let country = userInfo["country"]!

let place = AWFPlace(city: city, state: state, country: country)
let advisoryLoader = AWFAdvisoriesLoader()

advisoryLoader.getAdvisoriesForPlace(place, options: nil) { (advisories, e) -> Void in
if let error = e {
println("Error: \(error.localizedDescription)")
} else {
// Take the last advisory
if let advisory = advisories.last as? AWFAdvisory {

let userInfo = [
"location": city + ", " + state,
"name": advisory.name,
"body": advisory.body
]

NSNotificationCenter.defaultCenter().postNotificationName("ADVISORY_AVAILABLE",
object: nil,
userInfo: userInfo)
} else {
println("no advisories")
}
}
}
}
[/objc]

Now, when our WeatherServiceController receives a new location from our CoreLocationController (via a NSNotification labeled LOCATION_AVAILABLE), it will unpack this information from the userInfo dictionary, create an AWFPlace, and then use the AWFAdvisoriesLoader to load any advisories for the area.

Let’s take a look at what we’re doing with the advisories array. For simplicity, and to not barrage the user with push notifications, we’re going to take the last advisory in any list of advisories returned to us. The expression if let advisory = advisories.last as? AWFAdvisory is our use of Swift optionals to evaluate the if block to true if we have an advisory. If we do have an advisory then we create a userInfo dictionary containing the location of the advisory, the advisory name, and an advisory body which contains additional information. This information is then posted to the NSNotificationCenter with the name ADVISORY_AVAILABLE.

We’ve only scratched the surface in what is possible with the Aeris SDK. For more information on using Aeris, see the Getting Started documentation.

Pushing the Advisory

If you recall from Part One of our tutorial we created a class PushNotificationController to be responsible for managing our relationship with the Parse framework. Let’s return to our PushNotificationController class and update its init routine to handle listening for an ADVISORY_AVAILABLE notification.

The full init() function for PushNotificationController should now look like:

[objc]
override init() {
super.init()

let parseApplicationId = valueForAPIKey(keyname: "PARSE_APPLICATION_ID")
let parseClientKey = valueForAPIKey(keyname: "PARSE_CLIENT_KEY")

Parse.setApplicationId(parseApplicationId, clientKey: parseClientKey)

NSNotificationCenter.defaultCenter().addObserver(self,
selector: "advisoryAvailable:",
name: "ADVISORY_AVAILABLE",
object: nil)

}
[/objc]

Now let’s write our advisoryAvailable: routine:

[objc]
func advisoryAvailable(notification:NSNotification) -> Void {

println("advisoryAvailable")

// Get our advisory name
let userInfo = notification.userInfo as Dictionary<String,String>

let advisoryLocation = userInfo["location"]!
let advisoryName = userInfo["name"]!

let pushMessage = "Weather Advisory for \(advisoryLocation): \(advisoryName)"

// Create our Installation query
let token = PFInstallation.currentInstallation().deviceToken
let pushQuery:PFQuery = PFInstallation.query()
pushQuery.whereKey("deviceToken", equalTo:token)

// Send push notification to query
let pushNotification:PFPush = PFPush()
pushNotification.setQuery(pushQuery)
pushNotification.setData([
"sound":"alert.caf",
"alert":pushMessage
])
pushNotification.sendPushInBackgroundWithBlock({ (succeeded,e) -> Void in

if succeeded {
println("Push message to query in background succeeded")
}
if let error = e {
println("Error: \(error.localizedDescription)")
}
})
}
[/objc]

Breaking this down, we:

  • unpack our advisory information from the NSNotification
  • build a Parse PFQuery targeted for our device
  • use Parse to send the push notification

This code uses Parse’s client push capability which must be enabled for your Parse application. Go to your application in Parse and to the Settings – Parse page and enable client push.

tut_parse_client_push

Warning!Failure to enable client push in the Parse portal will result in your push notification failing!

We should be able to now run our application and using a combination of Wunderground and Xcode simulator find a location that we know has a pending advisory. Simply look at the current weather advisories, find a city in one, and then use Google to look up the GPS coordinates. As of today there is a Coastal Flood Advisory for Franklin, Louisiana, so we’ll put it’s GPS coordinates in our Waypoints.gpx file.

tut_gps_coordinates

Remember to put a minus sign for the longitude coordinate if you want the Western hemisphere!

Putting everything together and running our application, we get a nice push notification about a coastal flood advisory in Franklin, Louisiana.

tut_weather_advisory

Adding Sound to our Push Notification

Push notifications should typically grab your attention, especially ones for weather alerts! If you recall our push notification data is set as follows:

[objc]
pushNotification.setData([
"sound":"alert.caf",
"alert":pushMessage
])
[/objc]

The sound data specifies a sound resource to play back with the receipt of the push notification. This file has to be included in your application resources obviously to be located and played. So grab our alert.caf file and add it to your project. Then, ensure that it is added as a resource by going to your application target and selecting the Build Phases page and adding alert.caf in the Copy Bundle Resources section.

tut_copy_bundle_resources

Bitbucket Xcode Project

The work done thus far is available on Bitbucket on a branch named part3. To use the project you can directly download the zip file and open the enclosed Xcode project. There are a few steps you’ll need to complete to use the project:

  • Sign up for Parse and HAMweather and obtain your own API keys
  • Change the Bundle Identifier to reflect your organization (that is, change it.iachieved. to com.yourcompany)
  • Create and add your own ApiKeys.plist file and use your Parse and HAMweather API keys
  • Create and configure your own provisioning profile

Additional Thoughts

Our application is pretty basic. So basic, in fact, there’s no user interface. No home location we can set, no weather forecast to look at, and we can’t even choose which types of advisories we want to receive. Honestly, who needs coastal flood advisories sent to them? Well, other than those on the coast. There’s plenty we can do with the basic application framework developed, and now is where the fun can really begin.

Unfortunately, there is a bit of a flaw in our application design. What happens if an advisory is issued for an area after the user has moved into area? Oh dear. Well, perhaps we can run our application in the background and periodically wake up and check for advisories? Nope. Could we increase the frequency of receiving location updates in the background? Sure, if you wanted Apple to reject your application outright.

This is where it can become all too obvious that mobile applications that provide up-to-date information feeds typically require some type of cloud application to push those feeds to users. Taking a look at our demo application the issue is that something needs to be periodically checking whether or not there are new advisories for the user’s last known location.

Never fear. We’re going to expand this tutorial series to include utilizing Parse’s Cloud Code feature to run background jobs to update a user if there is a new advisory for their last location. Stay tuned!

Leave a Reply

Your email address will not be published. Required fields are marked *