Monday, August 5, 2013

Connecting an iOS App to Django-Tastypie



Developing an iOS App sometimes needs a REST API Server to communicate with and handle most of the app’s data. So how should we start in developing the connection between an iOS app to a REST API Server?

I have developed a few iOS apps and basically the basic workflow I currently have in developing that kind of app is:
1.     Use a networking library to connect to the web service.
2.     Download the server response (JSON, XML and etc.).
3.     Parse the response by transforming it into an NSDictionary or NSArray.
4.     Handle or display the data retrieved from the NSDictionary or NSArray.
And "poof" you now have a working iOS app connecting to a REST API Server. Sounds simple right? So to start the tutorial lets first talk about the needed tools in order to start the app development.

XCode and Objective-C
You are going to develop an iOS app surely you should know about XCode and Objective-C? So for those who still don’t have any idea about XCode and Objective-C there are quite a lot of great tutorials on the web probably you could start on Apple’s “Your First iOS App”.

Networking Library
Your app couldn’t live without networking and to add to it, networking is hard. There are quite various parts and factors that you really need to understand and consider to make it work.  There is already NSURLConnection on Apple’s Foundation framework to handle networking however like I said it’s hard to understand. Fortunately, a quite few open-source libraries have emerged to make networking easy. Thank God for open-source
One such library is AFNetworking, created and maintained by the awesome people of Gowalla, which its former name is “Alamofire”(Where they based “AF” for the name AFNetworking, cool). It is an easy to use network API for iOS. It contains everything you need to interface with online resources, web services to file downloads. It also helps you ensure that your UI is still responsive even when your app is in the middle of a large download. There are other networking libraries out there but I find AFNetworking as the library that is fit for our app.

REST API Server
Aside from being an iOS developer I am also a Python-Django Developer. So I have decided to use Python as the language and Django as its web framework for the REST API Server. And yes, I mentioned on the tutorial’s title Django so probably most of you who are viewing this page should have already an idea regarding about Python and Django. If not there are quite a few resources on the web regarding Python and Django. 
 So moving on, basically we need to create an API framework from scratch. Just kidding, why create when there are open-sourced API frameworks ready to be implemented on your Django app, unless you really want to create your own REST API framework for your app (so hardcore… lol). Choosing from the list of known API frameworks, we previously chose django-rest-framework (version 0.4.0, I know it’s an old version…lol) to be used on our REST API Server however just recently we started working on Django-TastyPie. So why use Django-TastyPie? I guess they have good documentation, found out it was basically mostly used to connect to an iOS app, good community and other reasons are found on there “Why TastyPie” section on github.

Getting Started with AFNetworking

Okay, we already talked about the tools used for the app development. So to start the app development, I guess you already have created a new project on XCode. If not, here is a tutorial on creating a new project on XCode. After creating a new project, download and add the AFNetworking library to your project. You could add the library by just dragging the folder into your XCode project, as shown on the image below.


After adding up you could probably develop your own Client class to which you could use the AFNetworking library. Here is a sample ClientSDK developed by my fellow developer, Mr. JA Arce.


+ (void)query: (NSString *)queryPath: (NSString *)method: (NSDictionary *)data: (void(^)(NSMutableDictionary* data))callback {
    /* querypath -- The relative URL of the resource ex. article, telemedicine, etc.
       method    -- Either POST, PUT or GET it's up to you baby!
       data      -- it'll be treated as a POST Data or Query String depending on the method.
       callback  -- callback function/completion block.
     */
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    NSString *urlPath = [NSString stringWithFormat:@"%@%@/",API_URL,queryPath];
    NSURL *url = [[NSURL alloc] initWithString:urlPath];
   
    AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
   
[httpClient setDefaultHeader:@"Authorization" value:[NSString stringWithFormat:@"OAuth %@", [userDefaults objectForKey:@"access_token"]]];
    if ([method isEqualToString:@"GET"]) {
        [httpClient getPath:urlPath parameters:data success:^(AFHTTPRequestOperation *operation, id responseObject) {
            callback([self parseResponseToDict:operation.responseData]);
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            callback([self parseResponseToDict:operation.responseData]);
        }];
    } else if ([method isEqualToString:@"POST"]) {
        [httpClient postPath:urlPath parameters:data success:^(AFHTTPRequestOperation *operation, id responseObject) {
            callback([self parseResponseToDict:operation.responseData]);
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            callback([self parseResponseToDict:operation.responseData]);
        }];
    } else {
        //whatever!? :P
    }
}


The query method, as shown above basically connects to the API Rest Server with the url, urlPath string variable. The urlPath is composed of the API_URL (The base  URL of the API Rest Server) and the queryPath (The relative URL). Basically on userDefaults the access_token  can be found which was stored during authentication, I will discuss later on how the Access Token was created. The Access token is used and placed on the request header as shown on this line of code.

[httpClient setDefaultHeader:@"Authorization" value:[NSString stringWithFormat:@"OAuth %@", [userDefaults objectForKey:@"access_token"]]];

The Authorization request-header would be checked on the REST API Server, which I will discuss later on. The request method is then checked if it’s either a GET or a POST request. Then after which the client then connects to the REST API Server. The success and failure block will run depending on the response of the REST API server. The JSON response would then be parsed and transform into a NSMutableDictionary using the parseResponseToDict method found below.

+ (NSMutableDictionary *)parseResponseToDict: (NSData *)responseData {
    NSError *err;
    NSString *responseStr = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
    NSData *data = [responseStr dataUsingEncoding:NSUTF8StringEncoding];
   
    NSMutableDictionary *jsonDictionary = [NSJSONSerialization JSONObjectWithData:data
                                                                          options:0
                                                                            error:&err];
    return jsonDictionary;
}

So basically you could use the code above like the one shown below which fetches for the user data.

- (void)fetchUserData 
{
    NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
    NSString *urlPath = [NSString stringWithFormat:@"user/%@", [userDefault objectForKey:@"user_id"]];

    [PClientSDK query:urlPath :@"GET" :nil :^(NSMutableDictionary *data) {
        NSString *displayName = [NSString stringWithFormat:@"%@ %@", [data objectForKey:@"first_name"], [data objectForKey:@"last_name"]];
        [self.displayNameLbl setText:displayName];
    }];
}

The query method is found on the PClientSDK class so we can call it as shown above. Basically the code uses a GET request method and if the connection is successful it will display the first_name and last_name found on the data NSMutableDictionary. As you can see AFNetworking is easy to use. You could explore more methods on the library like AFJSONRequestOperation and many more.

Getting Started with Django-TastyPie
So now let us start discussing on how to implement Django-TastyPie. I assume you already have a working Django project with models and other stuff needed to run the project app. First we need to install Django-TastyPie:

$ pip install django-tastypie

Then let us use the built-in Django User Model as a sample. We then create a file named api.py and  there we can create a Resource for the User Model.

class UserResource(ModelResource):
    class Meta:
        queryset = User.objects.all()
        resource_name = 'user'
        authorization = DjangoAuthorization()
        authentication = OAuth20Authentication()

You could check the documentation of TastyPie regarding the fields on class Meta. However I will discuss the authentication field, which is used to verify a certain user and validate their access to the API. Remember the Access Token we added on the Authorization request-header, it was used on the custom authentication as shown below.

class OAuth20Authentication(Authentication):
    def __init__(self, realm='API'):
        self.realm = realm

    def is_authenticated(self, request, **kwargs):
        """
        Verify 2-legged oauth request. Parameters accepted as
        values in "Authorization" header, or as a GET request
        or in a POST body.
        """
        logging.info("OAuth20Authentication")

        try:
            key = request.GET.get('oauth_consumer_key')
            if not key:
                key = request.POST.get('oauth_consumer_key')
            if not key:
                auth_header_value = request.META.get('HTTP_AUTHORIZATION')
                if auth_header_value:
                    key = auth_header_value.split(' ')[1]
            if not key:
                logging.error('OAuth20Authentication. No consumer_key found.')
                return None
            """
            If verify_access_token() does not pass, it will raise an error
            """
            token = verify_access_token(key)

            # If OAuth authentication is successful, set the request user to the token user for authorization
            request.user = token.user

            # If OAuth authentication is successful, set oauth_consumer_key on request in case we need it later
            request.META['oauth_consumer_key'] = key
            return True
        except KeyError, e:
            logging.exception("Error in OAuth20Authentication.")
            request.user = AnonymousUser()
            return False
        except Exception, e:
            logging.exception("Error in OAuth20Authentication.")
            return False
        return True

def verify_access_token(key):
    # Check if key is in AccessToken key
    try:
        token = AccessToken.objects.get(token=key)

        # Check if token has expired
        if token.expires < timezone.now():
            raise OAuthError('AccessToken has expired.')
    except AccessToken.DoesNotExist, e:
        raise OAuthError("AccessToken not found at all.")

    logging.info('Valid access')
    return token

Basically the custom authentication just checks for a provided HTTP_AUTHORIZATION and looks up to see if the token is a valid OAuth Access Token. If ever it fails it will return an error. So every time a request to the REST API Server is called the custom authentication would then run and authenticates if the request is valid or not.

After adding up the resources we then update the urls.py.

from tastypie.api import Api
from api import UserResource

v1_api = Api(api_name='v1')
v1_api.register(UserResource())

urlpatterns += patterns("",
    url(r'^api/', include(v1_api.urls)),
}

Then you can curl to the server the URL (assuming you are running it on localhost) to retrieve the users data which is formatted on json depending on what you added on the format GET parameter.

curl -v -H "Authorization: OAuth " http://localhost:8000/api/v1/users/?format=json

You would then have a JSON response upon calling the URL on the REST API Server.  Then all you have to do is call the URLs on your iOS App. But wait, how was the Access token created? Basically we installed django-oauth2-provider also to handle OAuth2.0 on our REST API Server. Just read the docs on installing it. And after installing it we need to add and create a method on our Client Class for authentication.

#define CLIENT_ID @"#####################"
#define CLIENT_SECRET @"##########################"
#define OAUTH_URL @"http://localhost:8000/oauth2/access_token/"

+ (void)authenticate: (NSString *)username: (NSString *)password: (void(^)(NSMutableDictionary* data))callback {
    // username -- Username of the user in NSString format
    // password -- Password of the user in NSString format
    // callback -- callback function/completion block.
   
    NSURL *url = [[NSURL alloc] initWithString:OAUTH_URL];
    NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
                            CLIENT_ID    , @"client_id",
                            CLIENT_SECRET, @"client_secret",
                            @"password"  , @"grant_type",
                            username     , @"username",
                            password     , @"password",
                            @"write"     , @"scope"
                            , nil];
   
    AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
    [httpClient postPath:@"" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
            callback([self parseResponseToDict:operation.responseData]);
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            callback([self parseResponseToDict:operation.responseData]);
        }
     ];
}

Then call the method on your login view.

- (void)loginUser {
    NSString *username = self.usernameIn.text;
    NSString *password = self.passwordIn.text;
    [PClientSDK authenticate:username :password :^(NSMutableDictionary *data) {
        if (!data) {
            [Utils createAlert:@"An Unexpected Error Occured." :@"Error Message" :@"Ok"];
        } else if ([data objectForKey:@"error"]) {
            [Utils createAlert:@"Invalid Username/Password." :@"Error Message" :@"Ok"];
        } else {
            NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
            [userDefaults setObject:[data objectForKey:@"access_token"] forKey:@"access_token"];
            [userDefaults setObject:[data objectForKey:@"user_id"] forKey:@"user_id"];
            [self navigateToDashboard];
        }
    }];
}

As you could see on the above code the string username and password are passed on the authenticate method on the PClientSDK. Then checks the values returned by the server on the data NSMutableDictionary if it is valid or not. If it is valid it will store the user_id and access_token created for that user on NSUserDefaults. Then everytime a request to the server is called the access_token on NSUserDefaults would be needed.

Simple right? Now you can connect the iOS App to your Django app. whew





3 comments:

  1. I n this regards it is a wise idea to have a firm grasp of Abu Dhabi Website Design the fundamentals before venturing off into more advanced courses.

    ReplyDelete
  2. Regular people call for savings and also other cheap small business arrivals. For the reason that they will journey over a day-to-day foundation. Number of air flow arrivals companies take into account these types of people while many do not.discount travel

    ReplyDelete
  3. Website development, the speediest growing segment one of the different procedures regarding video pattern, message or calls on the imaginative along with wonderful knowledge of the video designer, nonetheless it usually takes developers far from the bodily marketing with that they can are generally almost all acquainted. Instead, website development difficulties the generation regarding eye-catching along with evocative types solely in the electric, electronic digital moderate.CMS for html sites

    ReplyDelete