Posts Tagged ‘android

02
Dec
10

Opening a New Activity in Android

This page describes the process of creating an Android application that will present the user with a Navigation Menu. When the user clicks they will be directed to either the “Product” or “Location” page. The “breakout” pages will have a back button that allows the user to return to the main menu.

Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

Background

This page Covers:

  • Page Layouts
  • Creating ListView with menu items and icons
  • Creating Buttons and defining onClickListeners
  • Creating and starting Activities using Intents

Requirements

Create a new Project

  1. In eclipse Right click New -> project, choose ?Android Project?
  2. Project Name: AndroidNav
  3. Package name: com.test
  4. Create Activity: MainMenuActivity
  5. Click Finish

Define the Resources

Define the resource strings

res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Custom Navigation Main Menu</string>
    <string name="product">Product</string>
    <string name="locations">Locations</string>
</resources>

Layout Files

Main Menu Activity presents a ListView containing 2 choices. Each item in the list has an icon to the left and to the right of the text. When the user clicks on the item a new page displays.

/res/layout/main.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">
     
    <ListView android:id="@+id/ListView01"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">       
    </ListView>
 
</LinearLayout>

Each row of the ListView seen above is defined by the following xml file. Each row contains an icon to the left and right of the text. An additional LinearLayout was required to get the icon to align/justify to the right.

/res/layout/row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent" android:layout_height="fill_parent"
    android:orientation="horizontal">
     
    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:src="@drawable/ic_launcher"/>
 
    <TextView
        android:id="@+id/toptext"
        android:padding="5dp"
        android:textSize="10pt"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent" />
     
    <LinearLayout
        android:layout_width="fill_parent" android:layout_height="fill_parent"     
        android:orientation="horizontal"
        android:gravity="right|center_vertical">
        <ImageView      
            android:id="@+id/navIcon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"           
            android:src="@android:drawable/ic_media_play"          
            />
    </LinearLayout>
                         
</LinearLayout>

The Product and the Location page are the “breakout” pages. They have a back button on the bottom.

/res/layout/product.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">
     
    <TextView android:text="Product Menu" android:textSize="15pt"
        android:layout_width="fill_parent" android:layout_height="wrap_content"
        android:layout_weight="1" />
 
    <Button android:id="@+id/productReturnToMainButton"
    android:layout_height="wrap_content"
        android:text="&lt; Back to Main Menu"
        android:layout_centerInParent="true"
        android:layout_width="fill_parent"></Button>
 
</LinearLayout>

/res/layout/locations.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">
     
    <TextView android:text="Locations" android:textSize="15pt"
        android:layout_width="fill_parent" android:layout_height="wrap_content"
        android:layout_weight="1" />
 
    <Button android:id="@+id/locationReturnToMainButton"
    android:layout_height="wrap_content"
        android:text="&lt; Back to Main Menu"
        android:layout_centerInParent="true"
        android:layout_width="fill_parent"></Button>
 
</LinearLayout>

Activity Classes

The following is the main activity class. This class listens to events from the ListView. It contains a switch statement that directs the user to the appropriate page based on their selection. I know its not really the best way to do it, if you guys have a suggestion to improve it please post a comment.

src/com/test/MainMenuActivity.java

package com.test;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;
 
public class MainMenuActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
 
        setTitle("Custom Navigation Main Menu");
         
        //get the reference to the list view defined in main.xml layout.
        final ListView listView = (ListView)findViewById(R.id.ListView01);
         
        listView.setAdapter(new ArrayAdapter<String>(this, R.layout.row,
                R.id.toptext, new String[] {
                        getResources().getString(R.string.product),
                        getResources().getString(R.string.locations) }));
         
        listView.setOnItemClickListener(new OnItemClickListener() {
            public void onItemClick(AdapterView<?> parent, View view,
                    int position, long id) {
                switch(position) {
                case 0:
//                    Toast.makeText(getApplicationContext(),
//                            "Product List Item was clicked.",
//                            Toast.LENGTH_SHORT).show();
                    startActivity(new Intent(getApplicationContext(),
                            ProductMenuActivity.class));
                    break;
                case 1:
//                    Toast.makeText(getApplicationContext(),
//                            "Locations List Item was clicked.",
//                            Toast.LENGTH_SHORT).show();                            
                    startActivity(new Intent(getApplicationContext(),
                            LocationMenuActivity.class));
                    break;
                }
            }
        });
    }
}

The following two activities are very similar. The implementation of the Back functionality in both are done by calling finish(). This might not be the best way of doing it since the main menu could have already been killed by android to re-claim memory.

src/com/test/LocationMenuActivity.java

package com.test;
 
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
 
public class LocationMenuActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.locations);
        Button returnToMainButton = (Button)findViewById(R.id.locationReturnToMainButton);
        if(returnToMainButton!=null) {
            returnToMainButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    finish();
                }
            });       
        }
    }
}

src/com/test/ProductMenuActivity.java

package com.test;
 
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
 
public class ProductMenuActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
         
        setContentView(R.layout.product);
         
        Button returnToMainButton = (Button)findViewById(R.id.productReturnToMainButton);
        if(returnToMainButton!=null) {
            returnToMainButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    finish();
                }
            });       
        }
    }
}

Android Manifest

The manifest file contains definitions for:

  1. Application Name (Label)
  2. Icon that represents the application
  3. Each Activity
  4. The “Main” Activity that will run when the application starts

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.test"
      android:versionCode="1"
      android:versionName="1.0">
      
        <uses-sdk
        android:minSdkVersion="10"
        android:targetSdkVersion="19" />
        
    <application android:icon="@drawable/ic_launcher" android:label="@string/app_name">
        <activity android:name=".MainMenuActivity"
                  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:name=".ProductMenuActivity"
                  android:label="@string/app_name">
        </activity>
        <activity android:name=".LocationMenuActivity"
                  android:label="@string/app_name">
        </activity>
    </application>
</manifest> 
Full downloadable source for this page is available here.
28
Nov
10

Invoking a REST method from an Android App

This page describes the process of creating an Android App that obtains and displays a list of countries from a server using a REST method.

Background

If my previous blog I mentioned the process of creating an application that displays a list of countries. Unfortunately the list was hard-coded into the Activity class.

For this page we will refer to a URL that returns JSON data all of countries that we want to display.

Requirements

The JSON Data

The data used for this app is located at the following URL:

https://ext-jsf.googlecode.com/svn/trunk/wordpress/2010/11/countryJSON.do

Project Modifications

Instead of building an application from the  begining we will take the existing app presented here and modify it to get data from the URL above.

Steps We will take

  1. Update AnroidManifest to allow our application to access the device’s internet connection
  2. Add a new method to CountryDataManager to get data using the built in Apache HTTP Client
  3. Modify the Activity Class to call the newly created method
  4. Modify the Adapter to use the default icon in case we don’t have a flag icon for the country

Manifest

Add the highlighted line to the AndroidManifest.xml

	<!-- Add this to request internet access for your application -->
	<uses-permission android:name="android.permission.INTERNET" />

DataManager

Add the following method to the datamanager. The code below uses build in Apache HTTP Client library.

src/com/test/CountryDataManager.java

    public List getCountriesFromServer(){
    	final DefaultHttpClient httpclient = new DefaultHttpClient();
    	final String URL =
            "http://ext-jsf.googlecode.com/svn/trunk/wordpress/2010/11/countryJSON.do";

    	ResponseHandler handler = new ResponseHandler()
    	{
    	    public String handleResponse(
    	            HttpResponse response) throws ClientProtocolException, IOException {
    	        HttpEntity entity = response.getEntity();
    	        if (entity != null) {
    	            return EntityUtils.toString(entity);
    	        } else {
    	            return null;
    	        }
    	    }
    	};

    	List countryList = new ArrayList();
    	try {
            HttpConnectionParams.setConnectionTimeout(httpclient.getParams(), 25000);
    		String response = httpclient.execute(new HttpGet(URL), handler);
    		try {
    			JSONArray jsonArray = new JSONArray(response);
    			int length = jsonArray.length();
    			for(int i=0; i < length; i++) {
    				JSONObject a = (JSONObject)jsonArray.get(i);
    				CountryItem country = parseJSONObject(a);
    				countryList.add(country);
    			}
    		} catch (JSONException e) {
    			throw new RuntimeException(e);
    		}
		} catch (ClientProtocolException e) {
			throw new RuntimeException(e);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
        return countryList;
    }

Activity

Modify line 37 of CountryFlagActivity.java to call the newly created method above.

src/com/test/CountryFlagActivity.java

package com.test;

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

import android.app.ListActivity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;

public class CountryFlagActivity extends ListActivity {

    private CountryAdapter countryAdapter;
    private static final CountryDataManager countryDataManager
    = new CountryDataManager();

    private ProgressDialog m_ProgressDialog = null;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        this.countryAdapter = new CountryAdapter(this, R.layout.row,
                new ArrayList());
        setListAdapter(this.countryAdapter);

        Thread t = new Thread(new Runnable() {
			@Override
			public void run() {
				final List countryList = countryDataManager.getCountriesFromServer();
				runOnUiThread(new Runnable() {
					@Override
					public void run() {
				        for(CountryItem country : countryList) {
				            countryAdapter.notifyDataSetChanged();
				            countryAdapter.add(country);
				        }
				        m_ProgressDialog.dismiss();
				        countryAdapter.notifyDataSetChanged();
					}
				});
			}
		});

        t.start();

        // shows a please wait while the data loads.
        m_ProgressDialog = ProgressDialog.show(CountryFlagActivity.this,
                "Please wait...", "Retrieving data ...", true);

        getListView().setOnItemClickListener(new OnItemClickListener() {
            public void onItemClick(AdapterView parent, View view,
                    int position, long id) {
                Toast.makeText(getApplicationContext(),
                    ((TextView)((LinearLayout) view).getChildAt(1)).getText(),
                        Toast.LENGTH_SHORT).show();
            }
        });
    }
}

Adapter

We don’t have enough flag icon images. The list of countries coming from the server is larger then the original list we had in the previous blog. Therfore if the resource can not be found we will replace it with the default icon.

You can simply replace the getView method with the one seen below.

src/com/test/CountryAdapter.java

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View v = convertView;
        if (v == null) {
            LayoutInflater vi = (LayoutInflater) getContext().getSystemService(
                    Context.LAYOUT_INFLATER_SERVICE);
            v = vi.inflate(R.layout.row, null);
        }
        CountryItem o = items.get(position);
        if (o != null) {
            TextView tt = (TextView) v.findViewById(R.id.toptext);
            if (tt != null) {
                tt.setText(o.getName());
            }
            ImageView im = (ImageView) v.findViewById(R.id.icon);
            if (im != null) {
            	Resources resources = getContext().getResources();
            	int identifier = resources.getIdentifier(
                        "@drawable/" + o.getIsoCode(), "drawable",
                        getContext().getPackageName());
            	if(identifier == 0) {
            		// flag not found. use the default icon.
            		identifier = resources.getIdentifier(
                            "@drawable/icon", "drawable",
                            getContext().getPackageName());
            	}
                im.setImageDrawable(resources.getDrawable(identifier));
            }
        }
        return v;
    }

Run the application

When you run the application you should see a larger list of countries. Also some of the countries will be using the default android icon.

That’s all for now. Feel free to leave comments if you run into trouble.

25
Nov
10

Converting Java Object to JSON String

This page covers the process of converting Java Objects to JSON Strings. And JSON Strings back to Java Objects. I will also cover how to do the conversion with ArrayList of objects.

Background

JSON is becoming a popular way to transmit data between Web Applications and User Agents (Browsers, mobile apps etc…). I have gotten into Experimenting with Android applications so I thought it would be a nice idea to write some stuff about JSON and how its used with Java.

This page contains a working “Java Main” application that you can run from command line.

Requirements

  • Java 5 or above
  • Maven 2

Create the project

I will be creating this project using Maven but you can simply find the JSON libraries and install it in the classpath and follow along.

mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart

For the group id enter: com.test
For the artifactId enter: jsonTest

Answer the rest of the questions using defaults [Hit Enter].

cd to the project’s folder

Next we create the src/main/java folder since this is not done for us using the archetype.

on unix you type:

cd jsonTest
mkdir -p src/main/java
mkdir -p src/main/java/com/test

Project Configuration

Modify the configuration and verify that it looks something like this.

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.test</groupId>
  <artifactId>jsonTest</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>jsonTest</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
<dependency>
    <groupId>org.json</groupId>
    <artifactId>json</artifactId>
    <version>20090211</version>
</dependency>
  </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.0.2</version>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Address Bean

We start by creating the Address Bean.

src/main/java/com/test/Address.java

package com.test;

public class Address {
	private String street;
	private String city;
	private String state;
	private String zip;
	
	public Address(String street, String city, String state, String zip) {
		this.street = street;
		this.city = city;
		this.state = state;
		this.zip = zip;
	}
	public String getStreet() {
		return street;
	}
	public String getCity() {
		return city;
	}
	public String getState() {
		return state;
	}
	public String getZip() {
		return zip;
	}
	public String toString() {
		return "Address [city=" + city + ", state=" + state + ", street="
				+ street + ", zip=" + zip + "]";
	}
}

Create the AddressDataManager

This class is responsible for managing instances of the address class. This class encapsulates all the JSON operations away from the Address Java Bean. It doesn’t exactly follow the “Spring Framework’s” way of doing things but it could be enhanced to work with the spring framework.

src/main/java/com/test/AddressDataManager.java

package com.test;

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

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class AddressDataManager {
	public List<Address> parseJSONArrayAddressList(String jsonString) {
		List<Address> parsedAddressList = new ArrayList<Address>();

		try {
			JSONArray jsonArray2 = new JSONArray(jsonString);
			int length = jsonArray2.length();			
			for(int i=0; i< length; i++) {
				JSONObject a = (JSONObject)jsonArray2.get(1);
				parsedAddressList.add(parseJSONObject(a));
			}
			return parsedAddressList;
		} catch (JSONException e) {
			throw new RuntimeException(e);
		}
	}

	public Address parseJSONObject(JSONObject a) throws JSONException {
		Address address = new Address(
				a.getString("street"), 
				a.getString("city"), 
				a.getString("state"), 
				a.getString("zip"));
		return address;
	}
}

Test Class

Finally this is our test class that pretty much runs the show. Reading the comments is the best way to follow along.

src/main/java/com/test/TestActivity.java

package com.test;

import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class TestActivity {
	
	public static void main(String args[]) {
		
		AddressDataManager addressDataManager = new AddressDataManager();
		
		Address a1 = new Address("100 Main Street", "New York", "NY", "10010");
		Address a2 = new Address("110 Main Street", "New York", "NY", "12345");
		Address a3 = new Address("4 Chestnut Street", "New York", "NY", "54321");
		
		// convert a object into a string
		System.out.println("JSON String: " + new JSONObject(a1).toString());
		
		// convert a string into an object
		String singleAddressJSON = 
			"{\"zip\":\"10010\",\"street\":\"100 Main Street\",\"state\":\"NY\",\"city\":\"New York\"}";

		try {
			System.out.println("Address Object: " + 
					addressDataManager.parseJSONObject(new JSONObject(singleAddressJSON)));
		} catch (JSONException e) {
			throw new RuntimeException(e);
		}
		
		// convert an ArrayList into a json array
		java.util.List<JSONObject> addressList = 
			new java.util.ArrayList<JSONObject>();

		addressList.add(new JSONObject(a1));
		addressList.add(new JSONObject(a2));
		addressList.add(new JSONObject(a3));
		
		JSONArray jsonArray = new JSONArray(addressList);
		System.out.println(jsonArray.toString());
		
		// convert JSON array to ArrayList
		final String jsonString = 
			"[{\"zip\":\"10010\",\"street\":\"100 Main Street\",\"state\":\"NY\"" +
			",\"city\":\"New York\"},{\"zip\":\"12345\",\"street\":\"110 Main Street\"" +
			",\"state\":\"NY\",\"city\":\"New York\"},{\"zip\":\"54321\",\"street\"" +
			":\"4 Chestnut Street\",\"state\":\"NY\",\"city\":\"New York\"}]";

		List<Address> parsedAddressList = addressDataManager.parseJSONArrayAddressList(jsonString);		
		System.out.println("This is the arrayList: " + parsedAddressList);
	}
}

Run the project

mvn clean compile exec:java -Dexec.mainClass=com.test.TestActivity

You should see output like this…

JSON String: {"zip":"10010","street":"100 Main Street","state":"NY","city":"New York"}
Address Object: Address [city=New York, state=NY, street=100 Main Street, zip=10010]
[{"zip":"10010","street":"100 Main Street","state":"NY","city":"New York"},{"zip":"12345","street":"110 Main Street","state":"NY","city":"New York"},{"zip":"54321","street":"4 Chestnut Street","state":"NY","city":"New York"}]
This is the arrayList: [Address [city=New York, state=NY, street=110 Main Street, zip=12345], Address [city=New York, state=NY, street=110 Main Street, zip=12345], Address [city=New York, state=NY, street=110 Main Street, zip=12345]]

Thats all for now.

21
Nov
10

Android ListView Custom ViewAdapter

This page describes the process of creating an Android application that displays a list of countries in a ListView component. The ListView will be fed data using a customized ViewAdapter. The custom adapter will display a different flag icon for each country. The list as well as the flags will be stored locally as part of the application install package. The application will later on be modified to add a please wait message while the application and data is loading.

Background

The ListView is one of the more popular layout managers on the Android OS. We will be using an ArrayAdapter to read an ArrayList of Items to display on the page.

Requirements

Create a New Project

  1. In eclipse Right click New -> project, choose “Android Project”
  2. Project Name: AndroidCountryFlagList
  3. Package name: com.test
  4. Create Activity: CountryFlagActivity
  5. Click Finish

Java Code

We start by defining the POJO class that will hold information about each country.

src/com/test/CountryItem.java

package com.test;

public class CountryItem {
	private String isoCode;
	private String name;

	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getIsoCode() {
		return isoCode;
	}
	public void setIsoCode(String isoCode) {
		this.isoCode = isoCode;
	}
}

Country DataManager

Responsible for getting the list of countries. A Thread.sleep line was added to make slow on purpose.

src/com/test/CountryDataManager.java

package com.test;
 
import java.util.ArrayList;
import java.util.List;
 
import android.util.Log;
 
public class CountryDataManager {
 
    final String[][] COUNTRIES = new String[][] {
            {"af", "Afghanistan"}
            ,{"al", "Albania"}
            ,{"dz", "Algeria"}
            ,{"as", "American Samoa"}
            ,{"ad", "Andorra"}
            ,{"ao", "Angola"}
            ,{"ai", "Anguilla"}
            ,{"aq", "Antarctica"}
            ,{"ag", "Antigua and Barbuda"}
            ,{"ar", "Argentina"}
            ,{"am", "Armenia"}
            ,{"aw", "Aruba"}
            ,{"au", "Australia"}
            ,{"at", "Austria"}
            ,{"az", "Azerbaijan" }
    };
 
    public List<CountryItem> getCountries(){
        ArrayList<CountryItem> m_countries = null;
 
        try{
          m_countries = new ArrayList<CountryItem>();
 
          for(String[] country : COUNTRIES) {
              CountryItem o1 = new CountryItem();
              o1.setIsoCode(country[0]);
              o1.setName(country[1]);
              m_countries.add(o1);
          }
 
            Thread.sleep(5000); // make this data manager be slow on purpose
          } catch (Exception e) {
          }
          return m_countries;
      }
}

Properties

This file allows for internationalization of your mobile application.

res/values/string.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Country Flag List</string>
    <string name="main_no_items">No orders to display</string>
</resources>

Layout Files

The main screen will have a list of items followed by a place holder to indicate when there is no data to display.

res/layout/main.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"
   >
	<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/main_no_items"/>
</LinearLayout>

The next file defines the layout at each row of the List view. We will have an icon to the left of the text.

res/layout/row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent" android:layout_height="wrap_content"
    android:orientation="horizontal">
 
    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/icon"/>
 
    <TextView
        android:id="@+id/toptext"
        android:padding="5dp"
        android:textSize="10pt"
        android:singleLine="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
 
</LinearLayout>

Country Adapter

The following class is the heart of what makes this all work. The ListView will call this adapter on each row, the adapter’s responsibility is to return data for that row. The adapter has intimate knowledge of the components in the current row and is able to “inject” text or data into those components. For example in line 33 and 37 the adapter knows that there is a TextView and a ImageView component on the current row.

src/com/test/CountryAdapter.java

package com.test;
 
import java.util.List;
 
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;
 
class CountryAdapter extends ArrayAdapter<CountryItem> {
 
    private List<CountryItem> items;
 
    public CountryAdapter(Context context, int textViewResourceId,
            List<CountryItem> items) {
        super(context, textViewResourceId, items);
        this.items = items;
    }
 
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View v = convertView;
        if (v == null) {
            LayoutInflater vi = (LayoutInflater) getContext().getSystemService(
                    Context.LAYOUT_INFLATER_SERVICE);
            v = vi.inflate(R.layout.row, null);
        }
        CountryItem o = items.get(position);
        if (o != null) {
            TextView tt = (TextView) v.findViewById(R.id.toptext);
            if (tt != null) {
                tt.setText(o.getName());
            }
            ImageView im = (ImageView) v.findViewById(R.id.icon);
            if (im != null) {
                im.setImageDrawable(getContext().getResources().getDrawable(
                        getContext().getResources().getIdentifier(
                                "@drawable/" + o.getIsoCode(), "drawable",
                                getContext().getPackageName())));
            }
        }
        return v;
    }
}

List Activity

This is the main activity of the application.

src/com/test/CountryFlagActivity.java

package com.test;

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

import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;

public class CountryFlagActivity extends ListActivity {

    private CountryAdapter countryAdapter;
    private static final CountryDataManager countryDataManager 
    = new CountryDataManager();
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
                
        this.countryAdapter = new CountryAdapter(this, R.layout.row, 
        		new ArrayList<CountryItem>());
        setListAdapter(this.countryAdapter);
        
        List<CountryItem> countryList = countryDataManager.getCountries();
        
        for(CountryItem country : countryList) {
        	countryAdapter.notifyDataSetChanged();
        	countryAdapter.add(country);        	
        }
        countryAdapter.notifyDataSetChanged();
        
		getListView().setOnItemClickListener(new OnItemClickListener() {
			public void onItemClick(AdapterView<?> parent, View view,
					int position, long id) {
				Toast.makeText(getApplicationContext(),
					((TextView)((LinearLayout) view).getChildAt(1)).getText(), 
						Toast.LENGTH_SHORT).show();
			}
		});
		
    }

}

Icons

Right click on each one of these icons and “Save Link As”. Save these image files into “res/drawable-hdpi” and leave the name of the file the same.

Once all the files are saved into the res/drawable-hdpi then copy all the images into the two other folders “drawable-ldpi” and “drawable-mdpi”. Since I have an HTC Evo phone it always looks in the hdpi folder for me. Depending on the phone you have it might look into these other folders. You may need to resize the icons to be smaller… I will write more about this in a future blog but for now lets hope the copying worked for you.

Run The Application

At this point save all the files and right click on the project and select run as Android application. The phone should start the application and after a brief pause it should show you a list of countries with their associated flags.

Displaying a Please Wait Message

The data manager purposefully contains a line that makes it sleep for a few seconds to simulate a slow data connection. During this time the application is not responsive and does not display a message explaining what is happening. This is a bad user experience. To fix this we will display a message to the user letting them know what is going on and behind the scenes we will retrieve the data using a Thread.

In order to make this work we need to make a slight change to the CountryFlagActivity.java class. Make your class look like the one below. The text that is different is highlighted in gray.

package com.test;

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

import android.app.ListActivity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;

public class CountryFlagActivity extends ListActivity {

    private CountryAdapter countryAdapter;
    private static final CountryDataManager countryDataManager
    = new CountryDataManager();

    private ProgressDialog m_ProgressDialog = null;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        this.countryAdapter = new CountryAdapter(this, R.layout.row,
                new ArrayList<CountryItem>());
        setListAdapter(this.countryAdapter);

        Thread t = new Thread(new Runnable() {
			@Override
			public void run() {
				final List<CountryItem> countryList = countryDataManager.getCountries();
				runOnUiThread(new Runnable() {
					@Override
					public void run() {
				        for(CountryItem country : countryList) {
				            countryAdapter.notifyDataSetChanged();
				            countryAdapter.add(country);
				        }
				        m_ProgressDialog.dismiss();
				        countryAdapter.notifyDataSetChanged();
					}
				});
			}
		});

        t.start();

        // shows a please wait while the data loads.
        m_ProgressDialog = ProgressDialog.show(CountryFlagActivity.this,
                "Please wait...", "Retrieving data ...", true);

        getListView().setOnItemClickListener(new OnItemClickListener() {
            public void onItemClick(AdapterView parent, View view,
                    int position, long id) {
                Toast.makeText(getApplicationContext(),
                    ((TextView)((LinearLayout) view).getChildAt(1)).getText(),
                        Toast.LENGTH_SHORT).show();
            }
        });
    }
}

Threading is necessary to allow stuff to happen in the background. It allows applications to be more responsive.

The call to “runOnUiThread()” on line 38 is necessary because the system does not allow non-UI threads to change the state of the countryAdapter object. The “runOnUiThread()” accepts an object that implements runnable. In this we just declare a anonymous class that does the work of adding data to and notifying the countryAdapter.

Java Code

After running the application again you should see a message like this for a few seconds before displaying the list of countries.

Up Next

Getting the list of countries and their flag icons from a remote site.

References




Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Join 74 other followers

March 2017
S M T W T F S
« Mar    
 1234
567891011
12131415161718
19202122232425
262728293031  

Blog Stats

  • 800,977 hits