PhoneGap RSS Reader

这是关于如何使用phoneGap来开发手机上的RSS阅读器的文章,原作者分别写了四个版本。如下所示:

原文链接:http://www.raymondcamden.com/index.cfm/2011/10/11/PhoneGap-RSS-Reader

Part1

原文:

PhoneGap RSS Reader - Part 1

Earlier today I noticed an entry on the PhoneGap forums from a user asking about an RSS reader. I thought I'd whip one up using PhoneGap and jQuery Mobile and see what it took. Here's what I came up, and as always, feel free to rip apart the code and write it better. I'll warn folks that I only wrote the code to support RSS2, not ATOM, but in theory, it should be possible without too much additional work. (You would simply look at the metadata, note it, and change how you get data.)

I began by creating a new PhoneGap project. I strongly recommend reading my entry from earlier today about the nice Eclipse PhoneGap plugin to make it quicker to setup your projects. I began by adding code to handle getting the RSS code. First, I setup two simple variables people could tweak:

1//EDIT THESE LINES
2//Title of the blog
3var TITLE = "ColdFusion Jedi";
4//RSS url
5var RSS = "http://feedproxy.google.com/RaymondCamdensColdfusionBlog";

My code listens for my initial page to load. When this happens, I want to get the XML from the feed and work with it. Here's that snippet.

1$.get(RSS, {}, function(res, code) {
2var xml = $(res);
3var items = xml.find("item");
4$.each(items, function(i, v) {
5entry = { 
6title:$(v).find("title").text(), 
7link:$(v).find("link").text(), 
8description:$.trim($(v).find("description").text())
9};
10entries.push(entry);
11});

As a reminder, AJAX code in PhoneGap applications are not restricted by normal remote domain rules. I can easily open up the remote XML and parse it. Where did entries come from? Well this is where things get slightly complex. I'm going to store the parsed RSS in a global array called entries (hence the push to add things to the end). This will allow me to dynamically display the pages. But first, let's render out the list. My home page already has a blank list just for this purpose:

1<div data-role="content">    
2    <ul id="linksList" data-role="listview" data-inset="true"></ul>
3</div>

So my handler can simply append to it....

1//now draw the list
2var s = '';
3$.each(entries, function(i, v) {
4s += '<li><a href="#contentPage" class="contentLink" data-entryid="'+i+'">' + v.title + '</a></li>';
5});
6$("#linksList").append(s);
7$("#linksList").listview("refresh");

Note two things here. First, every single link will be going to a new jQuery Mobile page, contentPage. But I store the ID of the item in a data attribute. Also note the use listview("refresh"). This tells jQuery Mobile to add unicorn magic prettiness to HTML I've added in the list. Serious - Unicorn Magic Prettiness is the official term.

Ok, so what's going on with the content page? I created it as a blank page, like so:

1<div data-role="page" id="contentPage">
2
3    <div data-role="header">
4        <a href="#mainPage" data-rel="back">Home</a>
5        <h1></h1>
6    </div>
7
8    <div data-role="content" id="entryText">
9    </div>
10        
11</div>

And I've got a JavaScript click handler that notices the clicks and populates the data. First, the event listener for the link:

1//listen for detail links
2$(".contentLink").live("click", function() {
3selectedEntry = $(this).data("entryid");
4});

And then the page displayer...

1//Listen for the content page to load
2$("#contentPage").live("pageshow", function(prepage) {
3//Set the title
4$("h1", this).text(entries[selectedEntry].title);
5var contentHTML = "";
6contentHTML += entries[selectedEntry].description;
7contentHTML += '<p/><a href="'+entries[selectedEntry].link + '">Read Entry on Site</a>';
8$("#entryText",this).html(contentHTML);
9});

Pretty simple, right? (I should point out jQuery Mobile does support completely virtualized pages too. ) Here's a quick screen shot...

And another one...

And finally, the complete code. If anyone wants the actual APK for this, just ask. The home page first:

1<!DOCTYPE html> 
2<html> 
3    <head> 
4    <meta name="viewport" content="width=device-width, initial-scale=1">    
5    <title></title> 
6    <link rel="stylesheet" href="http://code.jquery.com/mobile/latest/jquery.mobile.min.css" />
7    <script src="http://code.jquery.com/jquery-1.6.2.min.js"></script>
8    <script src="main.js"></script>
9    <script src="http://code.jquery.com/mobile/latest/jquery.mobile.min.js"></script>
10</head> 
11<body> 
12
13<div data-role="page" id="mainPage">
14
15    <div data-role="header">
16        <h1></h1>
17    </div>
18
19    <div data-role="content">    
20        <ul id="linksList" data-role="listview" data-inset="true"></ul>
21    </div>
22
23    <div data-role="footer">
24        <h4>SimpleBlog by Raymond Camden</h4>
25    </div>
26
27    
28</div>
29
30<div data-role="page" id="contentPage">
31
32    <div data-role="header">
33        <a href="#mainPage" data-rel="back">Home</a>
34        <h1></h1>
35    </div>
36
37    <div data-role="content" id="entryText">
38    </div>
39        
40</div>
41
42</body>
43</html>

And the main.js file:

1$(document).ready(function() {
2
3//EDIT THESE LINES
4//Title of the blog
5var TITLE = "ColdFusion Jedi";
6//RSS url
7var RSS = "http://feedproxy.google.com/RaymondCamdensColdfusionBlog";
8//Stores entries
9var entries = [];
10var selectedEntry = "";
11
12//listen for detail links
13$(".contentLink").live("click", function() {
14selectedEntry = $(this).data("entryid");
15});
16
17//Listen for main page
18$("#mainPage").live("pageinit", function() {
19
20//Set the title
21$("h1", this).text(TITLE);
22
23$.get(RSS, {}, function(res, code) {
24var xml = $(res);
25var items = xml.find("item");
26$.each(items, function(i, v) {
27entry = { 
28title:$(v).find("title").text(), 
29link:$(v).find("link").text(), 
30description:$.trim($(v).find("description").text())
31};
32entries.push(entry);
33});
34
35//now draw the list
36var s = '';
37$.each(entries, function(i, v) {
38s += '<li><a href="#contentPage" class="contentLink" data-entryid="'+i+'">' + v.title + '</a></li>';
39});
40$("#linksList").append(s);
41$("#linksList").listview("refresh");
42});
43
44});
45
46//Listen for the content page to load
47$("#contentPage").live("pageshow", function(prepage) {
48//Set the title
49$("h1", this).text(entries[selectedEntry].title);
50var contentHTML = "";
51contentHTML += entries[selectedEntry].description;
52contentHTML += '<p/><a href="'+entries[selectedEntry].link + '">Read Entry on Site</a>';
53$("#entryText",this).html(contentHTML);
54});
55
56});

PhoneGap RSS Reader - Part 2

Two months ago I wrote a blog entry on how to build a simple PhoneGapp application. This application just read in an RSS feed and used jQuery Mobile to display the results. I think this was helpful, but a few readers pointed out some issues with the code that caused me to come back to it this weekend and work on some updates. The issues were...

Error Handling
I admit it. I do not do a good job of error handling in most of my AJAX based applications. While this may be acceptable in some applications, in a mobile application where the entire functionality depends on things working right, there is no excuse for lacking proper error handling. My application really just did one main thing - fetch an RSS feed. Everything after that was display. But as with server side apps, any network call is a point of failure. My first change was to add in a simple error handler. In jQuery, this is rather simple. Since the application can't do much of anything without an RSS feed (or can it - see my PS below), for now we just display a simple error message.

1$.ajaxSetup({
2    error:function(x,e,errorThrown) {
3        console.log(x.getStatusCode());
4        $("#status").prepend("Error!");        
5    }
6});

That's it. jQuery makes it simple to add global handlers and since we only have one network operation anyway, I can use this just fine. As I said, we don't really need to provide a lot of detail in this case, but we can at least let the user know something went wrong. To be fair, in a real application I'd probably add a bit more text. I'd let the user know the data couldn't be loaded and to please try again.

Page load issues
This one was a bonehead mistake. If you look at the code in the original blog entry, I do my network call and render results using the pageshow event. This means thatevery time the page is shown, it will fire, including times when the user hits back from an entry view. As I said - bonehead. Luckily it's simple enough to change to pageinit. Another change I made was to not make use of jQuery's document.ready logic. Instead, I simply load everything up at once. Here is my updated JavaScript file in it's entirety.

1$.ajaxSetup({
2    error:function(x,e,errorThrown) {
3        console.log(x.getStatusCode());
4        $("#status").prepend("Error!");        
5    }
6});
7
8//EDIT THESE LINES
9//Title of the blog
10var TITLE = "ColdFusion Jedi";
11//RSS url
12var RSS = "http://feedproxy.google.com/RaymondCamdensColdfusionBlog";
13//Stores entries
14var entries = [];
15var selectedEntry = "";
16
17//listen for detail links
18$(".contentLink").live("click", function() {
19    selectedEntry = $(this).data("entryid");
20});
21
22//Listen for main page
23$("#mainPage").live("pageinit", function() {
24    //Set the title
25    $("h1", this).text(TITLE);
26
27    $.get(RSS, {}, function(res, code) {
28        entries = [];
29        var xml = $(res);
30        var items = xml.find("item");
31        $.each(items, function(i, v) {
32            entry = { 
33                title:$(v).find("title").text(), 
34                link:$(v).find("link").text(), 
35                description:$.trim($(v).find("description").text())
36            };
37            entries.push(entry);
38        });
39
40        //now draw the list
41        var s = '';
42        $.each(entries, function(i, v) {
43            s += '<li><a href="#contentPage" class="contentLink" data-entryid="'+i+'">' + v.title + '</a></li>';
44        });
45        $("#linksList").html(s);
46        $("#linksList").listview("refresh");
47    });
48
49});
50
51//Listen for the content page to load
52$("#contentPage").live("pageshow", function(prepage) {
53    //Set the title
54    $("h1", this).text(entries[selectedEntry].title);
55    var contentHTML = "";
56    contentHTML += entries[selectedEntry].description;
57    contentHTML += '<p/><a href="'+entries[selectedEntry].link + '">Read Entry on Site</a>';
58    $("#entryText",this).html(contentHTML);
59});

And here is the front end HTML. The only change here was the addition of the status div used by error handling.

1<!DOCTYPE html> 
2<html> 
3<head> 
4<meta name="viewport" content="width=device-width, initial-scale=1"> 
5<title></title> 
6<link rel="stylesheet" href="js/jquery.mobile-1.0.min.css" />
7    <script type="text/javascript" src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
8    <script src="js/jquery.mobile-1.0.min.js"></script>
9<script src="js/main.js"></script>
10</head> 
11<body> 
12
13<div data-role="page" id="mainPage">
14
15<div data-role="header">
16<h1></h1>
17</div>
18
19<div data-role="content"> 
20        <div id="status"></div> 
21<ul id="linksList" data-role="listview" data-inset="true"></ul>
22</div>
23
24<div data-role="footer">
25<h4>SimpleBlog by Raymond Camden</h4>
26</div>
27
28
29</div>
30
31<div data-role="page" id="contentPage">
32
33<div data-role="header">
34<a href="#mainPage" data-rel="back">Home</a>
35<h1></h1>
36</div>
37
38<div data-role="content" id="entryText">
39</div>
40
41</div>
42
43</body>
44</html>

There you go! I've wrapped up the entire Eclipse project into a zip. It also includes a debug APK you can install to your device if you want to try it out.

p.s. Technically, we could try to handle network issues better. I'm not just talking about the RSS feed being down, but what if the user is offline? I've decided to follow this up with a third version that will try storing the RSS feed in local storage. If the user is offline, we can at least resort to the older data.

Download attached file

PhoneGap RSS Reader - Part 3

Welcome to my third entry for my (what was at first) simple PhoneGap RSS reader. If you haven't yet, please be sure to read part 1 and part 2 so you have some context about how this application works. In this part, I'm going to tackle two enhancements suggested to me by my readers:

  1. First - support rendering the entries if the user is offline.
  2. Second - clean up the UX a bit so that when a user views an entry, leaves, and then comes back to another entry, they don't see the old text there for a split second.

Let's tackle the offline question first. I spent some time thinking about this and tried a few things that didn't quite work out the way I wanted. The first thing I tried was checking navigator.onLine. (See this Stackoverflow entry for details.) This did not work well for me. When I switched my device to airplane mode it still reported me as online. I then looked into PhoneGap's Connection API. This kinda worked. It didn't seem to grok my Airplane mode at all. It certainly didn't report it as online (it returned a null actually) and I could have handled it, but I then had the issue of how I was going to handle coordinating the deviceready event along with the jQuery Mobile pageinit method.

Then I realized something. I already had an Ajax error handler. And it worked. That's obvious of course, but it occurred to me. Why not use this error handler? It would not only support offline mode, but any error on the remote server as well. At the end of the day, if I can't get the RSS feed, who cares if I'm offline or if the server is down. I couldsee caring, and if so, you would obviously want to use the PhoneGap Connection value, but I figured, why not go with the simple route.

As for storage - that turned out to be trivial - LocalStorage. Have you guys figured out yet that I really love HTML5 LocalStorage?

So, I decided to get rid of AjaxSetup. I only have one Ajax call in the entire application, so why not tie it to that call. So I switched from a $.get to a $.ajax:

1$.ajax({
2    url:RSS,
3    success:function(res,code) {
4        entries = [];
5        var xml = $(res);
6        var items = xml.find("item");
7        $.each(items, function(i, v) {
8            entry = { 
9                title:$(v).find("title").text(), 
10                link:$(v).find("link").text(), 
11                description:$.trim($(v).find("description").text())
12            };
13            entries.push(entry);
14        });
15        //store entries
16        localStorage["entries"] = JSON.stringify(entries);
17        renderEntries(entries);
18    },
19    error:function(jqXHR,status,error) {
20        //try to use cache
21        if(localStorage["entries"]) {
22            $("#status").html("Using cached version...");
23            entries = JSON.parse(localStorage["entries"])
24            renderEntries(entries);                
25        } else {
26            $("#status").html("Sorry, we are unable to get the RSS and there is no cache.");
27        }
28    }
29})

This is - for the most part, the same code as before, just using the core $.ajax method. You can see where the error function will look into LocalStorage for the cached copy, and where the success function always stores a copy. The renderEntries function is simply an abstracted out version of the display code:

1function renderEntries(entries) {
2var s = '';
3$.each(entries, function(i, v) {
4s += '<li><a href="#contentPage" class="contentLink" data-entryid="'+i+'">' + v.title + '</a></li>';
5});
6$("#linksList").html(s);
7$("#linksList").listview("refresh");        
8}

Woot. That works. Now for the next request. We want to ensure that users don't see the old content when loading in the RSS entry detail page. This turned out to be a bit weird. jQuery Mobile has logic to say, "Do this when a page is hiding, or before it hides", but for the life of me (and please correct me if I'm wrong) there doesn't seem to be a good way to get the page that is leaving. You get passed the page you are going to, but not the current page. I really feel like I'm missing something here, so please note this may get corrected later. For me though I simply added an event listener to the main page. It now sees if a previous page exists, and if so, clears out the text:

1$("#mainPage").live("pagebeforeshow", function(event,data) {
2    if(data.prevPage.length) {
3        $("h1", data.prevPage).text("");
4        $("#entryText", data.prevPage).html("");
5    };
6});

And that's it. I've included the entire JavaScript file below (the HTML hasn't changed from the previous entry) and a zip of the entire project may be downloaded for the low cost of free.

1//EDIT THESE LINES
2//Title of the blog
3var TITLE = "ColdFusion Jedi";
4//RSS url
5var RSS = "http://feedproxy.google.com/RaymondCamdensColdfusionBlog";
6//Stores entries
7var entries = [];
8var selectedEntry = "";
9
10//listen for detail links
11$(".contentLink").live("click", function() {
12    selectedEntry = $(this).data("entryid");
13});
14
15function renderEntries(entries) {
16var s = '';
17$.each(entries, function(i, v) {
18s += '<li><a href="#contentPage" class="contentLink" data-entryid="'+i+'">' + v.title + '</a></li>';
19});
20$("#linksList").html(s);
21$("#linksList").listview("refresh");        
22}
23
24//Listen for main page
25$("#mainPage").live("pageinit", function() {
26    //Set the title
27    $("h1", this).text(TITLE);
28    
29    $.ajax({
30        url:RSS,
31        success:function(res,code) {
32            entries = [];
33            var xml = $(res);
34            var items = xml.find("item");
35            $.each(items, function(i, v) {
36                entry = { 
37                    title:$(v).find("title").text(), 
38                    link:$(v).find("link").text(), 
39                    description:$.trim($(v).find("description").text())
40                };
41                entries.push(entry);
42            });
43            //store entries
44            localStorage["entries"] = JSON.stringify(entries);
45            renderEntries(entries);
46        },
47        error:function(jqXHR,status,error) {
48            //try to use cache
49            if(localStorage["entries"]) {
50                $("#status").html("Using cached version...");
51                entries = JSON.parse(localStorage["entries"])
52                renderEntries(entries);                
53            } else {
54                $("#status").html("Sorry, we are unable to get the RSS and there is no cache.");
55            }
56        }
57    });
58    
59});
60
61$("#mainPage").live("pagebeforeshow", function(event,data) {
62    if(data.prevPage.length) {
63        $("h1", data.prevPage).text("");
64        $("#entryText", data.prevPage).html("");
65    };
66});
67
68//Listen for the content page to load
69$("#contentPage").live("pageshow", function(prepage) {
70    //Set the title
71    $("h1", this).text(entries[selectedEntry].title);
72    var contentHTML = "";
73    contentHTML += entries[selectedEntry].description;
74    contentHTML += '<p/><a href="'+entries[selectedEntry].link + '">Read Entry on Site</a>';
75    $("#entryText",this).html(contentHTML);
76});

Download attached file

PhoneGap RSS Reader - Part 4

Edited on August 4, 2012: Readers noted that this version didn't correctly handle trying to load the cache when offline. I confirmed that and posted a fix in the comments.

For whatever reason, my articles on PhoneGap and RSS (see related entries below) have been incredibly popular. The last entry currently has 163 comments. Some of the comments deal with the fact that RSS, while a standard, does have a bit of variation to it. My code made some assumptions that didn't always work for other feeds. I thought this was a great opportunity to look at ways I could make the code more applicable to other types of feeds, especially Atom. Luckily, there is an awesome service for this - the Google Feed API.

As you can probably guess, the Google Feed API allows you to parse RSS feeds into simple to use data. It takes any valid RSS or Atom feed and parses it into a simple,standard data model. While the previous code I used wasn't too difficult, it was also very tied to one particular flavor of RSS. I could have continued to add in support for multiple styles of RSS feeds but this seemed far easier to me.

To begin, I added a script tag to load in the Google Loader. This is service Google provides that allows you to dynamically include in JavaScript support for various Google APIs.

<script type="text/javascript" src="https://www.google.com/jsapi"></script>

To load in support for the Feed API, I modified my mainPage pageinit event handler to ask Google Load to go grab the bits. It is very important that you provide the callback option to this API. If you do not, Google Load will blow away the jQuery Mobile DOM completely.

     
     
$ ( "#mainPage" ). live ( "pageinit" , function () {
//Set the title
$ ( "h1" , this ). text ( TITLE );
google . load ( "feeds" , "1" ,{ callback : initialize });
});
view raw gistfile1.js This Gist brought to you by  GitHub.

Now let's look at initialize. Previously, this is the portion that would have done the Ajax call, used XML parsing on the results, and stored the entries. Because Google's Feed API is doing this for me the code is now somewhat simpler. (I also added support for jQuery Mobile's "showPageLoadingMsg" API to make it obvious to the user that something is happening.)

     
     
function initialize () {
console . log ( 'ready to use google' );
var feed = new google . feeds . Feed ( RSS );
feed . setNumEntries ( 10 );
$ . mobile . showPageLoadingMsg ();
feed . load ( function ( result ) {
$ . mobile . hidePageLoadingMsg ();
if ( ! result . error ) {
entries = result . feed . entries ;
localStorage [ "entries" ] = JSON . stringify ( entries );
renderEntries ( entries );
} else {
console . log ( "Error - " + result . error . message );
if ( localStorage [ "entries" ]) {
$ ( "#status" ). html ( "Using cached version..." );
entries = JSON . parse ( localStorage [ "entries" ]);
renderEntries ( entries );
} else {
$ ( "#status" ). html ( "Sorry, we are unable to get the RSS and there is no cache." );
}
}
});
}
view raw gistfile1.js This Gist brought to you by  GitHub.

And that's pretty much it. My previous code stored the content of the RSS item in a key named content. Google uses a key named description so I modified my display code.

As a final enhancement, I decided to make use of PhoneGap Build to create my mobile application. This allowed me to define a simple config.xml file to help define how the executables are generated. For example, I was able to provide simple icon support. (The icons I used were provided by Fast Icon.)

     
     
<?xml version="1.0" encoding="UTF-8" ?>
<widget xmlns = "http://www.w3.org/ns/widgets"
         xmlns:gap = "http://phonegap.com/ns/1.0"
         id = "com.camden.rssviewer4"
         versionCode= "10"
         version = "4.0.0" >
     <name>RSS Viewer </name>
     <description>
        A simple RSS Viewer.
     </description>
     <author href= "http://www.raymondcamden.com" email= "raymondcamden@gmail.com" >
        Raymond Camden
     </author>
     <icon src= "icons/Feed_128x128.png" />
</widget>
view raw gistfile1.xml This Gist brought to you by  GitHub.

You can see my application at the bottom here in the screen shot. And yes - I have a Selena Gomez app. Don't hate me for downloading apps my daughter loves.

Want to download the app yourself? Try the public Build page for every platform but iOS. You can also download all of the bits below.

Download attached file


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值