Sunday, June 26, 2011

Simple RSS Reader in Android


The Connexions app reads both Atom and RSS feeds. The implementations are similar, but the Atom reader is a bit Connexions specific because of the way we add metadata in the content element of the feed. Our RSS feeds are pretty straightforward, so the reader is also. RSS is currently used for Recently Published Content. The reader is basically a Sax parser that is looking for specific elements and grabbing the attribute or text from the needed elements.

To be a Sax parser, the RssHandler class implements the basic Sax methods of startElement(), endElement() and characters(). The 3 elements I needed from the feed were item, title and link. I set a boolean when I encounter one of the elements in startElement(). I also create a Content object when an item element is found. The Content object will be used in the other methods as well.

public void startElement(String uri, String name, String qName, Attributes atts)
{
if(name.trim().equals("item"))
{
currentContent = new Content();
}
if (name.trim().equals("title"))
{
inTitle = true;
}
else if (name.trim().equals("link"))
{
inLink = true;
}
}


In characters(), I get the text of the elements if one of the booleans have been set. Since there is no guarantee that the entire buffer will be delivered, there is code to concatenate the remaining text to the initial String is necessary. There is also code to hack the cnx.org domain sent with the feed to be mobile.cnx.org.

public void characters(char ch[], int start, int length)
{

String chars = (new String(ch).substring(start, start + length));

if (inTitle && currentContent != null)
{
if(currentContent.title == null || currentContent.title.equals("") )
{
currentContent.title = chars;currentContent.title = chars;
}
else
{
currentContent.title = currentContent.title + chars;
}
}
else if(inLink && currentContent != null)
{
String link = new String(chars);
try
{
currentContent.setUrl(new URL("http://mobile.cnx.org" + link.substring(14)));
}
catch (MalformedURLException e)
{
e.printStackTrace();
}

}
}


The method endElement() resets the booleans to false, calls a method to set the correct icon on the Content object and adds the Content object to an ArrayList.

public void endElement(String uri, String name, String qName) throws SAXException
{
inTitle = false;
inLink = false;

if(currentContent != null && currentContent.url != null && currentContent.title != null && !contentList.contains(currentContent))
{
if(currentContent.getIconImage() == null)
{
setIcon();
}
contentList.add(currentContent);
}

}


The 3 methods described above are triggered by the parseFeed() method.

public ArrayList parseFeed(Context ctx, Feed feed)
{
try
{

SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
XMLReader xr = sp.getXMLReader();
xr.setContentHandler(this);
xr.parse(new InputSource(feed.url.openStream()));

}
catch (IOException e)
{
Log.e(HANDLER, e.toString());
}
catch (SAXException e)
{
Log.e(HANDLER, e.toString());
}
catch (ParserConfigurationException e)
{
Log.e(HANDLER, e.toString());
}
return contentList;
}


You can browse the source or download a zip file of the source. Connexions for Android is available in the Android Market or from the Connexions website.

Saturday, June 4, 2011

Creating a File Browser in Android

The Connexions app can be used to download PDF and EPUB files so it was a natural to allow the user to view the downloaded files, select one and do something with it. The code for most of this functionality is in the FileBrowserActivity class. The readFileList() method is called by onCreate(). readFileList() checks to see if the Connexions directory exists. If it does, the user has downloaded a PDF or EPUB, so it calls handleFIle().


public void readFileList()
{
currentDirectory = new File(Environment.getExternalStorageDirectory(), "Connexions/");
if(currentDirectory.exists())
{
handleFile(currentDirectory);
}

}



The method handleFile() checks if the file/directory passed in is the Connexions directory. If it is, the list of files is loaded into a List object. It is the behavior when onCreate() calls the method. If a user selects a file from the displayed list, the onListItemClick() method calls handleFile() which opens an alert to ask if the user wants to open the file.


private void handleFile(final File dirOrFile)
{
//Log.d("FileBrowserActivity.browseTo()", "Called");
if (dirOrFile.isDirectory() && !dirOrFile.getPath().endsWith("Connexions/"))
{
this.currentDirectory = dirOrFile;
loadList(dirOrFile.listFiles());
}
else
{

AlertDialog alertDialog = new AlertDialog.Builder(this).create();
alertDialog.setTitle(getString(R.string.file_dialog_title));
alertDialog.setMessage("Open file " + dirOrFile.getName() + "?");
alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
openFile(dirOrFile);

} });
alertDialog.setButton(AlertDialog.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
//do nothing

} });
alertDialog.show();

}
}


If the user selects to open the file, openFile() is called and it tries to open the file in a different application for the file mime-type. If there is no app for the mime-type, a Toast message is displayed to notify the user.


private void openFile(File file)
{

File newFile = new File(Environment.getExternalStorageDirectory() + "/Connexions/" + file.getName());
Uri path = Uri.fromFile(newFile);
//Log.d("FileBrowserActivity", "path: " + path.toString());
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
if(file.getAbsolutePath().indexOf(".pdf") > -1)
{
intent.setDataAndType(path, "application/pdf");
}
else if(file.getAbsolutePath().indexOf(".epub") > -1)
{
intent.setDataAndType(path, "application/epub+zip");
}

try
{
startActivity(intent);
}
catch (ActivityNotFoundException e)
{
Toast.makeText(FileBrowserActivity.this, getString(R.string.file_browser_toast), Toast.LENGTH_SHORT).show();
}
}


As always, you can browse the source or download a zip file of the source. Connexions for Android is available in the Android Market or from the Connexions website.