Twitter, Android and Phonegap

Standard

After coming out of depression and one year of mental inactivity, it is good to know that finally things are taking shape for me. Thanks to this one girl who has been an instant and a constant motivator. The gist of it is, I’m now an Android/iPhone developer and working on some good projects and one of these projects required me to play with PhoneGap on Android.

Lets talk about PhoneGap a bit. <wiki>PhoneGap is a mobile development framework produced by Nitobi, purchased by Adobe Systems. It enables software programmers to build applications for mobile devices using JavaScript, HTML5, and CSS3, instead of device-specific languages such as Objective-C. The resulting applications are hybrid, meaning that they are neither truly native (because all layout rendering is done via web views instead of the platform’s native UI framework) nor purely web-based (because they are not just web apps, but are packaged as apps for distribution and have access to native device APIs).</wiki>.

What this basically means is, I can use existing web technologies like JavaScript/jQuery, HTML and CSS to make an app for Android (and iOS too). For a starter course, you should refer this how-to guide. Assuming that you’ve set up your environment as described in the link, I’ll directly jump to the juicy part. The part where you play with Twitter. Now, after searching the web for accessing Twitter using PhoneGap, I came across many tutorials but due to some complications on my platform, I wasn’t able to follow them. Finally, the discussion forums at Twitter developers pointed me to an awesome library that handles interacting with Twitter blissfully.

In this post, I’ll walk through how I used the Codebird library along with PhoneGap and jQuery to make a Twitter client for Android. The good thing about it is, we just need to know JavaScript and HTML and some Android experience. Lets begin.

Now that you have set up your environment as directed in the how-to guide, download Codebird.js (and its companion sha1.js) and put it in your assets directory of the app. Create a new js file where we will write the bulk of our code. I named it “something.js”. Every PhoneGap app starts with setting up an event listener that waits for device to be ready (i.e. waits for PhoneGap to load), so we write:

function onLoad() {
    document.addEventListener("deviceready", onDeviceReady, false);
}

Web developers would know how to call this method when the page loads

<body onload="onLoad()">...</body>

The second argument to the event listener is the function that is called once the device is ready. I’ll show the code first so that you can copy it and analyze it along the way.

var cb = new Codebird;
function onDeviceReady() {
    cb.setConsumerKey("YOUR CONSUMER KEY", "YOUR CONSUMER SECRET");
    // check if the we already have access tokens
   if(localStorage.accessToken && localStorage.tokenSecret) {
        // then directly setToken() and read the timeline
        cb.setToken(localStorage.accessToken, localStorage.tokenSecret);
        cb.__call(
            "statuses_homeTimeline", {},
            function (reply) {
                for(var key in reply){
                    $('#timeline').append('<li><p>'+ reply[key].user["name"] + ': ' + parseHashtags(parseUsers(parseLinks(reply[key].text))) +'</p></li>');
                }
            }                        
        ); 
    } else { // authorize the user and ask her to get the pin.
        cb.__call(
            "oauth_requestToken",
                {oauth_callback: "oob"},
                function (reply) {
                    // nailed it!
                       cb.setToken(reply.oauth_token, reply.oauth_token_secret);
                       cb.__call(
                    "oauth_authorize",    {},
                    function (auth_url) {
                        var ref = window.open(auth_url, '_blank');
                        ref.addEventListener('exit', authorize);
                    }
                );
            }
        );
    }
}

The first line of the code, var cb = new Codebird; defines a new object of the Codebird class from the codebird library (codebird.js). This object is then used to call all the subroutines of the class. Inside the onDeviceReady routine, we first set the Consumer Key and Consumer Secret. To obtain your consumer key and token, you need to register your app with Twitter first. The setConsumerToken sets the consumer key and secret. For now, we will ignore the code that is written inside the “if” conditional (it is elucidated later in this post) and jump to the code inside the “else”.

Inside the else block, we authorize using a method known as Out-Of-Bounds authorization wherein, we are presented with a unique pin number to authorize our app. This basically means that we send a request to a specific URL (“oauth_requestToken”) to get OAuth specific request tokens. We then set the request tokens and then open an authorization window where the user is asked to log into her Twitter account. After the user has logged in, they are given an unique pin number.

Notice the call to window.open. This is the invocation of the PhoneGap’s InAppBrowser. It opens up a browser INSIDE the app (or rather, as a PART of the app) and assigns it to an object. The next line ref.addEventListener adds an event listener where it waits for the user to exit the InAppBrowser instance and calls another function(authorize) to proceed with the next steps.

The next step is fairly simple, you need to ask the user to verify her pin. In your HTML, you can make an input box where the user enters her pin or you can (like I did) use the JavaScript “prompt” box. I’ll again put the code first

function authorize() {
       var pin = prompt("Enter pin");
       if(pin!=null && pin!=""){ 
         cb.__call(
               "oauth_accessToken", {oauth_verifier: pin},
               function(reply) {
                   cb.setToken(reply.oauth_token, reply.oauth_token_secret);
                   localStorage.accessToken = reply.oauth_token;
                   localStorage.tokenSecret = reply.oauth_token_secret; // twitter does not expire tokens, so no refresh token is required. Bingo!
                cb.__call(
                    "statuses_homeTimeline", {},
                    function (reply) {
                        var i = 0;
                        for (var key in reply) {
                            $('#timeline').append('<li><p>'+ reply[key].user["name"] + ': ' + parseHashtags(parseUsers(parseLinks(reply[key].text))) +'</p></li>');
                        }
                    }                        
                );
            }
        );    
    }
}

We again use the codebird library to authorize the pin entered (oauth_verifier) and set the access tokens we receive in reply. Just like a full fledged browser, InAppBrowser too can store data (in form of cookies or in the underlying filesystem or locally in the browser itself). We need to store the authentication tokens received lest the user has to log into Twitter every time she runs the app. I used the local storage method. Notice the line:

localStorage.accessToken = reply.oauth_token;
localStorage.tokenSecret = reply.oauth_token_secret;

We save the authorization token and secret in the local cache of the browser for later use. Lastly, when we are authorized, the user can access her Twitter timeline (note that the codebird library is not limited to accessing timeline only, we can do everything with it, I used the timeline as an example).

Going back to the onDeviceReady function, the use of “if” block is now apparent. The browser now has the verified OAuth tokens and since Twitter DOES NOT expire tokens, we can directly set them without having to go through the whole authorization process again.

The “helper” functions that you can see inside the “for” loop: “parseHashtags” etc are used to format the data received from the call to Twitter API, I copied them from here and they look like:

function parseLinks(text) {
    var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
    return text.replace(exp,'<a href="$1" target="_blank">$1</a>');
}

// function for parsing @username
function parseUsers(text) {
    var exp = /@(\w+)/ig;
    return text.replace(exp,'<a href="https://twitter.com/$1" target="_blank">@$1</a>');
}

// function for parsing #hashtags
function parseHashtags(text) {
    var exp = /#(\w+)/ig;
    return text.replace(exp,'<a href="https://twitter.com/search?q=%23$1" target="_blank">#$1</a>');
}

You should peruse the Codebird documentation for more awesome features such as posting a tweet or seeing user’s followers/following lists etc. Before the I conclude this post, take a look at the line that says:

$('#timeline').append('<li>'+ parseHashtags(parseUsers(parseLinks(reply[key].text))) +'</li>');

This is a call to the jQuery append function and it appends to a HTML <ul> tag having an ID “timeline”. The HTML code is as shown below:

<!DOCTYPE html>
<html>
  <head>
    <title>GapMyTweets</title>
    <script type="text/javascript" charset="utf-8" src="cordova.js"></script>
    <script type="text/javascript" charset="utf-8" src="jquery-min.js"></script>
    <script type="text/javascript" charset="utf-8" src="sha1.js"></script>
    <script type="text/javascript" charset="utf-8" src="codebird.js"></script>    
    <script type="text/javascript" charset="utf-8" src="some.js"></script>
  </head>
  <body onload="onLoad()">
      <ul id="timeline">
      <ul>
  </body>
</html>

Though, I’d prefer using MooTools over jQuery but since all the help on the Internet use jQuery so I didn’t want to break the trend… yet.

Well that was all for this post. Tweeting was easier than it seemed. The HTML is devoid of any fancy CSS and stuff but you can show off your CSS skills for ornamentation. If you can handle animations with jQuery (its more fun in MooTools though) you can make an awesome Twitter client with tweets flying in and out of your screen and everything translucent (dream world mode: on).

UPDATE: the complete JavaScript can be found here.

UPDATE 2: For no PIN based authentication using a callback URL, take a look at this implementation.

Advertisements

24 thoughts on “Twitter, Android and Phonegap

    • hi 🙂
      umm, I don’t quite understand your comment. Are you pointing out an error (which is awesome cuz I have to update the app anyway) or are you trying to ask something?

      • Sorry about that. LOL. Yeah. I’m asking a question. It seems like there’s no oauth_token being returned therefore calling the url (that I pointed out) without any oauth_token parameter value. Maybe phonegap firewall blocks me to connect to Twitter API?
        Do I need to set ” to ” in www/config.xml? I found that the firewall maybe a problem to this but can’t try the code right at the moment.

        On another note, nice WordPress theme!!! Kudos! 🙂

      • no, you don’t need to set that. At least I didn’t need to set that in config.xml. The Adobe link that I shared in the post is exactly what you’ll need to set up your Phonegap. Maybe you’re not setting the access tokens (that you get when you register your app with Twitter) right. In that case, the oauth_token is not set by the Twitter API.

      • it can be anything… try “code://ninja” (I actually used it in my project). Refer to the updates towards the end of this post. There is a GitHub link where I used callback URL method to authenticate with Twitter. The trick is to detect the state change of the InAppBrowser and when the URL matches your callback URL, you should extract the tokens from the URL and close the InAppBrowser.

      • Oh, another thing. I did not use the InAppBrowser plugin so it runs a browser instead of the inappbrowser. Is that a factor?
        Sorry to hound you with questions since I still have to wait until I get home to try this out.

      • no need to be sorry 🙂
        You have to use the InAppBrowser. The InAppBrowser opens up as a part of the app and hence you can easily detect state changes and all. You just have to edit your config.xml a bit. Just add:

        <feature name="InAppBrowser"
        <param name="android-package" value="org.apache.cordova.InAppBrowser"/
        </feature

        and you’ll be fine.

      • Ok, but I think I set everything up correctly. Is this the only line where to set the keys? ‘cb.setConsumerKey(“THE KEY TO”, “YOUR SECRET”);’

        BTW, I’m using the callback url instead of the PIN-based authorization.

      • Well, that would let you authorize using the PIN method. For non-PIN authentication, you must specify a callback URL. Try and run my gist I linked in update-2 towards the end of the post. You can directly copy paste it in your project if you’ve set up your project according to that Adobe link.

    • I think I already know the problem here. It’s the callback url afterall. I changed the callback to “http://www.neilmarion.com” (bec twitter does not allow “http://localhost/”) and also changed the callback field in the Application settings in the Twitter app. And yeah, I was redirected to the correct auth_url. But after pressing “authorize app” the inAppBrowser was redirected to the site “http://www.neilmarion.com”. Maybe I just need to change the parameter of iABObject.url.match() to close the inAppBrowser after authorization. Right?

      • yup… you need to change the parameter of the match function. Match it to “http://www.neilmarion.com”. Although Twitter has never said that it does not allow localhost. The gist I posted uses localhost and the app that I officially made uses “code://ninja”. What basically happens is, any app interacting with the Twitter API sends a callback URL to the API. The API then appends appropraite tokens in the URL provided by the app. Now, its up to the app to extract the tokens from the callback URL. As I’m more of a C programmer, check out the definition of “value-result argument” of a function.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s