Android Full App, Part 3: Parsing the XML response

This is the third part of the “Android Full Application Tutorial” series. The complete application aims to provide an easy way of performing movies/actors searching over the internet. In the first part of the series (“Main Activity UI”), we created the Eclipse project and set up a basic interface for the main activity of the application. In the second part (“Using the HTTP API”), we used the Apache HTTP client library in order to consume an external HTTP API and integrate the API’s searching capabilities into our application. In this part we are going to see how to parse the XML response using Android’s built-in XML parsing capabilities.

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.

Related Articles :
Exit mobile version