Android Core

Android Full App, Part 4: Performing the API request asynchronously from the main activity

This is the fourth 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 the third part (“Parsing the XML response”) we saw how to parse the XML response using Android’s built-in XML parsing capabilities. In this part, we will tie together the HTTP retriever and XML parser services in order to perform the API search request from our application’s main activity. The request will be executed asynchronously in a background thread in order to avoid blocking the main UI thread.

On mobile applications development, one very important aspect of the application’s behavior is the smooth execution. The application’s response to user input should be quick and the whole experience should be smooth and snappy. Application responsiveness is very significant especially for the Android platform and Google has published some design guidelines for that. This is the reason that we will have the application perform the searching operations in the background, meaning that those will execute in a thread other than the main UI thread.

Despite the fact that internet connection rates on mobile devices have been tremendously improved over the last years, it still remains a fact that downloading data from the internet can be a time consuming operation. Thus, we don’t want to pause the main thread while the HTTP client waits for the data to be downloaded. Also note that stalling the main thread UI for more than five seconds will cause an “Application Not Responding” (ANR) dialog to kick in and the user will be given the opportunity to kill your application. That is definitely not a feature to have.

For that purpose, we are going to leverage the Android API and use the built in class named AsyncTask. I have explained its usage in a previous post of mine (“Android Reverse Geocoding with Yahoo API – PlaceFinder”) but in short, this class allows us to properly and easily use the UI thread. From the official documentation page: “AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers”.

Before we begin writing the asynchronous code, we will first introduce some service classes which will be responsible for performing the HTTP requests, parsing the XML responses, create the corresponding model objects and returning those to the calling Activity. Those classes will extend the abstract base class named GenericSeeker with the following source code:

package com.javacodegeeks.android.apps.moviesearchapp.services;

import java.net.URLEncoder;
import java.util.ArrayList;

public abstract class GenericSeeker<E> {
    
    protected static final String BASE_URL = "http://api.themoviedb.org/2.1/";    
    protected static final String LANGUAGE_PATH = "en/";
    protected static final String XML_FORMAT = "xml/";
    protected static final String API_KEY = "<YOUR_API_KEY_HERE>";
    protected static final String SLASH = "/";
    
    protected HttpRetriever httpRetriever = new HttpRetriever();
    protected XmlParser xmlParser = new XmlParser();
    
    public abstract ArrayList<E> find(String query);
    public abstract ArrayList<E> find(String query, int maxResults);

    public abstract String retrieveSearchMethodPath();
    
    protected String constructSearchUrl(String query) {
        StringBuffer sb = new StringBuffer();
        sb.append(BASE_URL);
        sb.append(retrieveSearchMethodPath());
        sb.append(LANGUAGE_PATH);
        sb.append(XML_FORMAT);
        sb.append(API_KEY);
        sb.append(SLASH);
        sb.append(URLEncoder.encode(query));
        return sb.toString();
    }
    
    public ArrayList<E> retrieveFirstResults(ArrayList<E> list, int maxResults) {
        ArrayList<E> newList = new ArrayList<E>();
        int count = Math.min(list.size(), maxResults);
        for (int i=0; i<count; i++) {
            newList.add(list.get(i));
        }
        return newList;
    }

}

The GenericSeeker class denotes that it is able to find results of a particular class and the extending classes will have to provide concrete implementations for the appropriate classes. The HttpRetriever and XmlParser objects from our previous tutorials will be used. Remember that the TMDb API uses similar URLs for movies and person searching:

Thus, we are using a common base URL and the extending classes have to provide the additional path by implementing the “retrieveSearchMethodPath” method. Two more methods have to be implemented, find(String) and find(String, int), which both return an ArrayList of objects with the appropriate class. The second one can be used in order to narrow down the total number of results. This could be helpful because the API typically return results which are not very relevant to the search query and could be discarded in order to have some performance gain. Finally, do not forget to replace the value of the API_KEY variable with a valid key from the TMDb site.

Next, we have the code for the two child classes, MovieSeeker and PersonSeeker. The classes are very similar, so I only present one here for reasons of brevity:

package com.javacodegeeks.android.apps.moviesearchapp.services;

import java.util.ArrayList;

import android.util.Log;

import com.javacodegeeks.android.apps.moviesearchapp.model.Movie;

public class MovieSeeker extends GenericSeeker<Movie> {
        
    private static final String MOVIE_SEARCH_PATH = "Movie.search/";
    
    public ArrayList<Movie> find(String query) {
        ArrayList<Movie> moviesList = retrieveMoviesList(query);
        return moviesList;
    }
    
    public ArrayList<Movie> find(String query, int maxResults) {
        ArrayList<Movie> moviesList = retrieveMoviesList(query);
        return retrieveFirstResults(moviesList, maxResults);
    }
    
    private ArrayList<Movie> retrieveMoviesList(String query) {
        String url = constructSearchUrl(query);
        String response = httpRetriever.retrieve(url);
        Log.d(getClass().getSimpleName(), response);
        return xmlParser.parseMoviesResponse(response);
    }

    @Override
    public String retrieveSearchMethodPath() {
        return MOVIE_SEARCH_PATH;
    }

}

The private method “retrieveMoviesList” is the core of that class. It first constructs the URL for the API call and the executes the HTTP request using the HttpRetriever class instance. If the request is successful, the XML response is feeded to the XmlParser service which is responsible for mapping the response to the Movie model object. The find(String) and find(String, int) methods are actually wrappers to the private method.

We are now ready to use the search services from our main Activity. First we create an instance of those services as follows:

...
private GenericSeeker<Movie> movieSeeker = new MovieSeeker();
private  GenericSeeker<Person> personSeeker = new PersonSeeker();
...

As mentioned in the beginning of the article, the call to the find methods of those classes should be performed in a thread other than the UI thread. It is now time to create our AsyncTask implementation, which is the following for Movie searching:

...
private class PerformMovieSearchTask extends AsyncTask<String, Void, List<Movie>> {

   @Override
   protected List<Movie> doInBackground(String... params) {
      String query = params[0];
      return movieSeeker.find(query);
   }
   
   @Override
   protected void onPostExecute(final List<Movie> result) {         
      runOnUiThread(new Runnable() {
      @Override
      public void run() {
         if (progressDialog!=null) {
            progressDialog.dismiss();
            progressDialog = null;
         }
         if (result!=null) {
               for (Movie movie : result) {
                  longToast(movie.name + " - " + movie.rating);
               }
            }
      }
       });
   }
      
}
...

First we declare that our implementation extends the class AsyncTask>. This signature means that the type of the parameters sent to the task upon execution are of type String, that no progress units will be published during the background computation (denoted by Void) and that the result of the background computation is of type List which holds Movie objects. In the doInBackground method, we perform the actual HTTP data retrieval and then, on the onPostExecute method we present the results in the form of Toast notifications. Note that another task class is named “PerformPersonSearchTask” is also created and is used for performing person searching.

Note that a ProgressDialog widget is used in order to let the user know that data retrieval takes place and that he should be patient. The progress dialog is declared cancelable in its factory method so that the user can cancel the task upon request. The relevant code is the following:

...
private void performSearch(String query) {
        
        progressDialog = ProgressDialog.show(MovieSearchAppActivity.this,
                "Please wait...", "Retrieving data...", true, true);
        
        if (moviesSearchRadioButton.isChecked()) {
            PerformMovieSearchTask task = new PerformMovieSearchTask();
            task.execute(query);
            progressDialog.setOnCancelListener(new CancelTaskOnCancelListener(task));
        }
        else if (peopleSearchRadioButton.isChecked()) {
            PerformPersonSearchTask task = new PerformPersonSearchTask();
            task.execute(query);
            progressDialog.setOnCancelListener(new CancelTaskOnCancelListener(task));
        }
        
    }
...

We also provide an implementation for the OnCancelListener of the progress dialog with the sole purpose of canceling the relevant task. The code is the following:

...
private class CancelTaskOnCancelListener implements OnCancelListener {
        private AsyncTask<?, ?, ?> task;
        public CancelTaskOnCancelListener(AsyncTask<?, ?, ?> task) {
            this.task = task;
        }
        @Override
        public void onCancel(DialogInterface dialog) {
            if (task!=null) {
                task.cancel(true);
            }
        }
    }
...

Let’s see the results of our code so far. Use Eclipse to run the project’s configuration and launch the application. Provide a query string in the edit text and hit the button to perform the search. The progress dialog appears notifying for the request operation as in the following image:

The user is able to cancel the task at any time by hitting the “Back” button. If the operation is successful, the callback method will get triggered and a toast for each result will be presented to the user as in the following image:

This concludes the fourth part of the tutorial series. 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.

6 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
mohirl
mohirl
12 years ago

Thanks for the tutorial, really helpful so far.
But unless I missed a step (quite possible!) there’s one small step missing – the OnClickListener for the searchButton in the main Activity needs to be changed to call performSearch(query); 
At the moment, in my project, it still just displays the search query with a toast, but doesn’t actually do anything.

Mayukh
Mayukh
11 years ago

I am getting this below error

01-08 12:39:45.685: W/HttpRetriever(302): Error for URL http://api.themoviedb.org/2.1/Movie.search/en/xml/09018f62a712fb9286a26450752e8162/Inception

Though when the same url is hit from browser , able to connect and see the xml.
Any idea?

Raj
Raj
10 years ago

I followed this tutorial keenly upto this part, but now have a problem that themoviedb.org api has moved to version 3 of their api. Would your tutorial undergo any changes to accommodate for this api change. The new thing appears to be a handshake for authentication.

Adarsh
Adarsh
10 years ago

Just like Raj i followed till here n found out the TMDB api have changed drastically they dont support XML anymore they have Json and Jsonp as response format.

Asad
Asad
9 years ago

will grateful if i get full source code

cheedep
cheedep
9 years ago

For anyone interested, I have the sample with the latest API at
https://github.com/cheedep/PhilmSearchApp

Back to top button