Thursday, July 24, 2014

iOS8 interactive notification available in AeroGear

AeroGear UnifiedPush Server is ready for iOS8!
Want to see how to create iOS8 interactive notifications? Follow me...

AeroDoc push notification application revisited, step by step

Last year, I blogged about AeroDoc, a demo app for AeroGear UnifiedPush Server (UPS). This is a very good entry point to get an overview on UPS. Here's the story:

A sell agent can receive push notifications triggered by her admin. Upon notification, she has the option to accept the lead and add it to her personal list. But to accept the lead, you have to tap the notification and bring the app in the foreground. The whole purpose of interactive notification is to let you do action without context switch. No more app switching!
Going back to AeroDoc tutorial, let's see how we can enhance the client app to accept a lead directly in the notification.

What you need

  • iOS8+. At the time of writing, iOS8 beta4 is out.
  • Xcode6. Same here beta4
  • clone, build and deploy UPS or alternatively create a UPS on OpenShift
  • clone, build and deploy AeroDoc backend
  • clone, build AeroDoc iOS client

  • Steps by steps

    Step1: Create an Application on UnifiedPush Server

    Watch the screencast to learn how to:
  • create an OpenShift AeroGear UPS instance.
  • create a new backend app
  • associate an iOS variant with its APNs certificate



  • Step2: AeroDoc backend configuration

    Install AeroDoc backend following instructions.
    Go to configuration link and create a new configuration, entering:
  • UPS server URL
  • AppID
  • App Secret

  • Step3: AeroDoc backend app

    AeroDoc backend app uses unifiedpush-java-client to send push notification to UPS. Since 0.8.0, unifiedpush-java-client can send interactive notification in its payload. See the extra payload line 11. The category identifier "acceptLead" should be reused when implementing actions on client side. And that's all you need to do to support interactive notification server side!
        public void sendLeads(List users, Lead lead) {
                ...
                UnifiedMessage unifiedMessage = new UnifiedMessage.Builder()
                        .pushApplicationId(getActivePushConfig().getPushApplicationId())
                        .masterSecret(getActivePushConfig().getMasterSecret())
                        .categories("lead")
                        .actionCategory("acceptLead")
                        .aliases(users)
                        .simplePush("version=" + new Date().getTime())
                        .attribute("id", lead.getId().toString())
                        .attribute("messageType", "pushed_lead")
                        .attribute("name", lead.getName())
                        .attribute("location", lead.getLocation())
                        .attribute("phone", lead.getPhoneNumber()).sound("default")
                        .alert("A new lead has been created").build();
    
                javaSender.send(unifiedMessage, new LeadSenderMessageResponseCallback());
    
        }
    

    Note: APNs interactive notifications add category to its payload to associate a group of actions to a category. As UPS already uses the name 'categories' in its payload but for a different purpose, the name action-category was chosen instead.

    Step3: AeroDoc iOS app

    Watch it live coding:



    Or just follow these instructions:

    When registering for remote notification, provide a category (from the previous code we called it 'acceptLead'). To define a category, add all associated actions. Here we demo only one action but you can define a list. Depending on your notification setup, the list of visible actions can be limited. Using UIUserNotificationActionContextMinimal you can define which actions should be displayed in priority when space is limited.
    - (UIMutableUserNotificationCategory*)registerActions {
        UIMutableUserNotificationAction* acceptLeadAction = [[UIMutableUserNotificationAction alloc] init];
        acceptLeadAction.identifier = @"Accept";
        acceptLeadAction.title = @"Accept";
        acceptLeadAction.activationMode = UIUserNotificationActivationModeForeground;
        acceptLeadAction.destructive = false;
        acceptLeadAction.authenticationRequired = false;
        
        UIMutableUserNotificationCategory* category = [[UIMutableUserNotificationCategory alloc] init];
        category.identifier = @"acceptLead";
        [category setActions:@[acceptLeadAction] forContext: UIUserNotificationActionContextDefault];
        return category;
    }
    
    Once all actions are defined and wrapped into a category, inject category to UIUserNotificationSettings as shown below:
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    #ifdef __IPHONE_8_0
        UIUserNotificationCategory* category = [self registerActions];
        NSMutableSet* categories = [NSMutableSet set];
        [categories addObject:category];
        UIUserNotificationSettings* notificationSettings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:categories];
        [[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
        [[UIApplication sharedApplication] registerForRemoteNotifications];
    #else
        [[UIApplication sharedApplication] registerForRemoteNotificationTypes: (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
    #endif
    

    When an action has been selected in the interactive push notification, the callback application:handleActionWithIdentifier:forRemoteNotification:completionHandler: is called. To implement your action you need to check on which action was chosen:
    - (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void(^)())completionHandler {
        if([identifier isEqualToString: @"Accept"]) {
          ...
          ...
        }
        completionHandler();
    }
    
    You only left to implement the acceptLead logic within the if. If you want to see the full source code go to github.

    Enjoy !

    Friday, July 18, 2014

    Can I skip Optionals?

    TL;TR: NO!
    Working with Swift but using Cocoa/CocoaTouch API, you will have to get a good grasp on what they are and how to use them. When an Objective-C API returns nil, Swift will receive an Optional set to the value NONE.

    Quick definition

    A definition by example:
    var optionalString: String?
    optionalString == nil
    // Non optional values needs to be initialised before using them. 
    var string: String = "Test String"
    string == "Test String"
    
    Optionals is an enum to say "I have something" or "it's empty". You can picture it as a box which can be empty or contain something. It's just a box, to know what's inside you need to unwrap it. When defining an optional type, use ?, when unwrapping it, use !. Note that ? and ! are short name for Optional and ImplicitlyUnwrappedOptional.

    To unwrap or not to unwrap?

    When do we want to unwrap them? Work with them as much as you can in their Optional form, unwrap them when you need to work with their value.
    // An optional is sth or nothing, you can not compute operation with optional
    // you need to unwrap them
    var i: Int?
    var j: Int?
    //var k = i + j     // compilation issue
    //var k = i! + j!   // no compilation issue but runtime issue if i/j not initialised
    
    But the confusion is that most of the time, operating on optionals required unwrapping... except for println and string interpolation. the exception to confirm the rule, would say linguist ;)
     
    var kitty: String? = "Kitty"
    var greeting = "Hello \(kitty)"     // No compiler error!
    print("Hello"); println(kitty)      // Also fine.
    var nope = "Hello " + kitty         // Compiler error
    


    Different ways of unwrapping

    Several way of unwrapping:
    • either go the brute way, unwrap without checking and risk a runtime error
    • or check before with if statement, you can even use the if let or if var statement
    • or go with optional chaining with elvis operator ?. (turn your head left to see Elvis, remind me Groovy syntax here)
    // 1. force unwrapped
    var optionalValue: String?
    // optionalValue! // no compilation issue but runtime issue because optionaValue is nil
    
    // 2. if let: unwrap with
    var optionalName: String?
    var hello = "Hello!"
    if let name = optionalName {
        hello = "Hello, \(name)" // not executed
    }
    optionalName = "John Appleseed"
    hello = "Hello, \(optionalName)"
    
    if let name = optionalName {
        hello = "Hello, \(name)" // executed
    }
    
    // Same idea with for
    var jsonResponse: [String:String?] = ["one":"onevalue", "two":nil]
    var str:String?
    for (key, value) in jsonResponse {
        str = "key \(key) value \(value)"
    }
    str // Some "key one value onevalue"
    
    // 3. optional chaining
    // do not unwrap optionals if not needed, work with them 
    if let a = call1() {
        if let b = a.call2 {
            if let c = b.call3 {
                // do sth
            }
       }
    }
    let c = call1()?.call2?.call3
    


    Working with implicitly unwrapped optionals

    Implicitly unwrapped optionals can be nil same than normal optional, but they are automatically unwrapped when used. As per unwrap optional you may run into runtime error.
    func toInt(first:String!) -> Int! {
        return Int(first.toInt()!)
    }
    var myIn:Int = toInt("3")
    // var myIn:Int = toInt("3e") // runtime error
    


    Try it yourself

    Have a go with Optionals Playground format and Happy Swift coding!

    Tuesday, July 15, 2014

    Working with tuple

    Tuples are groups of values combined into a single, compound value. Brand new in Swift, they offer new approach on how to design and code, they particularly play well with functional programming. Very often used as return type of a function. For example, it allows you to return a value combined with an error code. Let's see how to use them with some playground examples:
    // Defining a Tuple using parenthesis around the comma-delimited list of values
    let httpError404 = (404, "Not found")
    let someOtherTuple2:(Double, Bool) = (100, false)
    
    // You can decompose a tuple very easily
    var (varStatusCode, varStatusMessage) = httpError404
    
    // Access tuple values with the dot operator followed by their index
    httpError404.0
    httpError404.1
    
    // Alternatively, you can name the elements of a Tuple
    let namedTuple = (statusCode: 404, message: "Not found")
    namedTuple.statusCode == namedTuple.0
    namedTuple.message == namedTuple.1
    
    I was surprised with Beta3 there are some lacking support for tuple an array/dictionary. In [1] like we define myArray, I'd expect the definition plus instantiation with tuple to work. In [2], not being to append a tuple.
    var myArray = [String]()
    // [1] Error in playground: invalid use of () to call a value of non-function type
    var array1 = [(String, String)]()
    
    var array1: [(String, String)] = []
    array1 +=  ("1", "2")
    array1
    
    var array2:[(String, String)] = []
    var tuple = ("fddfd", "fdfdf")
    // [2] Error in playgroungd: Missing argument #2 in call
    array2.append(tuple)
    array2 += tuple
    array2
    
    // Correct in playgroung
    var array3:[String] = []
    array3.append("ddd")
    
    Another good usage of tuple is with switch statement. You may need to differentiate switch cases depending on 2 criteria. Like in this sample code where the image name dependant on atmospheric measurement plus daylight factor. Tuple can also be used to enumerate through a dictionary
    var dict = ["onekey":"onevalue", "twokey":"twovalue"]
    
    for (key, value) in dict {
        dict[key] = "assign-me-sth"
        println("\(key):\(value)")
    }
    dict
    


    Let's keep an eye on tuple and array.
    Try it yourself on Playground format and Happy Swift coding!

    Thursday, July 10, 2014

    Markdown plays well with Playground

    Last blog post, I've told you about great learning resources with playgrounds. Searching github, you can have several example already available. I suggest you to build your own. Your playground will be your toolbox. Get inspired, create you own code snippets and ... share it!

    I started Swift with the Swift guided tour. I was impressed you could read and try code snippet at the same time with Xcode. Excellent idea! Googling around, I bumped into this nice project swift-playground-builder which takes markdown document and generate a playground. How does it do it? simple Playground is a folder with xml files... But it looks like magic, you can create your doc à la "guided tour".


    Wednesday, July 9, 2014

    Switch to Swift

    June 2nd 2014 at WWDC, Apple gave birth to Swift: a brand new language for developing iOS and Mac apps! Much ink has been spilled since then, comparing Swift to other languages. And yes, there is some family resemblance. Some see it as a disadvantage, see Ash furrow blog post, but to me, it has advantages: bring all the good stuff together :)

    REPL && Playground

    From Apple: "Xcode’s debugger includes an interactive version of the Swift language, known as the REPL (Read-Eval-Print-Loop). Use Swift syntax to evaluate and interact with your running app or write new code in a script-like environment. The REPL is available from within LLDB in Xcode’s console, or from Terminal."

    So you can use Swift in your terminal! Very convenient to try this out, get your hands into Swift.
    
    > sudo xcode-select -switch /Applications/Xcode6-Beta.app/Contents/Developer
    > xcrun swift
    

    And even better, you can run in your debugger.

    But what I like best is Playground. What an amazing feature being able to type a line of code and see the result immediately. Having a script like experience with a compiled language. Not a new idea, Scala had his worksheet, but what I really like with Playground is that you can also play with graphical object. Apple released a complete Swift book and a Guided tour playground where you can play with the language as you learn it. Pretty neat!

    At the time of writing, with XcodeBeta2 there is still bugs and I manage to get it crashed a dozen of time today. But no doubt it will improve.

    Objective-C / Swift interoperability

    From the start, Swift was designed to work with Objective-C. From Apple Inc. “The Swift Programming Language.” iBooks:
    “ [Swift] provides seamless access to existing Cocoa frameworks and mix-and-match interoperability with Objective-C code. ”

    There is also a whole iBook dedicated to compatibility matters, "Using Swift with coco and Objective-C". If you're part of apple iOS developer program, the videos to watch on compatibility matter are session 406 "Integrating Swift with Objective-C" and session 407 "Swift interoperability in Depth".

    Stay tuned, I will write more on the topic really soon with short examples on playground off course ;)

    First impressions

    Swift is here to stay, that's for sure. It's already broadly used. Github will tell you.
    Benchmark are not so good so far but no doubt things will improve.

    Ready to switch to Swift?

    At AeroGear, we're on the starting block, getting ready for iOS8 official announcement. Don't want to unveil too soon, but our 2.0 release is going to be Swifty... No Apple way here, we're open! If you want to know more, read the mailing list aerogear-dev@lists.jboss.org and send your feedback!

    Thursday, June 19, 2014

    AeroGear iOS 1.6 is out!

    With AeroGear-iOS 1.6 out, we have a complete OAuth2 authorization code grant support (a few features were started in 1.4). Ready to go social? Take a tour and read our documentation.

    What’s new?

    • Refresh token grant was added. Pipe seamlessly integrate with OAuth2, providing transparent refresh token requests when needed.

    • Revoke token request on demand.

    • AccountManager to manage multiple accounts and be able to store them permanently on your local phone allowing the user to grant access only when the application was first launched.

    • Social support tested with Google, Facebook and Keycloak. If you want to compare the different approaches on how to post to Facebook please read this blog post as always plenty of cookbook examples Shoot’sShare enhanced with AccountManager and GoogleDrive with revoke and refresh (Thanks to Yagyesh Agrawal for this contribution).


    Coming along with 1.6 announcement, we also have a new 0.9.1 AeroGear Push Registration release which includes static lib and framework packaging with its HelloWorld Demo and a more complete contact app example already taking advantage of.

    Enjoy our 1.6 journey and as always, we love hearing from you. Go Social, share with us your AeroGear experience on mailing list.

    Wednesday, June 4, 2014

    Different ways to manage Facebook OAuth2 for iOS

    Take this old Hinduism saying: "There is one truth, reached by many paths". Very often there are many ways to achieve the same end result. In our OAuth2 blog serie, we've seen how to use OAuth2 to grant access to GoogleDrive, how to transparently renew grant after expiration time, let's now focus on another OAuth2 main provider: Facebook.

    Let's explore different ways of accessing your Facebook wall.

    Let's start with ...

    Using Social.framework

    Since iOS 6, Apple added the support of Facebook in its Social.framework. With the built-in support, end user has to register their Facebook account into iOS settings and grant access.
    - (IBAction)postToFacebook:(id)sender {
        
        ACAccountStore *accountStore = [[ACAccountStore alloc] init];
        ACAccountType *facebookAccountType = [accountStore
                                              accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook];
        
        __block ACAccount *facebookAccount;
        // Specify App ID and permissions
        NSDictionary *options = @{ACFacebookAppIdKey: @"xxxxx",                        // [1]
                                  ACFacebookPermissionsKey: @[@"publish_actions"],     // [2]
                                  @"ACFacebookAudienceKey": ACFacebookAudienceFriends};
        
        [accountStore requestAccessToAccountsWithType:facebookAccountType
                                              options:options completion:^(BOOL granted, NSError *e) {
                                                  if (granted) {                      // [3]
                                                      NSArray *accounts = [accountStore
                                                                           accountsWithAccountType:facebookAccountType];
                                                      facebookAccount = [accounts lastObject];
                                                      // Get the access token, could be used in other scenarios
                                                      ACAccountCredential *fbCredential = [facebookAccount credential];
                                                      NSString *accessToken = [fbCredential oauthToken];
                                                      
                                                      NSLog(@"...Facebook Access Token: %@", accessToken);
                                                      
                                                  } else {
                                                      // Handle Failure
                                                  }
                                              }];
       
        if([SLComposeViewController isAvailableForServiceType:SLServiceTypeFacebook]) {   // [4]
            SLComposeViewController *controller = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeFacebook];
            
            [controller setInitialText:@"First post from my iPhone app"];
            [self presentViewController:controller animated:YES completion:Nil];
        }
    }
    

    In [1], you specify your Facebook app id and you specify the permissions needed [2].
    In [3], you're in the callback from the authorization. As there is only one callback you need to test for successful grant or not. You can then embed your logic in it. As an exemple I show how you can retrieve access token and just logged them.
    In [4], you can wait until the asynch authorization request has ended and do you Facebook post using SLComposeViewController. Note when doing you POST request it's the framework itself that pass the access token into the HTTP header. Easy peasy nothing to do on your side.

    Pro and cons:
    As a pre-requisite, the end user need to be registered in Setting
    Facebook credentials are stored in your phone
    Only build-in support provider like Twitter, Facebook (for now)
    Note all Facebook actions are supported, for more advanced features you will need to go the 2 other ways.

    Using Facebook iOS SDK

    The good part of Facebook SDK is that it comes bundled with a bunch of good exemples, install it and follow the instruction as described here. For the purpose of our comparison exercice I've chosen to extract some code snipet from the HelloFacebookSample which shows you how handle OAuth2 authenticate and authorize using external browser. To deal with going back to main app from external browser, you work with URL schema and you need the following setting in your property file:
     CFBundleURLTypes
     
      
       CFBundleURLSchemes
       
        fbCLIENT_APP_ID
       
      
     
    
    
    The Facebook SDK provides you predefined view and controller to achieve the different actions. For example with FBLoginView you can create a login button. Like shown below:
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        // Create Login View so that the app will be granted "status_update" permission.
        FBLoginView *loginview = [[FBLoginView alloc] init];
        loginview.frame = CGRectOffset(loginview.frame, 5, 5);
        loginview.delegate = self;
        [self.view addSubview:loginview];
        [loginview sizeToFit];
    }
    
    Implementing the FBLoginViewDelegate delegate, you can implement the callback method as required. Notice here we use a delegate vs a callback block approach for async call. My preference goes toward block if you remember my previous blog post, but yeah just a matter of preferences ;)
    @protocol FBLoginViewDelegate 
    - (void)loginViewShowingLoggedInUser:(FBLoginView *)loginView;
    
    - (void)loginViewFetchedUserInfo:(FBLoginView *)loginView
                                user:(id)user;
    
    - (void)loginViewShowingLoggedOutUser:(FBLoginView *)loginView;
    
    - (void)loginView:(FBLoginView *)loginView
          handleError:(NSError *)error;
    
    @end
    
    Pro and cons:
    Another SDK to learn,
    a Facebook specific dependency to add

    Using AeroGear

    Using AeroGear iOS OAuth2 adapter, you can log in to any OAuth2 provider, don't need to include Facebook iOS sdk or Social.framework. The main advantage is if your Shoot'nShare app wants to share a photo to different providers like Google+, Facebook you don't need to work with each sdk.
    -(void)oauthFacebook {
        // start up the authorization process
        AGAuthorizer* authorizer = [AGAuthorizer authorizer];
        
        // TODO replace XXX -> secret and YYY -> your app id in this file + plist file
        id _facebookAuthzModule = [authorizer authz:^(id config) {  // [1]
            config.name = @"restAuthMod";
            config.baseURL = [[NSURL alloc] init];
            config.authzEndpoint = @"https://www.facebook.com/dialog/oauth";
            config.accessTokenEndpoint = @"https://graph.facebook.com/oauth/access_token";
            config.clientId = @"YYY";
            config.clientSecret = @"XXX";
            config.redirectURL = @"fbYYY://authorize/";
            config.scopes = @[@"user_friends, publish_actions"];
            config.type = @"AG_OAUTH2_FACEBOOK";
        }];
        [_facebookAuthzModule requestAccessSuccess:^(id response) {              // [2]
            [self shareWithFacebook];
            NSLog(@"Success to authorize %@", response);
            
        } failure:^(NSError *error) {
            NSLog(@"Failure to authorize");
        }];
    }
    
    In [1], you configure your OAuth2 provider. In [2], you request the grant access. If this is the first time, the app open an external browser to start OAuth2 danse. Enter login/password if not already logged and grant access. Once access is granted you will be forwarded back to Shoot'nshare app using URL schema. The main advantage of using external browser rather than embedded view is that it's keep your login/password safe. No code in between embedded view and client app that you can't control.
    Some configuration is needed in your Shoot-Info.plist for URL schema:
        CFBundleURLTypes
        
            
                CFBundleURLName
                fb240176532852375
                CFBundleURLSchemes
                
                    fb240176532852375
                
            
        
    
    Ready to actually work with Pipe objects, also part of AeroGear iOS, Pipe is a connection abstraction that let you do CRUD operation asynchronously over the network:
    -(void)shareWithFacebook {
            // extract the image filename
            NSString *filename = ...;
            
            // the Facebook API base URL, you need to
            NSURL *gUrl = [NSURL URLWithString:@"https://graph.facebook.com/me/"];
            
            AGPipeline* gPipeline = [AGPipeline pipelineWithBaseURL:gUrl];
            
            // set up upload pipe
            id uploadPipe = [gPipeline pipe:^(id config) {   // [1]
                [config setName:@"photos"];
                [config setAuthzModule:_facebookAuthzModule];
            }];
            
            // Get currently displayed image
            NSData *imageData = ...;
            
            // set up payload with the image
            AGFileDataPart *dataPart = [[AGFileDataPart alloc] initWithFileData:imageData
                                                                           name:@"image"
                                                                       fileName:filename
                                                                       mimeType:@"image/jpeg"];
            NSDictionary *dict = @{@"data:": dataPart};
            
            // show a progress indicator
            [uploadPipe setUploadProgressBlock:^(NSURLSession *session, NSURLSessionTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    [SVProgressHUD showProgress:(totalBytesSent/(float)totalBytesExpectedToSend) status:@"uploading, please wait"];
                });
            }];
            
            // upload file
            [uploadPipe save:dict success:^(id responseObject) {   // [2]
                [SVProgressHUD showSuccessWithStatus:@"Successfully uploaded!"];
            } failure:^(NSError *error) {
                [SVProgressHUD showErrorWithStatus:@"Failed to upload!"];
            }];
        }
    
    
    In [1] I just instantiate a Pipe that I can use to save my picture [2].
    Pros and cons:
    AeroGear OAuth2 dependency to add but cross providers
    More verbose in term of configuration but you can do whatever you want once you get the access token, it's just plain HTTP REST calls.

    You can find the complete source code in aerogear-ios-cookbook git repo, going to the Shoot recipe.

    Conclusion

    Because I'm core committer on the AeroGear project, my view is biased of course. I'd go for the AeroGear way :) the main reason being it gives you the most flexibility without having to link to several SDKs. The built-in solution is also very tempting but limited besides the fact it's required the end user a previous registration.
    Many ways to achieve the same end result, and looking deeper you may find other ways. I'm curious to hear about your discoveries and how we could improve AeroGear iOS libs. Share your thoughts, email AeroGear.