Android Full App, Part 3: Parsing the XML response
The TMDb API supports both XML and JSON formats for the HTTP responses. We are going to use XML for our application. Let’s see first how some sample responses to search queries look like:
The responses are typical XML documents that can be parsed using the standard procedures either using SAX or DOM. The SAX specification defines an event-based approach where the implemented parsers scan through XML data and they use call-back handlers whenever certain parts of the document have been reached. On the other hand, the DOM specification defines a tree-based approach to navigating an XML document.
In general, SAX’s usage is more challenging, because the API requires development of callback functions that handle the events, while the DOM approach requires a bigger memory footprint. For that reason, we are going to choose SAX for our XML parsers implementation, since our application will leave in a rather resource-constrained environment such as a mobile device.
Before we proceed with the XML parsing, we are going to create some model classes which will map the XML elements to Java classes. By simply looking at the XML responses, the following model classes can be derived:
package com.javacodegeeks.android.apps.moviesearchapp.model; import java.util.ArrayList; public class Person { public String score; public String popularity; public String name; public String id; public String biography; public String url; public String version; public String lastModifiedAt; public ArrayList<Image> imagesList; }
package com.javacodegeeks.android.apps.moviesearchapp.model; import java.util.ArrayList; public class Movie { public String score; public String popularity; public boolean translated; public boolean adult; public String language; public String originalName; public String name; public String type; public String id; public String imdbId; public String url; public String votes; public String rating; public String certification; public String overview; public String released; public String version; public String lastModifiedAt; public ArrayList<Image> imagesList; public String retrieveThumbnail() { if (imagesList!=null && !imagesList.isEmpty()) { for (Image movieImage : imagesList) { if (movieImage.size.equalsIgnoreCase(Image.SIZE_THUMB) && movieImage.type.equalsIgnoreCase(Image.TYPE_POSTER)) { return movieImage.url; } } } return null; } }
package com.javacodegeeks.android.apps.moviesearchapp.model; public class Image { public static final String SIZE_ORIGINAL = "original"; public static final String SIZE_MID = "mid"; public static final String SIZE_COVER = "cover"; public static final String SIZE_THUMB = "thumb"; public static final String TYPE_PROFILE = "profile"; public static final String TYPE_POSTER = "poster"; public String type; public String url; public String size; public int width; public int height; }
Nothing really special here, we are just adding String fields for each XML element. Note that the Image class will be commonly used by both the Person and Movie class. Also, the Movie class provides the retrieveThumbnail method which loops through the available Images and returns the one of size “thumb” and type “poster”.
We proceed with creating a class named XmlParser which uses SAX approach in order to parse the XML responses. The class uses two custom handlers (PersonHandler and MovieHandler) in order to perform the parsing. The code for that class is the following:
package com.javacodegeeks.android.apps.moviesearchapp.services; import java.io.StringReader; import java.util.ArrayList; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import com.javacodegeeks.android.apps.moviesearchapp.handlers.MovieHandler; import com.javacodegeeks.android.apps.moviesearchapp.handlers.PersonHandler; import com.javacodegeeks.android.apps.moviesearchapp.model.Movie; import com.javacodegeeks.android.apps.moviesearchapp.model.Person; public class XmlParser { private XMLReader initializeReader() throws ParserConfigurationException, SAXException { SAXParserFactory factory = SAXParserFactory.newInstance(); // create a parser SAXParser parser = factory.newSAXParser(); // create the reader (scanner) XMLReader xmlreader = parser.getXMLReader(); return xmlreader; } public ArrayList<Person> parsePeopleResponse(String xml) { try { XMLReader xmlreader = initializeReader(); PersonHandler personHandler = new PersonHandler(); // assign our handler xmlreader.setContentHandler(personHandler); // perform the synchronous parse xmlreader.parse(new InputSource(new StringReader(xml))); return personHandler.retrievePersonList(); } catch (Exception e) { e.printStackTrace(); return null; } } public ArrayList<Movie> parseMoviesResponse(String xml) { try { XMLReader xmlreader = initializeReader(); MovieHandler movieHandler = new MovieHandler(); // assign our handler xmlreader.setContentHandler(movieHandler); // perform the synchronous parse xmlreader.parse(new InputSource(new StringReader(xml))); return movieHandler.retrieveMoviesList(); } catch (Exception e) { e.printStackTrace(); return null; } } }
In each method, we first retrieve a reference of the SAX parser factory class using the newInstance static method of the SAXParserFactory. That method returns the appropriate Android’ implementation. Then, a SAXParser object is created using the newSAXParser method, which creates a new instance of a SAXParser using the currently configured factory parameters. The SAXParser class defines the API that wraps an XMLReader implementation class. XMLReader is an interface for reading an XML document using callbacks. The callbacks are defined usually via classes that extend the DefaultHandler class, which is the default base class for SAX2 event handlers. We provide two handlers, one for parsing the person search responses (PersonHandler) and one for parsing the movies search responses (MovieHandler). The code for the PersonHandler class follows (the MovieHandler class is pretty same, thus is omitted for brevity – the source code for that class can be found in the available Eclipse project at the end of the tutorial):
package com.javacodegeeks.android.apps.moviesearchapp.handlers; import java.util.ArrayList; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import com.javacodegeeks.android.apps.moviesearchapp.model.Image; import com.javacodegeeks.android.apps.moviesearchapp.model.Person; public class PersonHandler extends DefaultHandler { private StringBuffer buffer = new StringBuffer(); private ArrayList<Person> personList; private Person person; private ArrayList<Image> personImagesList; private Image personImage; @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { buffer.setLength(0); if (localName.equals("people")) { personList = new ArrayList<Person>(); } else if (localName.equals("person")) { person = new Person(); } else if (localName.equals("images")) { personImagesList = new ArrayList<Image>(); } else if (localName.equals("image")) { personImage = new Image(); personImage.type = atts.getValue("type"); personImage.url = atts.getValue("url"); personImage.size = atts.getValue("size"); personImage.width = Integer.parseInt(atts.getValue("width")); personImage.height = Integer.parseInt(atts.getValue("height")); } } @Override public void endElement(String uri, String localName, String qName)throws SAXException { if (localName.equals("person")) { personList.add(person); } else if (localName.equals("score")) { person.score = buffer.toString(); } else if (localName.equals("popularity")) { person.popularity = buffer.toString(); } else if (localName.equals("name")) { person.name = buffer.toString(); } else if (localName.equals("id")) { person.id = buffer.toString(); } else if (localName.equals("biography")) { person.biography = buffer.toString(); } else if (localName.equals("url")) { person.url = buffer.toString(); } else if (localName.equals("version")) { person.version = buffer.toString(); } else if (localName.equals("last_modified_at")) { person.lastModifiedAt = buffer.toString(); } else if (localName.equals("image")) { personImagesList.add(personImage); } else if (localName.equals("images")) { person.imagesList = personImagesList; } } @Override public void characters(char[] ch, int start, int length) { buffer.append(ch, start, length); } public ArrayList<Person> retrievePersonList() { return personList; } }
The standard approach for SAX parsing (described in many tutorials online) is used, thus the above code should look familiar if you have parsed XML documents before. Note though that instead of the qName parameter, it is the localName variable that holds the element’s data.
In our class, we define the necessary callback functions:
- startElement: Called when a new element is found. We initialize the appropriate field there.
- endElement: Called when the element’s end has been reached. The corresponding field gets populated there.
- characters: Called when new text has been found inside an element. An internal buffer gets populated with the content of the element.
Note that within a response, a number of Person elements might be found and inside each one of them, a number of Images can be found. Particularly for the images, the relevant information resides within the elements attributes and not inside a text node. Thus the appropriate getValue method is used in order to extract that information.
At this point, the third part of the series has reached its end. In this part, we prepared the infrastructure for performing the XML parsing of the API responses using the SAX approach. At the following tutorials, we will use that in order to map the responses to our model classes. You can download here the Eclipse project created so far.
Joan I am having a Force Close later on and i think it has something do with the XML Reader can u explain how to use the android.util.Xml.Parser cause i am bit new to Android ?!
What do I have to do if I have to parse a json response ? I’m an android beginner and wanna use that (http://punchfork.com/api) API ? Can anyone give me any hints ?
Class Person has a typo. It says : ” public ArrayList imagesList; ” , but
you should change it with the class Image.
Just in case…
Hello ancanta,
Thanks for the info. We will correct it ASAP!
BRs
where is the public class moviehandler ?
You can download the projet and there you can see the moviehandler.java file
I search the site and there is no project download. Is there a different URL/site?
Class Person has a typo.