Pages

Tuesday, 29 November 2011

Using Intents in Android


1. Android Intents

1.1. Overview

Intents allow sending or receiving data from and to other activities or services. Intents are objects of type "android.content.Intent" and are used to send asynchronous messages within your application or between applications.
Intents can also be used to broadcast to the Android system that a certain event has occurred. Other components in Android can register to this event and will get notified.
Intents are a powerful concept as they allow the creation of loosely coupled applications. Intents can be used to communicate between any installed application component on the device.
An Intent object can contain information for the receiving component. For example if your application calls via an Intent a browser it may send the URL to the browser component. An Intent also contain information for the Android system so that the Android system can determine which component should handle the request.

1.2. Implicit vrs Explicit Intents

Android supports explicit intents and implicit intents. Explicit intent names the component, e.g. the Java class which should be called.
Implicit intents asked the system to perform a service without telling the system which Java class should do this service. In constructing an implicit Intent you specify the action which should be performed and optionally an URI which should be used for this action. For example you could tell the system that you want to view (action) a webpage (URI). By starting an intent for this data the system would try to find an application which is registered for this event, e.g. a browser.

1.3. Starting Activities and Sub-Activities

To start an activity use the method startActivity(Intent) if you do not need a return value from the called activity.
If you need some information from the called activity use the method startActivityForResult(). Once the called Activity is finished the method onActivityResult() in the calling activity will be called. If you use startActivityForResult() then the activity which is started is considered a "Sub-Activity".

2. More on Intents

2.1. Data Transfer

An Intent can contain data. This data call be filled by the component which creates the intent and can get extracted by the component which receives the intent.
You can add data to the Intent with the overloaded method putExtra() on the Intent. Extras are key/value pairs.
To get the Intent information in the called Activity use the method getIntent(). If the Activity was called via an implicit Intent you can receive the data and url from this Intent via getAction(), getData() and getExtras().

2.2. Intent Filter

The Android system will determine suitable applications for an implicit intent and if several applications exists offer the user the choice to open one. The determination is based on intent filters, e.g. the class "android.content.IntentFilter". Intent filters are typically defined via the "AndroidManifest.xml" file.
To react to a certain implicit intent an application component must register itself via an IntentFilter in the "AndroidManifest.xml" to this event. If a component does not define intent filters it can only be called by explicit intents.
Your application can also register itself for implicit intents in Android. These can be new intents or existing intents. For example you can register your own application for the intent which is triggered whenever someone wants to view a webpage. If you do this the user will get an popup which browser application he wants to use for the triggered intent.
For this you have to specify an intent filter for the selected event. Intent filters are typically declared in "AndroidManifest.xml". An intent filter must specify category, action and data filters.

2.3. Intents as event triggers

Intents can also be used to broadcast messages into the Android system. An Android application can registerBroadcast Receivers to these events and react accordingly. The Android system also uses Intents to broadcast system events. Your application can also register to these system events, e.g. a new email has arrived, system boot is complete or a phone call is received and react accordingly.

3. Finding out if an intent is available

Sometimes you want to find if an application has registered for a certain intent. For example you want to check if a certain receiver is available and if you enable some functionality in your app.
This can be done via checking the PackageManager. The following code checks if an intent exists. You can check via this method for intent and change your application behavior accordingly for example disable or hide menu items.

public boolean isIntentAvailable(Context context, String action) {
    final PackageManager packageManager = context.getPackageManager();
    final Intent intent = new Intent(action);
    List<ResolveInfo> resolveInfo =
            packageManager.queryIntentActivities(intent,
                    PackageManager.MATCH_DEFAULT_ONLY);
   if (resolveInfo.size() > 0) {
     return true;
    }
   return false;
}

  

4. Prerequisites for this tutorial

The following assumes that you have already basic knowledge in Android development. Please check the Android development tutorial to learn the basics.

5. Tutorial: Implicit Intents

The following creates an example project for calling several implicit intent. The Android system is asked to display a URI and chooses the corresponding application for the right URI. Create a new Android application "de.vogella.android.intent.implicit" with the Activity "CallIntents". Create the following view layout.

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

<Button android:id="@+id/Button01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Call browser"  android:onClick="callIntent"></Button>
<Button android:id="@+id/Button02" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Call Someone" android:width="100px" android:onClick="callIntent"></Button>
<Button android:id="@+id/Button03" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Dial" android:width="100px" android:onClick="callIntent"></Button>
<Button android:id="@+id/Button04" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Show Map" android:width="100px" android:onClick="callIntent"></Button>
<Button android:id="@+id/Button05" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Search on Map" android:width="100px" android:onClick="callIntent"></Button>
<Button android:id="@+id/Button06" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Take picture" android:width="100px" android:onClick="callIntent"></Button>
<Button android:id="@+id/Button07" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Show contacts" android:width="100px" android:onClick="callIntent"></Button>
<Button android:id="@+id/Button08" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Edit first contact" android:width="100px" android:onClick="callIntent"></Button>

</LinearLayout>

  

To be able to use certain intents you need to register then for your application. Maintain the following "AndroidManifest.xml".

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="de.vogella.android.intent.implicit"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".CallIntents"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
    <uses-sdk android:minSdkVersion="9" />


<uses-permission android:name="android.permission.CALL_PRIVILEGED"></uses-permission>
<uses-permission android:name="android.permission.CALL_PHONE"></uses-permission>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission android:name="android.permission.CAMERA"></uses-permission>
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
</manifest> 
  

Change your activity to the following. We will start the new intent with the method startActivityForResult() which allow us to specify a desired result code. Once the intent is finished the method onActivityResult() is called and you can perform actions based on the result of the activity.

package de.vogella.android.intent.implicit;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

public class CallIntents extends Activity {
 
/** Called when the activity is first created. */
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } public void callIntent(View view) { Intent intent = null; switch (view.getId()) { case R.id.Button01: intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.vogella.de")); startActivity(intent); break; case R.id.Button02: intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:(+49)12345789")); startActivity(intent); break; case R.id.Button03: intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:(+49)12345789")); startActivity(intent); break; case R.id.Button04: intent = new Intent(Intent.ACTION_VIEW, Uri.parse("geo:50.123,7.1434?z=19")); startActivity(intent); break; case R.id.Button05: intent = new Intent(Intent.ACTION_VIEW, Uri.parse("geo:0,0?q=query")); startActivity(intent); break; case R.id.Button06: intent = new Intent("android.media.action.IMAGE_CAPTURE"); startActivityForResult(intent, 0); break; case R.id.Button07: intent = new Intent(Intent.ACTION_VIEW, Uri.parse("content://contacts/people/")); startActivity(intent); break; case R.id.Button08: intent = new Intent(Intent.ACTION_EDIT, Uri.parse("content://contacts/people/1")); startActivity(intent); break; default: break; } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK && requestCode == 0) { String result = data.toURI(); Toast.makeText(this, result, Toast.LENGTH_LONG); } } }

If you start your application you should see an list of buttons and if you press the button, different activities should be performed. Note that you didn't specify any receiving application only the thing that should be done.

6. Tutorial: Explicit intents and data transfer between activities

The following demonstrates how you can transfer data between two activities. We will use explicit intents in this example and create two activities. The first activity will call the second one via an explicit intent. This second activity will receive data from the first one via the class "Bundle" which can be retrieved via intent.getExtras().
The second activity can be finished either via the back button on the phone or via the button. The method finish() is performed in this case. In this method you can transfer some data back to the calling activity. This is possible because we use the method startActivityForResult(). If you start an activity via this method the method onActivity result is called on the calling activity once the called activity is finshed.
Create a new Android application "de.vogella.android.intent.explicit" with the Activity "ActivityOne". Change the layout "main.xml" to the following.

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

 <LinearLayout android:id="@+id/LinearLayout01"
  android:layout_width="wrap_content" android:layout_height="wrap_content">
  <TextView android:id="@+id/TextView01" android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="First Activity. Press button to call second activity"
   android:minHeight="60dip" android:textSize="20sp"></TextView>
 </LinearLayout>
 <LinearLayout android:id="@+id/LinearLayout02"
  android:layout_width="wrap_content" android:layout_height="wrap_content"></LinearLayout>
 <Button android:id="@+id/Button01" android:layout_width="wrap_content"
  android:layout_height="wrap_content" android:onClick="onClick"
  android:text="Calling an intent"></Button>
</LinearLayout>

  

Create the layout "second.xml".

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

 <LinearLayout android:id="@+id/LinearLayout01"
  android:layout_width="wrap_content" android:layout_height="wrap_content">
  <TextView android:id="@+id/TextView01" android:layout_width="wrap_content"
   android:layout_height="wrap_content" android:text="First value from Activity 1"></TextView>
  <EditText android:text="@+id/EditText01" android:id="@+id/EditText01"
   android:layout_width="wrap_content" android:layout_height="wrap_content"></EditText>
 </LinearLayout>
 <LinearLayout android:id="@+id/LinearLayout02"
  android:layout_width="wrap_content" android:layout_height="wrap_content">
  <TextView android:id="@+id/TextView02" android:layout_width="wrap_content"
   android:layout_height="wrap_content" android:text="Second Value from Activity one"></TextView>
  <EditText android:text="@+id/EditText02" android:id="@+id/EditText02"
   android:layout_width="wrap_content" android:layout_height="wrap_content"></EditText>
 </LinearLayout>
 <Button android:id="@+id/Button01" android:layout_width="wrap_content"
  android:layout_height="wrap_content" android:onClick="onClick"
  android:text="Finished this activity"></Button>
</LinearLayout>

  

Create a new activity "ActivityTwo" via the AndroidManifest.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="de.vogella.android.intent.explicit"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".ActivityOne"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    <activity android:label="ActivityTwo" android:name="ActivityTwo"></activity>
</application>
    <uses-sdk android:minSdkVersion="9" />

</manifest> 
  

Create the following coding for your two activities. The second activity will be called from the first one, displays the transferred data and if you select the button of the back button on the phone you send some data back tot the calling application.

package de.vogella.android.intent.explicit;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

public class ActivityOne extends Activity {
 private static final int REQUEST_CODE = 10;

 
/** Called when the activity is first created. */
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } public void onClick(View view) { Intent i = new Intent(this, ActivityTwo.class); i.putExtra("Value1", "This value one for ActivityTwo "); i.putExtra("Value2", "This value two ActivityTwo"); // Set the request code to any code you like, you can identify the // callback via this code startActivityForResult(i, REQUEST_CODE); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK && requestCode == REQUEST_CODE) { if (data.hasExtra("returnKey1")) { Toast.makeText(this, data.getExtras().getString("returnKey1"), Toast.LENGTH_SHORT).show(); } } } }


package de.vogella.android.intent.explicit;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;

public class ActivityTwo extends Activity {

 
/** Called when the activity is first created. */
@Override public void onCreate(Bundle bundle) { super.onCreate(bundle); setContentView(R.layout.second); Bundle extras = getIntent().getExtras(); if (extras == null) { return; } String value1 = extras.getString("Value1"); String value2 = extras.getString("Value2"); if (value1 != null && value2 != null) { EditText text1 = (EditText) findViewById(R.id.EditText01); EditText text2 = (EditText) findViewById(R.id.EditText02); text1.setText(value1); text2.setText(value2); } } public void onClick(View view) { finish(); } @Override public void finish() { Intent data = new Intent(); data.putExtra("returnKey1", "Swinging on a star. "); data.putExtra("returnKey2", "You could be better then you are. "); setResult(RESULT_OK, data); super.finish(); } }

7. Tutorial: Registering via Intentfilter

Lets create a small browser. Our application will register itself as a browser and will display the HTML code for a giving webpage.
Create the Android project "de.vogella.android.intent.browserfilter" with the activity "BrowserActivitiy". Change "AndroidManifest.mf" to the following to register your application to the browser view intent. THe following also request the permission to access the Internet.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="de.vogella.android.intent.browserfilter"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".BrowserActivitiy"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="http"/> 
            </intent-filter>
        </activity>

    </application>
    <uses-sdk android:minSdkVersion="9" />

<uses-permission android:name="android.permission.INTERNET"></uses-permission>
</manifest> 
  

Change "main.xml" to the following.

<?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"
    >
<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:id="@+id/textView"/>
</LinearLayout>

  

As your activity gets called with an intent you can get the data from the intent and display it in your application.

package de.vogella.android.intent.browserfilter;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.widget.TextView;

public class BrowserActivity extends Activity {
 
/** Called when the activity is first created. */
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Intent intent = getIntent(); TextView text = (TextView) findViewById(R.id.textView); // To get the action of the intent use System.out.println(intent.getAction()); // We current open a hard-coded URL // To get the data the intent use the following line //Uri data = intent.getData(); URL url; try { // url = new URL(data.getScheme(), data.getHost(), data.getPath()); url = new URL("http://www.vogella.de"); BufferedReader rd = new BufferedReader(new InputStreamReader( url.openStream())); String line = ""; while ((line = rd.readLine()) != null) { text.append(line); } } catch (Exception e) { e.printStackTrace(); } } }

If you call and URL you should be able to select your browser and the HTML code should be loaded into your text view.



Android ListView and ListActivity


1. Android and Lists

1.1. ListView

The display of Lists is a very common pattern in mobile applications. The user gets a list of items can scroll through them and select one item. This selection is used to trigger something else.
Android provides the view "ListView" which is capable of displaying a scrollable list of items.

1.2. ListActivity

You can directly use the "ListView" in your layout as any other UI component. In case your Activity is primary showing a list you can extend the activity "ListActivity" which simplifies the handling of a "ListView".
"ListActivity" extends "Activity" and provides simplified handling of lists. For example you have a predefine method if someone clicks on a list element.
"ListActivity" contains a "ListAdapter" which is responsible for managing the data. This adapter must be set in the onCreate() method of your Activity via the method setListAdapter().
If the user select in the list a list entry the method onListItemClick() will be called. This method allows to access the selected element.
Android provides already some default layouts which you can use in your Adapter, e.g. "android.R.layout.simple_list_item1". In case you don't want to use one of the pre-defined layouts your own layout must have an element with the id "@android:id/list" which is the ListView. This is defined in XML via the id attribute on the ListView. For example:

<ListView
  android:id="@+android:id/list"
  android:layout_width="match_parent"
  android:layout_height="wrap_content" >
</ListView>
   

You can also use a view with the id "@android:id/empty". This view is displayed if the list is empty. For example you could display here an error message.

2. Adapter

2.1. What are Adapters?

"ListView" gets the data to display via an adapter. An adapter which must extend "BaseAdapter" and is responsible for providing the data model for the list and for converting the data into the fields of the list.
Android has two standard adapters, ArrayAdapter and CursorAdapter . "ArrayAdapter" can handle data based on Arrays or Lists while "SimpleCursorAdapter" handle database related data. You can develop your own Adapter by extending these classes or the BaseAdapter class.
The most important method of the Adapter is getView(). getView() is called for every line in the ListView to determine which data should be displayed in the line and how. getView() also provides a parameter "convertView" which allow to re-use an existing row of the table which is not displayed anymore because the user scrolled it out of the visible part of the list. If this convertView is not null it can be re-used, e.g. you can only update the content of the row and you don't have to load the layout for the view which is a big performance saver as handling of XML files is an expensive operation.

2.2. ListViews and performance

Displaying a large dataset must be efficiently implemented on a mobile device. Therefore the ListView only creates views (widget) if needed and attach them to the view hierarchy. The default Adapter implementation for a ListView will recycle views, e.g. if a row is not displayed anymore it will be recycled and only its content will change. If you implement your own adapter for a view you also should do this to avoid performance problems.

3. Tutorial: Simple ListActivity

In this chapter we will create an extremely simple list based on some default provides by Android. We use the default Adapter class "ArrayAdapter" and a layout which is predefined by Android.
Create a new Android project "de.vogella.android.listactivity" with the activity "MyListActivity". You do not need to change the default layout "main.xml". Create the following activity.

package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

public class MyListActivity extends ListActivity {
 public void onCreate(Bundle icicle) {
  super.onCreate(icicle);
  String[] values = new String[] { "Android", "iPhone", "WindowsMobile",
    "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
    "Linux", "OS/2" };
  ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
    android.R.layout.simple_list_item_1, values);
  setListAdapter(adapter);
 }

 @Override
 protected void onListItemClick(ListView l, View v, int position, long id) {
  String item = (String) getListAdapter().getItem(position);
  Toast.makeText(this, item + " selected", Toast.LENGTH_LONG).show();
 }
}
  

Note that we do not use the setContentView(). An ListActivity has per default a ListView available if you do not specify a layout which should be used.

ListActivity shows the Items

4. Tutorial: ListActivity with own layout

You can also define your own layout for the rows and assign this layout to your adapter. If you do this the layout will be the same for every entry, we will later learn how to be more flexible. In our example we will add a fixed icon to each list entry.
Create the following layout file "rowlayout.xml" in the res/layout folder of your project "de.vogella.android.listactivity".

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" >

    <ImageView
        android:id="@+id/icon"
        android:layout_width="22px"
        android:layout_height="22px"
        android:layout_marginLeft="4px"
        android:layout_marginRight="10px"
        android:layout_marginTop="4px"
        android:src="@drawable/ic_launcher" >
    </ImageView>

    <TextView
        android:id="@+id/label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@+id/label"
        android:textSize="20px" >
    </TextView>

</LinearLayout>
  

Change your activity to the following. This is almost the same coding as in the previous example, the only difference is that we are using our own layout in the ArrayAdapter and telling the adapter which UI element should contains the text. This was not necessary in the previous example a standard layout was used. If the text id is not provides Android searches for an element with the id @android:id/text1 in the layout of the row.

package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

public class MyListActivity extends ListActivity {
 public void onCreate(Bundle icicle) {
  super.onCreate(icicle);
  String[] values = new String[] { "Android", "iPhone", "WindowsMobile",
    "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
    "Linux", "OS/2" };
  // Use your own layout
  ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
    R.layout.rowlayout, R.id.label, values);
  setListAdapter(adapter);
 }

 @Override
 protected void onListItemClick(ListView l, View v, int position, long id) {
  String item = (String) getListAdapter().getItem(position);
  Toast.makeText(this, item + " selected", Toast.LENGTH_LONG).show();
 }
}
  


5. Tutorial: ListActivities with flexible layout

5.1. Overview

The above example uses one layout for all rows. If you want to influence the display of the different rows you can define your own adapter and override the getView() method.
This method is responsible for creating the individual rows of your "ListView". getView() returns a View. This view is typically a Layout (ViewGroup) and contains several other views, e.g. an ImageView and a TextView. Within getView() you also set the values of the individual views.
To read an layout from an XML resource in getView() you can use the system service LayoutInflator.
In this example we extend ArrayAdapter but we could also directly implement "BaseAdapter"

5.2. Defining a simple Adapter

The not performance optimized implementation of an own Adapter is straightforward. Receive in your constructor the Activity and the data you want to display and save them to a field. In your getView() method you inflate your pre-defined layout for the rows and receive the relevant elements via findViewById() on the layout. You can then set their properties.
The following uses two images "no.png" and "ok.png". I placed it in the "res/drawable-mdpi" folder. You must create your own icons. In case you don't find any icons just copy "icon.png" and use a drawing program to change it a little bit.
Create the class "MySimpleArrayAdapter" which will serve as our adapter.

package de.vogella.android.listactivity;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class MySimpleArrayAdapter extends ArrayAdapter<String> {
 private final Context context;
 private final String[] values;

 public MySimpleArrayAdapter(Context context, String[] values) {
  super(context, R.layout.rowlayout, values);
  this.context = context;
  this.values = values;
 }

 @Override
 public View getView(int position, View convertView, ViewGroup parent) {
  LayoutInflater inflater = (LayoutInflater) context
    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  View rowView = inflater.inflate(R.layout.rowlayout, parent, false);
  TextView textView = (TextView) rowView.findViewById(R.id.label);
  ImageView imageView = (ImageView) rowView.findViewById(R.id.icon);
  textView.setText(values[position]);
  // Change the icon for Windows and iPhone
  String s = values[position];
  if (s.startsWith("Windows7") || s.startsWith("iPhone")
    || s.startsWith("Solaris")) {
   imageView.setImageResource(R.drawable.no);
  } else {
   imageView.setImageResource(R.drawable.ok);
  }

  return rowView;
 }
}

   

To use this adapter, change the class "MyList" to the following.

package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;

public class MyListActivity extends ListActivity {
 public void onCreate(Bundle icicle) {
  super.onCreate(icicle);
  String[] values = new String[] { "Android", "iPhone", "WindowsMobile",
    "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
    "Linux", "OS/2" };
  MySimpleArrayAdapter adapter = new MySimpleArrayAdapter(this, values);
  setListAdapter(adapter);
 }

}
   

If you run this example you should get a list with different icons for the certain elements.

5.3. Performance Optimization for your own Adapter

Creating Java objects for each row is time and memory consumption. As described earlier Android recycles rows (views) of your list which are not displayed anymore and passes them to the method getView() as convertView parameter
Your adapter implementation can re-use this view and can avoid inflating a layout for this row. This saves memory and CPU consumption.
In your implementation you have to check if convertView is not null and if not then you can re-use it by setting the new data into the existing layout.
Our implementation will also use the so-called "ViewHolder" pattern. The method findViewById() is a expensive operation, therefore we should avoid doing this operation if not necessary.
The ViewHolder stores a reference to the required views in a row. This ViewHolder is then attached to the row via the method setTag(). Every view can get a tag assigned. If the row is recycled we can get the ViewHolder via getTag() method. This seems like a lot of overhead but is much faster then the repetitive call of findViewById().
Both techniques (re-used of existing views and the ViewHolder pattern) improve the performance of a ListView by approx. 175%, especially for larger data sets.
We still using the project "de.vogella.android.listactivity". Create the following class "MyArrayAdapter.java".

package de.vogella.android.listactivity;

import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class MyArrayAdapter extends ArrayAdapter<String> {
 private final Activity context;
 private final String[] names;

 public MyArrayAdapter(Activity context, String[] names) {
  super(context, R.layout.rowlayout, names);
  this.context = context;
  this.names = names;
 }

 // static to save the reference to the outer class and to avoid access to
 // any members of the containing class
 static class ViewHolder {
  public ImageView imageView;
  public TextView textView;
 }

 @Override
 public View getView(int position, View convertView, ViewGroup parent) {
  // ViewHolder will buffer the assess to the individual fields of the row
  // layout

  ViewHolder holder;
  // Recycle existing view if passed as parameter
  // This will save memory and time on Android
  // This only works if the base layout for all classes are the same
  View rowView = convertView;
  if (rowView == null) {
   LayoutInflater inflater = context.getLayoutInflater();
   rowView = inflater.inflate(R.layout.rowlayout, null, true);
   holder = new ViewHolder();
   holder.textView = (TextView) rowView.findViewById(R.id.label);
   holder.imageView = (ImageView) rowView.findViewById(R.id.icon);
   rowView.setTag(holder);
  } else {
   holder = (ViewHolder) rowView.getTag();
  }

  holder.textView.setText(names[position]);
  // Change the icon for Windows and iPhone
  String s = names[position];
  if (s.startsWith("Windows7") || s.startsWith("iPhone")
    || s.startsWith("Solaris")) {

   holder.imageView.setImageResource(R.drawable.no);
  } else {
   holder.imageView.setImageResource(R.drawable.ok);
  }

  return rowView;
 }
}

   


package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;

public class MyListActivity extends ListActivity {
 public void onCreate(Bundle icicle) {
  super.onCreate(icicle);
  String[] values = new String[] { "Android", "iPhone", "WindowsMobile",
    "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
    "Linux", "OS/2" };
  setListAdapter(new MyArrayAdapter(this, values));
 }

}
   


6. Advanced ListActivities

6.1. Adding a longclick listener to the list items

You can also add a LongItemClickListener to the view. For this receive the ListView via the method getListVIew() and set the long click listener via the method setOnItemLongClickListener().

package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

public class MyList extends ListActivity {

 
/** Called when the activity is first created. */
public void onCreate(Bundle icicle) { super.onCreate(icicle); // Create an array of Strings, that will be put to our ListActivity String[] names = new String[] { "Linux", "Windows7", "Eclipse", "Suse", "Ubuntu", "Solaris", "Android", "iPhone", "Linux", "Windows7", "Eclipse", "Suse", "Ubuntu", "Solaris", "Android", "iPhone" }; ArrayAdapter<String> adapter = new TwoLayoutsArrayAdapter(this, names); setListAdapter(adapter); ListView list = getListView(); list.setOnItemLongClickListener(new OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { Toast.makeText(MyList.this, "Item in position " + position + " clicked", Toast.LENGTH_LONG).show(); // Return true to consume the click event. In this case the // onListItemClick listener is not called anymore. return true; } }); } @Override protected void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); // Get the item that was clicked Object o = this.getListAdapter().getItem(position); String keyword = o.toString(); Toast.makeText(this, "You selected: " + keyword, Toast.LENGTH_SHORT) .show(); } }

6.2.  Rows interacting with the data model

Your row layout can also contain views which interact with the underlying data model. For example you can have a "Checkbox" view in your row and if the checkbox is selected you change the data which is displayed in the row.
We still use the same project. Create a new row layout "rowbuttonlayout.xml"

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent" android:layout_height="wrap_content">
 <TextView android:text="@+id/label" android:layout_width="wrap_content"
  android:layout_height="wrap_content" android:id="@+id/label"
  android:textSize="30px"></TextView>
 <CheckBox android:id="@+id/check" android:layout_width="wrap_content"
  android:layout_height="wrap_content" android:layout_marginLeft="4px"
  android:layout_marginRight="10px" android:layout_alignParentRight="true"
  ></CheckBox>
</RelativeLayout>

   

create for this example the class "Model" which hold the name and the information if this element is currently selected.

package de.vogella.android.listactivity;

public class Model {

 private String name;
 private boolean selected;

 public Model(String name) {
  this.name = name;
  selected = false;
 }

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public boolean isSelected() {
  return selected;
 }

 public void setSelected(boolean selected) {
  this.selected = selected;
 }

}

   

Create the following Adapter. This adapter will add a listener on the Checkbox. If the checkbox is selected the underlying data of the model is also changed. Search Checkbox gets its model element assigned via the setTag() method.

package de.vogella.android.listactivity;

import java.util.List;

import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;

public class InteractiveArrayAdapter extends ArrayAdapter<Model> {

 private final List<Model> list;
 private final Activity context;

 public InteractiveArrayAdapter(Activity context, List<Model> list) {
  super(context, R.layout.rowbuttonlayout, list);
  this.context = context;
  this.list = list;
 }

 static class ViewHolder {
  protected TextView text;
  protected CheckBox checkbox;
 }

 @Override
 public View getView(int position, View convertView, ViewGroup parent) {
  View view = null;
  if (convertView == null) {
   LayoutInflater inflator = context.getLayoutInflater();
   view = inflator.inflate(R.layout.rowbuttonlayout, null);
   final ViewHolder viewHolder = new ViewHolder();
   viewHolder.text = (TextView) view.findViewById(R.id.label);
   viewHolder.checkbox = (CheckBox) view.findViewById(R.id.check);
   viewHolder.checkbox
     .setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

      @Override
      public void onCheckedChanged(CompoundButton buttonView,
        boolean isChecked) {
       Model element = (Model) viewHolder.checkbox
         .getTag();
       element.setSelected(buttonView.isChecked());

      }
     });
   view.setTag(viewHolder);
   viewHolder.checkbox.setTag(list.get(position));
  } else {
   view = convertView;
   ((ViewHolder) view.getTag()).checkbox.setTag(list.get(position));
  }
  ViewHolder holder = (ViewHolder) view.getTag();
  holder.text.setText(list.get(position).getName());
  holder.checkbox.setChecked(list.get(position).isSelected());
  return view;
 }
}

   

Finally change your "ListView" to the following.

package de.vogella.android.listactivity;

import java.util.ArrayList;
import java.util.List;

import android.app.ListActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;

public class MyList extends ListActivity {

 
/** Called when the activity is first created. */
public void onCreate(Bundle icicle) { super.onCreate(icicle); // Create an array of Strings, that will be put to our ListActivity ArrayAdapter<Model> adapter = new InteractiveArrayAdapter(this, getModel()); setListAdapter(adapter); } private List<Model> getModel() { List<Model> list = new ArrayList<Model>(); list.add(get("Linux")); list.add(get("Windows7")); list.add(get("Suse")); list.add(get("Eclipse")); list.add(get("Ubuntu")); list.add(get("Solaris")); list.add(get("Android")); list.add(get("iPhone")); // Initially select one of the items list.get(1).setSelected(true); return list; } private Model get(String s) { return new Model(s); } }

If you start your app you should be able to flag items. These changes will be reflected in your model.

7. Single vrs. Multiselection

You can also support single and multi selection. See the following snippets for examples. To get the selected item(s) use listView.getCheckedItemPosition() or listView.getCheckedItemPositions(). If you have stable ID you could also use listView.getCheckedItemIds() to get the selected ids.

package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MyList extends ListActivity {

 
/** Called when the activity is first created. */
public void onCreate(Bundle icicle) { super.onCreate(icicle); // Create an array of Strings, that will be put to our ListActivity String[] names = new String[] { "Linux", "Windows7", "Eclipse", "Suse", "Ubuntu", "Solaris", "Android", "iPhone", "Linux", "Windows7", "Eclipse", "Suse", "Ubuntu", "Solaris", "Android", "iPhone" }; setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_multiple_choice, android.R.id.text1, names)); ListView listView = getListView(); listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); } }


package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MyList extends ListActivity {

 
/** Called when the activity is first created. */
public void onCreate(Bundle icicle) { super.onCreate(icicle); // Create an array of Strings, that will be put to our ListActivity String[] names = new String[] { "Linux", "Windows7", "Eclipse", "Suse", "Ubuntu", "Solaris", "Android", "iPhone", "Linux", "Windows7", "Eclipse", "Suse", "Ubuntu", "Solaris", "Android", "iPhone" }; setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_single_choice, android.R.id.text1, names)); ListView listView = getListView(); listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); } }

8. Header and Footer

You can of course put arbitray elements around your ListView. For example you can define a layout with two TextViews and a ListView between them. If you do this, you must assign the id "@android:id/list" to the ListView, as the ListActivity searches for a view with this id. If you do this then one TextView will always be visible above the List (header) and the other will be visible below the ListView. If you want to display the header / footer view only if see the beginning / end of the list you can use view.setHeaderView() or view.setFooterView(). For example:

package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MyList extends ListActivity {

 
/** Called when the activity is first created. */
public void onCreate(Bundle icicle) { super.onCreate(icicle); // Create an array of Strings, that will be put to our ListActivity String[] names = new String[] { "Linux", "Windows7", "Eclipse", "Suse", "Ubuntu", "Solaris", "Android", "iPhone", "Linux", "Windows7", "Eclipse", "Suse", "Ubuntu", "Solaris", "Android", "iPhone" }; View header = getLayoutInflater().inflate(R.layout.header, null); View footer = getLayoutInflater().inflate(R.layout.footer, null); ListView listView = getListView(); listView.addHeaderView(header); listView.addFooterView(footer); setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_single_choice, android.R.id.text1, names)); } }

9. SimpleCursorAdapter

In case you work with a content provider or directly with the database you can use the SimpleCursorAdapter to define the data for your ListView. For more information on Android and SQLight please see the SQLite and Lists Tutorial .
Create a new Android project "de.vogella.android.listactivity.cursor" with the activity "MyListActivity". Create the following activity.

package de.vogella.android.listactivity.cursor;

import android.app.ListActivity;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.widget.ListAdapter;
import android.widget.SimpleCursorAdapter;

public class MyListActivity extends ListActivity {
 
/** Called when the activity is first created. */
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Cursor mCursor = getContacts(); startManagingCursor(mCursor); // Now create a new list adapter bound to the cursor. // SimpleListAdapter is designed for binding to a Cursor. ListAdapter adapter = new SimpleCursorAdapter(this, // Context. android.R.layout.two_line_list_item, // Specify the row template // to use (here, two // columns bound to the // two retrieved cursor // rows). mCursor, // Pass in the cursor to bind to. // Array of cursor columns to bind to. new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME }, // Parallel array of which template objects to bind to those // columns. new int[] { android.R.id.text1, android.R.id.text2 }); // Bind to our new adapter. setListAdapter(adapter); } private Cursor getContacts() { // Run query Uri uri = ContactsContract.Contacts.CONTENT_URI; String[] projection = new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME }; String selection = ContactsContract.Contacts.IN_VISIBLE_GROUP + " = '" + ("1") + "'"; String[] selectionArgs = null; String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"; return managedQuery(uri, projection, selection, selectionArgs, sortOrder); } }

Make sure you give your application the permission to read the contacts. (Uses Permissions "android.permission.READ_CONTACTS" in AndroidManifest.xml)