Android Core

Android Full App, Part 5: Launching new activities with intents

This is the fifth 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 the fourth part (“Performing the API request asynchronously from the main activity”), we tied together the HTTP retriever and XML parser services in order to perform the API search request from our application’s main activity. The request was executed asynchronously in a background thread in order to avoid blocking the main UI thread. In this part, we will see how to launch a new Activity and how to transfer data from one Activity to another.

Google has published the Activity and Task Design Guidelines which describe the core principles of the Android application framework, from a high-level, user-centric perspective useful to interaction and application designers as well as application developers. You should take a look at the guidelines, but for now we should just mention a few things about Activities here:

  • Activities are the main building blocks of Android applications.
  • As the user moves through the user interface, they start activities one after the other.
  • Each activity has a life-cycle that is independent of the other activities in its application or task.
  • Android keeps a linear navigation history of activities the user has visited (activity stack).
  • Data can be transferred among activities using the Intent class.

Up until now in our tutorial, we performed the movies search inside the main activity and just notified the users about the results using Toasts. We are now going to take this a step further and use a new activity in which the results will be presented as a list. For this reason, the new activity will actually extend the ListActivity Android class, which is a special kind of activity that displays a list of items by binding to a data source such as an array or Cursor.

The new activity will be launched from the main one by means of an Intent. We will use the Intent constructor that involves a Context and a target Class. From the reference documentation, we read that this constructor “provides a convenient way to create an intent that is intended to execute a hard-coded class name, rather than relying on the system to find an appropriate class for you”. Since we know which activity we wish to handle the Intent, this is the suitable one.

In order to transfer data among the two activities, we will use one of the overloaded putExtra methods that Intent class offers. The transferable data have to be Serializable and since the ArrayList is by default, we also have to mark our model classes (Movie/Person/Image) with the mark interface. Finally, we use the startActivity method to launch the known new activity.

So, inside the “PerformMovieSearchTask” class, we use the following code in order to start the new activity that will handle the search results presentation:

...
Intent intent = 
 new Intent(MovieSearchAppActivity.this, MoviesListActivity.class);
intent.putExtra("movies", result);
startActivity(intent);
...

Do not forget to mark the model classes as serializable:

...
public class Movie implements Serializable
...
public class Person implements Serializable
...
public class Image implements Serializable
...

In order to have the system find the new activity, we have to declare it in our “AndroidManifest.xml” file. This is done very easily by adding the following line inside the “application” element:

...
<activity android:name=".MoviesListActivity" />
...

The new ListActivity will retrieve the data inside its onCreate method. In order to do so, we first take a reference of the intent that launched the activity by using the getIntent method that returns the original intent. From that, we retrieve the extended data via the getSerializableExtra method, where we use the name of the item that was previously added with the putExtra method. Since the method returns a Serializable object, we perform the necessary casting.

In order to present the search results in a list we need to use an ArrayAdapter, i.e. a ListAdapter that manages a ListView backed by an array of arbitrary objects. The ArrayAdapter will be initialized with the movies list retrieved from the original activity. We then use the setListAdapter method to associate our ListActivity with the ArrayAdapter.

We wish to handle click events on each of the list’s items and more specifically to open the browser in the movie’s IMDB page. Thus, we override the onListItemClick method, which is called when an item in the list is selected. Inside that method, we retrieve the Movie object that is associated with the list item using the ArrayAdapter, find the IMDB ID and then construct the IMDB page URL, which consists of the following:

Base URL: http://m.imdb.com/title/

and the corresponding movie ID. Then, we create a new Intent with its action set to ACTION_VIEW. This is the Activity Action which in general displays data to the user. In case of a URL, the default browser will be launched and redirected to the target page. The new activity which handles the intent is launched with the usual method startActivity.

Let’s see the code for our “MoviesListActivity” class:

package com.javacodegeeks.android.apps.moviesearchapp;

import java.util.ArrayList;

import android.app.ListActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

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

public class MoviesListActivity extends ListActivity {
    
    private static final String IMDB_BASE_URL = "http://m.imdb.com/title/";
    
    private ArrayList<Movie> moviesList;
    private ArrayAdapter<Movie> moviesAdapter;
    
    @SuppressWarnings("unchecked")
    @Override
    public void onCreate(Bundle savedInstanceState) {
        
        super.onCreate(savedInstanceState);
        setContentView(R.layout.movies_layout);
        
        moviesList = (ArrayList<Movie>) getIntent().getSerializableExtra("movies");
        
        moviesAdapter = new ArrayAdapter<Movie>(this, android.R.layout.simple_list_item_1, moviesList);
        
        setListAdapter(moviesAdapter);
        
    }

    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        
        super.onListItemClick(l, v, position, id);
        Movie movie = moviesAdapter.getItem(position);
        
        String imdbId = movie.imdbId;
        if (imdbId==null || imdbId.length()==0) {
            longToast(getString(R.string.no_imdb_id_found));
            return;
        }
        
        String imdbUrl = IMDB_BASE_URL + movie.imdbId;
        
        Intent imdbIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(imdbUrl));                
        startActivity(imdbIntent);
        
    }
    
    public void longToast(CharSequence message) {
        Toast.makeText(this, message, Toast.LENGTH_LONG).show();
    }
    
}

Note that the ArrayAdapter uses the toString method of each of the encapsulated objects, so we should override the method and provide a more meaningful representation:

@Override
public String toString() {
 StringBuilder builder = new StringBuilder();
 builder.append("Movie [name=");
 builder.append(name);
 builder.append("]");
 return builder.toString();
}

The layout file that describes the content view for our activity needs to have a ListView declaration with the id “@+id/android:list”. Moreover, we provide a TextView with id “@+id/android:empty” that will appear in the case that the list includes no items. These views are included inside a LinearLayout, which is a layout that arranges its children in a single column or a single row. The XML file resides in the “res/layout” folder, it is named “movies_layout.xml” and has the following contents:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:orientation="vertical"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent"
   >
   
 <ListView
     android:id="@+id/android:list"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
     />
 <TextView
     android:id="@+id/android:empty"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
     android:text="@string/no_movies_found"/>
    
</LinearLayout>

Now, run the Eclipse configuration, provide a query string, perform a Movie search and wait until the results are retrieved.

Upon retrieval, the “MoviesListActivity” class will be invoked and the results will be displayed in a list (invoking the toString method, as mentioned above).

You can scroll down (this is handled automatically by Android) and click the movie that you wish to find details for. This will trigger an event that will open the device’s browser and point to the movie’s IMDB page:

Great! Movie information straight to our smartphone! 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

My Eclipse wants me to remove the Override notations in the main activity and the app crash without it i guess :S what should i do ?!

Abhratanu Mukherjee
Abhratanu Mukherjee
7 years ago

Replace these

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;

Mohamed Nabil Ahmed
Mohamed Nabil Ahmed
12 years ago

these are the error messages i am getting  01-26 15:50:50.535: E/AndroidRuntime(336): FATAL EXCEPTION: AsyncTask #101-26 15:50:50.535: E/AndroidRuntime(336): java.lang.RuntimeException: An error occured while executing doInBackground()01-26 15:50:50.535: E/AndroidRuntime(336): at android.os.AsyncTask$3.done(AsyncTask.java:200)01-26 15:50:50.535: E/AndroidRuntime(336): at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:274)01-26 15:50:50.535: E/AndroidRuntime(336): at java.util.concurrent.FutureTask.setException(FutureTask.java:125)01-26 15:50:50.535: E/AndroidRuntime(336): at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:308)01-26 15:50:50.535: E/AndroidRuntime(336): at java.util.concurrent.FutureTask.run(FutureTask.java:138)01-26 15:50:50.535: E/AndroidRuntime(336): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1088)01-26 15:50:50.535: E/AndroidRuntime(336): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:581)01-26 15:50:50.535: E/AndroidRuntime(336): at java.lang.Thread.run(Thread.java:1019)01-26 15:50:50.535: E/AndroidRuntime(336): Caused by: java.lang.IllegalArgumentException: Illegal character in path at index 50: http://api.themoviedb.org/2.1/Movie.search/en/xml//Inception01-26 15:50:50.535: E/AndroidRuntime(336): at java.net.URI.create(URI.java:776)01-26 15:50:50.535: E/AndroidRuntime(336): at org.apache.http.client.methods.HttpGet.(HttpGet.java:75)01-26 15:50:50.535: E/AndroidRuntime(336): at com.moviesearchapp.services.HttpRetriever.retrieve(HttpRetriever.java:22)01-26 15:50:50.535: E/AndroidRuntime(336): at com.moviesearchapp.services.MovieSeeker.retrieveMoviesList(MovieSeeker.java:20)01-26 15:50:50.535: E/AndroidRuntime(336): at com.moviesearchapp.services.MovieSeeker.find(MovieSeeker.java:11)01-26 15:50:50.535: E/AndroidRuntime(336): at com.moviesearchapp.MovieSearchActivity$PerformMovieSearchTask.doInBackground(MovieSearchActivity.java:155)01-26 15:50:50.535: E/AndroidRuntime(336): at com.moviesearchapp.MovieSearchActivity$PerformMovieSearchTask.doInBackground(MovieSearchActivity.java:1)01-26 15:50:50.535: E/AndroidRuntime(336): at… Read more »

Sam Mathew
11 years ago

works perfect..thank you guys soooooo much.

Jack Edkins
Jack Edkins
10 years ago

Can you explain the use of SuppressWarnings(“unchecked”) in MoviesListActivity class?

robi
10 years ago

Good explanation……..Have a look at………..http://androidtutorialsrkt.blogspot.in/

Rishabh Gupta
Rishabh Gupta
8 years ago

hese are the error messages i am getting 01-26 15:50:50.535: E/AndroidRuntime(336): FATAL EXCEPTION: AsyncTask #101-26 15:50:50.535: E/AndroidRuntime(336): java.lang.RuntimeException: An error occured while executing doInBackground()01-26 15:50:50.535: E/AndroidRuntime(336): at android.os.AsyncTask$3.done(AsyncTask.java:200)01-26 15:50:50.535: E/AndroidRuntime(336): at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:274)01-26 15:50:50.535: E/AndroidRuntime(336): at java.util.concurrent.FutureTask.setException(FutureTask.java:125)01-26 15:50:50.535: E/AndroidRuntime(336): at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:308)01-26 15:50:50.535: E/AndroidRuntime(336): at java.util.concurrent.FutureTask.run(FutureTask.java:138)01-26 15:50:50.535: E/AndroidRuntime(336): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1088)01-26 15:50:50.535: E/AndroidRuntime(336): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:581)01-26 15:50:50.535: E/AndroidRuntime(336): at java.lang.Thread.run(Thread.java:1019)01-26 15:50:50.535: E/AndroidRuntime(336): Caused by: java.lang.IllegalArgumentException: Illegal character in path at index 50: http://api.themoviedb.org/2.1/Movie.search/en/xml//Inception01-26 15:50:50.535: E/AndroidRuntime(336): at java.net.URI.create(URI.java:776)01-26 15:50:50.535: E/AndroidRuntime(336): at org.apache.http.client.methods.HttpGet.(HttpGet.java:75)01-26 15:50:50.535: E/AndroidRuntime(336): at com.moviesearchapp.services.HttpRetriever.retrieve(HttpRetriever.java:22)01-26 15:50:50.535: E/AndroidRuntime(336): at com.moviesearchapp.services.MovieSeeker.retrieveMoviesList(MovieSeeker.java:20)01-26 15:50:50.535: E/AndroidRuntime(336): at com.moviesearchapp.services.MovieSeeker.find(MovieSeeker.java:11)01-26 15:50:50.535: E/AndroidRuntime(336): at com.moviesearchapp.MovieSearchActivity$PerformMovieSearchTask.doInBackground(MovieSearchActivity.java:155)01-26 15:50:50.535: E/AndroidRuntime(336): at com.moviesearchapp.MovieSearchActivity$PerformMovieSearchTask.doInBackground(MovieSearchActivity.java:1)01-26 15:50:50.535: E/AndroidRuntime(336): at… Read more »

mrt
mrt
7 years ago

This tutorial dead. TMDB API changed, Apachi library deprecated. You should update it.

Back to top button