Android Core

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 :

Ilias Tsagklis

Ilias is a software developer turned online entrepreneur. He is co-founder and Executive Editor at Java Code Geeks.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

8 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Mohamed Nabil Ahmed
Mohamed Nabil Ahmed
12 years ago

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 ?!

Timo Geinrich
Timo Geinrich
12 years ago

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 ?

ancanta
ancanta
12 years ago

Class Person has a typo. It says :  ” public ArrayList imagesList; ” ,  but 
you should change it with the class Image.
Just in case…

Byron Kiourtzoglou
12 years ago
Reply to  ancanta

Hello ancanta,

Thanks for the info. We will correct it ASAP!

BRs

Jonan Mendez
11 years ago

where is the public class moviehandler ?

Anas
Anas
10 years ago
Reply to  Jonan Mendez

You can download the projet and there you can see the moviehandler.java file

lavalleed
lavalleed
9 years ago
Reply to  Anas

I search the site and there is no project download. Is there a different URL/site?

sara
sara
2 years ago

Class Person has a typo.

Back to top button