Sign In Start Free Trial
Account

Add to playlist

Create a Playlist

Modal Close icon
You need to login to use this feature.
  • Book Overview & Buying Instant PhoneGap Social App Development
  • Table Of Contents Toc
Instant PhoneGap Social App Development

Instant PhoneGap Social App Development

By : Kerri Shotts
close
close
Instant PhoneGap Social App Development

Instant PhoneGap Social App Development

By: Kerri Shotts

Overview of this book

Social media integration in our apps is almost a requirement. Using Phonegap, some plugins, and JSON, we can create an app that consumes Twitter feeds and allows the user to share on Twitter. "Instant PhoneGap Social App Development" shows you how to create compelling mobile apps that integrate with social media based on Phonegap. The book will show you how to consume Twitter feeds and also share content to Twitter using Twitter Web Intents. Using code listings and easy steps, this book will guide you through the process of creating a Phonegap app, adding plugins, and using the Twitter API and Twitter’s web intents. You’ll learn how to install PhoneGap plugins so that you can extend the capabilities of your application. You’ll also be introduced to Twitter’s JSON API and Twitter’s Web Intents which allow the consuming of feed content and the posting of content to Twitter respectively. Along the way, you’ll also learn how to create a cross-platform mobile app that works on iOS and Android.
Table of Contents (7 chapters)
close
close

Implementing the data model


This section describes the implementation of our data model.

Getting ready

We'll be creating two files, twitter.js and twitterUsers.js. Place these in the www/models directory.

How to do it…

Let's start with the twitter.js file:

var TWITTER = TWITTER || {};

As always, we define our namespace; in this case, TWITTER:

TWITTER._baseURL = "http://api.twitter.com/1/";
TWITTER._searchBase = "http://search.twitter.com/";

We define two variables global to the TWITTER namespace: _baseURL and _searchBase. These two URLs point at Twitter's JSON API; the first is for API requests such as user lookups, user streams, and such, while the latter is only for searching. We define them here for two reasons: to make the URLs a little less nasty in the following code, and if Twitter should ever decide to have a different version of the API (and you want to change it), you can do so here.

Next, we define our first object, TwitterUser using the following code snippet:

TWITTER.TwitterUser = function ( theScreenName, completion )
{
    var self = this;
    self._screenName = "";
    self._userData   = {};

We've defined our two properties here, _screenName and _userData. We're using underscores at the front to indicate that these are internal (private) variables that no outside object should access. Instead, an outside object should use the get/set methods we define next:

    self.getScreenName = function ()
    {
      return self._screenName;
    }

This one's simple enough, it just returns the private member when asked. But the next one's more complicated:

    self.setScreenName = function ( theScreenName, completion 
         )
    {
        self._screenName = theScreenName;

Like a normal set method, we've assigned theScreenName to _screenName. But when this happens, we want to load in the user information from Twitter. This is why it is important to have get/set methods in front of private methods; you might just need them to do something important when the value changes or is read.

        var getUserURL = TWITTER._baseURL + 
            "users/lookup.json?screen_name=" + 
            encodeURIComponent(theScreenName);

Here we've defined our URL that we're going to use to ask Twitter to look up the user in question. For more information about how this particular URL works, see the Twitter documentation at https://dev.twitter.com/docs/api/1/get/users/lookup. You can see a full example of what is returned at the bottom of the page.

We use the encodeURIComponent() method to ensure that the text is properly encoded (so that it can handle international characters).

Now that we have our URL, we're going to use another utility function defined for us in PKUTIL (www/framework/utility.js), called loadJSON(). It uses AJAX to send a request to the earlier URL, and Twitter then sends a response back, in the form of JSON. When it is finished, the function will call the completion function we're passing as the second parameter after getUserURL. This method can check if the request succeeded or not, and set any private members that are necessary. We'll also call the completion function passed to the setScreenName() method.

        PKUTIL.loadJSON ( getUserURL, function ( 
                success, data )
          {
            if (success)
            {
              self._userData = data;

If success is true, then the JSON has been properly returned and parsed into the data parameter. We just assign it to the private _userData member.

            }
            else
            {
              self._userData = { "error": "Twitter error; rate 
                  limited?" };

But, if the return value of success is false, then something's gone wrong. Anything could have happened. Twitter might be down (not unheard of), the network connection might have failed, or Twitter might have rate limited us. (For Twitter's error codes, see https://dev.twitter.com/docs/error-codes-responses.) For now, we're just going to assume the latter, but you could certainly build more complicated error-detection schemes to figure out the type of error.

          }
            if (completion)
            {
                completion ();
            }

Finally, regardless of success or failure, we call the completion function passed to us. This completion function is important so that we know when we can safely access the _userData member (via getUserData a little lower).

          }
        );
    }

    self.getProfileImageURL = function ()
    {
        if (self._userData[0])
        {
            return self._userData[0].profile_image_url;
        }
        return "";
    }

The method getProfileImageURL() is a convenience function that returns the user's profile image URL. This is a link to the avatar being used for Twitter. First we check to see if _userData[0] exists, and if it does, return profile_image_url, a value defined by the Twitter API. If it doesn't, we'll just return an empty string.

    self.getUserData = function ()
    {
        return self._userData;
    }

Next, the getUserData() method is used to return the _userData member. If it has been properly loaded, it will have a lot of values in it, all determined by Twitter. If it has failed to load, it'll have an error property in it, and if it hasn't been loaded at all, it'll be empty.

    self.getTimeline = function ( theMaxCount, completion )
    {
        return new TWITTER.TwitterStream ( "@" + 
            self._theScreenName, completion, theMaxCount || 25 
            );
    }

The getTimeline() method is also a convenience function used to get the timeline for the Twitter user. theMaxCount is the maximum number of tweets to return (up to 200), and completion is a function to call when it's all done. We do this by creating a new TwitterStream object (defined later) with the Twitter screen name prepended by an @ character.

If theMaxCount isn't specified, we use a little || trick to indicate the default value of 25 tweets.

    self.setScreenName ( theScreenName, completion );
}

The last thing we do is actually call the setScreenName() method with the screen name and completion function passed in to the constructor. If you remember your JavaScript, this whole function, while we can think of it as defining an object, is also the constructor of that object. In this case, as soon as you create the TwitterUser object, we'll fire off a request to Twitter to load in the user's data and set it to _userData.

Our next object is the TwitterStream object:

TWITTER.TwitterStream = function ( 
    theScreenNameOrSearchPhrase, completion, theMaxCount )
{
    var self = this;

    self._searchPhrase = "";
    self._stream     = {};
    self._theMaxCount   = 25;

Here we've defined three properties, _searchPhrase, _stream, and _theMaxCount. The _searchPhrase property can either be the screen name of a user or a literal search term, such as a hashtag. The _stream property is the actual collection of tweets obtained from Twitter, and the _theMaxCount property is the maximum number of tweets to ask for. (Keep in mind that Twitter is free to return less than this amount.)

You may ask why we're storing either a search phrase or a screen name. The reason is that we're attempting to promote some code re-use. It's logical to assume that a Twitter stream is a Twitter stream, regardless of how it was found, either by asking for a particular user's stream or by searching for a word. Fair assumption, right?

Yeah, but totally wrong, too. The streams are close enough so that we can work around the differences, but still, not the same. So, even though we're treating them here as one-and-the-same, they really aren't – at least until Twitter decides to change their Search API to better match their non-Search API.

    self.setMaxCount = function ( theMaxCount )
    {
        self._theMaxCount = theMaxCount;
    }

    self.getMaxCount = function ()
    {
        return self._theMaxCount;
    }

Here we have the get/set methods for the _theMaxCount property. All we do is set and retrieve the value. One thing to note is that this should be called before we actually load a stream. This value is part of the ultimate URL we sent to Twitter.

    self.setScreenName = function ( theScreenName )
    {
        self._searchPhrase = "@" + theScreenName;
    }
    
    self.setSearchPhrase = function ( theSearchPhrase )
    {
        self._searchPhrase = theSearchPhrase;
    }
    self.getSearchPhrase = function ()
    {
        return self._searchPhrase;
    }

Notice that we have two set methods that act on the _searchPhrase property while we only have one get method. What we're doing here is permitting someone to call the setScreenName() method without the @ character. The _searchPhrase property will then be set with the @ character prepended to the screen name. The next set method (setSearchPhrase()) is intended to be used when setting real search terms (such as a hashtag).

Internally, we'll use the @ character at the front to mean something special, but you'll see that in a second.

    self.getStream = function ()
    {
        return self._stream;
    }

The getStream() method just returns the _stream property, which if we haven't loaded, will be blank. So let's look at the loadStream() method:

    self.loadStream = function ( completion )
    {
        var theStreamURL;
        var forScreenName = false;

The loadStream() method takes a completion function. We'll call this at the end of the operation no matter what; it lets the rest of our code know when it is safe to access the _stream member via the getStream() method.

The other component is the forScreenName variable; if true, we'll be asking Twitter for the stream that belongs to the screen name stored in the _searchPhrase property. Otherwise, we'll ask Twitter to do an actual search for the _searchPhrase property:

        if (self._searchPhrase.substr(0,1)=="@")
        {
           theStreamURL = TWITTER._baseURL + 
            "statuses/user_timeline.json?include_entities=
             true&include_rts=true&count=" +
             self._theMaxCount + "&screen_name=" + 
                encodeURIComponent(self._searchPhrase);
             forScreenName = true;
        }
        else
        {
            theStreamURL = TWITTER._searchBase + 
                "search.json?q=" + 
                encodeURIComponent(self._searchPhrase) + 
                "&include_entities=true" + 
                "&include_rts=true&rpp=" + self._theMaxCount;
            forScreenName = false;
        }

All we've done so far is defined the theStreamURL property to point either at the Search API (for a search term) or the non-Search API (for a screen name's stream). Next we'll load it with the loadJSON() method using the following code snippet:

        PKUTIL.loadJSON ( theStreamURL, function (success, 
           data)
          {
            if (success)
            {
              if (forScreenName)
              {
                self._stream = data;
              }
              else
              {
                self._stream = data.results;
              }
            }

Here's another reason why we need to know if we're processing for a screen name or for a search: the JSON we get back is slightly different. When searching, Twitter helpfully includes other information (such as the time it took to execute the search). In our case, we're not interested in anything but the results, hence the two separate code paths.

          else
            {
              self._stream = { "error": "Twitter error; rate 
                  limited?" };
             }

Again, if we have a failure, we're assuming that we are rate-limited.

             if (completion)
             {
               completion( self._stream );
             }

When done, we call the completion method, helpfully passing along the data stream.

             }
        );
    }
    self.setSearchPhrase ( theScreenNameOrSearchPhrase );
    self.setMaxCount ( theMaxCount || 25 );
    self.loadStream ( completion );
}

Just like at the end of the previous object, we call some methods at the end of this object too. First we set the incoming search phrase, then we set the maximum number of tweets to return (or 25, if it isn't given to us), and then we call the loadStream() method with the completion function. This means that the moment we create a new TwitterStream object, it's already working on loading all the tweets we'll be wanting to have access to.

We've taken care of almost all our data model requirements, but we've got just a little bit left to do in the twitterUsers.js file; use the following instruction:

TWITTER.users = Array();

First, we create a users() array in the Twitter namespace. We're going to use this to store our predefined Twitter users, which will be loaded with the following loadTwitterUsers() method:

TWITTER.loadTwitterUsers = function ( completion )
{
  TWITTER.users.push ( new TWITTER.TwitterUser ( "photoKandy"  , function () 
    { TWITTER.users.push ( new TWITTER.TwitterUser ( "CNN"  , 
    function ()
      { TWITTER.users.push ( new TWITTER.TwitterUser ( 
      "BBCWorld" , function ()
        { TWITTER.users.push ( new TWITTER.TwitterUser ( 
        "espn", function ()
          { TWITTER.users.push ( new TWITTER.TwitterUser ( 
          "lemondefr", completion ) ); }
          ) ); }
        ) ) ; }
      ) ) ; }
    ) ) ;
}

What we've done here is essentially just chained together five requests for five different Twitter accounts. You can store these in an array and ask for them all at once. But for this our app needs to know when they've all been loaded. You could also do this by using recursion through an array of users, but we'll leave it as an example to you, the reader.

We have implemented our data model and predefined the five Twitter accounts we want to use. We also went over the loadJSON() method in PKUTIL, which helps with the entire process. We've also been introduced to the Twitter API.

There's more…

Before we go on, let's take a look at the loadJSON() method you've been introduced to. It's been added to this project's www/framework/utility.js file, as shown in the following code block:

PKUTIL.loadJSON = function ( theURL, completion )
{
  PKUTIL.load( theURL, true, function ( success, data )
    {

First off, this is a pretty simple function to begin with. What we're really doing is utilizing the PKUTIL.load() method (explained later) to do the hard work of calling out to the URL and passing us the response, but when the response is received, it's going to be coming back to us in the data variable.

      var theParsedData = {};

The theParsedData variable will store the actual JSON data, fully parsed.

      if (success)
      {
        try
        {
          theParsedData = JSON.parse ( data );

If the URL returns something successfully, we try to parse the data. Assuming it is a valid JSON string, it'll be put into theParsedData. If it isn't, the JSON.parse() method will throw an exception as follows:

        }
          catch (err)
        {
          console.log ("Failed to parse JSON from " + theURL);
          success = COMPLETION_FAILURE;
        }

Any exceptions will be logged to the console, and we'll end up telling our completion function that the request failed:

      }
      if (completion)
      {
        completion (success, theParsedData);
      }

At the end, we call the completion function and tell it if the request failed or succeeded, and what the JSON data was (if successfully parsed).

    }
  );
}

The PKUTIL.load() method is another interesting beast (for full implementation details, visit https://github.com/photokandyStudios/YASMF/blob/master/framework/utility.js#L126). It's defined as follows:

PKUTIL.load = function ( theFileName, aSync, completion )
{

First, we'll check to see if the browser understands XMLHttpRequest. If it doesn't, we'll have to call the completion function with a failure notice and a message describing how we couldn't load anything. This is shown in the following code block:

  if (!window.XMLHttpRequest) 
  { 
    if (completion) 
    {
      completion ( PKUTIL.COMPLETION_FAILURE,
                   "This browser does not support 
                    XMLHttpRequest." );
      return;
    }
  }

Next we set up the XMLHttpRequest() method, and assign the onreadystatechange function as shown in the following code snippet:

  var r = new XMLHttpRequest();
  r.onreadystatechange = function()
  {

This function can be called many times during the loading process, so we check for a specific value. In this case, 4 in the following code snippet means that the content has been loaded:

    if (r.readyState == 4)
    {

Of course, just because we got data doesn't mean that it is useable data; we need to verify the status of the load, and here we get into a little bit of murky territory. iOS defines success with a zero value, while Android defines it with 200, as shown in the following code snippet:

      if ( r.status==200 || r.status == 0)
      {

If we've successfully loaded the data, we'll call the completion function with a success notification and the data:

        if (completion)
        {
          completion ( PKUTIL.COMPLETION_SUCCESS,
                       r.responseText );
        }
      }

But, if we've failed to load the data, we call the completion function with a failure notification and the status value of the load:

      else
      {
        if (completion)
        {
          completion ( PKUTIL.COMPLETION_FAILURE,
                       r.status );
        }
      }
    }
  }

Keep in mind that we're still just setting up the XMLHttpRequest object; we've not actually triggered the load yet.

The next step is to specify the path to the file, and here we run into a problem on WP7 versus Android and iOS. On both Android and iOS we can load files relative to the index.html file, but on WP7, we have to load them relative to the /app/www directory. Subtle to track down, but critically important. Even though we aren't supporting WP7 in this book, the framework does, and so it needs to handle cases such as the following:

  if (device.platform=="WinCE")
  {
    r.open ('GET', "/app/www/" + theFileName, aSync); 
  }
  else
  {
    r.open ('GET', theFileName, aSync); 
  }

Now that we've set the filename, we fire off the load:

  r.send ( null );
      
}

Tip

Should you ever decide to support WP7, it is critical that even though the framework supports passing false for aSync, which should result in a synchronous load, you shouldn't actually ever do so. WP7's browser does very funny things when it can't load data asynchronously. For one thing, it loads it asynchronously anyway (not your intended behavior), and it also has a tendency to think the file simply doesn't exist. So, instead of loading scripts, you'll get errors in the console indicating that a 404 error has occurred. And you'll scratch your head (I did!) wondering how in the world that could be when the file is right there. Then you'll remember this tip, change the value back to true, and things will suddenly start working. (You seriously do not want to know the hours it took me to debug on WP7 to finally figure this out. I want those hours back!)

CONTINUE READING
83
Tech Concepts
36
Programming languages
73
Tech Tools
Icon Unlimited access to the largest independent learning library in tech of over 8,000 expert-authored tech books and videos.
Icon Innovative learning tools, including AI book assistants, code context explainers, and text-to-speech.
Icon 50+ new titles added per month and exclusive early access to books as they are being written.
Instant PhoneGap Social App Development
notes
bookmark Notes and Bookmarks search Search in title playlist Add to playlist font-size Font size

Change the font size

margin-width Margin width

Change margin width

day-mode Day/Sepia/Night Modes

Change background colour

Close icon Search
Country selected

Close icon Your notes and bookmarks

Confirmation

Modal Close icon
claim successful

Buy this book with your credits?

Modal Close icon
Are you sure you want to buy this book with one of your credits?
Close
YES, BUY

Submit Your Feedback

Modal Close icon
Modal Close icon
Modal Close icon