Free Online Courses for Software Developers - MrBool
× Please, log in to give us a feedback. Click here to login
×

You must be logged to download. Click here to login

×

MrBool is totally free and you can help us to help the Developers Community around the world

Yes, I'd like to help the MrBool and the Developers Community before download

No, I'd like to download without make the donation

×

MrBool is totally free and you can help us to help the Developers Community around the world

Yes, I'd like to help the MrBool and the Developers Community before download

No, I'd like to download without make the donation

Mobile Application Integrations: advanced client XML

In this article we will discuss about mobile application integrations with advanced client XML. We will also discuss about some problems that can occur when we develop XML and date handling code for Android and how to work around it.

Introduction

In order to build a working application, there are a handful of additional features that are required. In this article, I will discuss:

  • Sending structured data back to the server (using the example of a form to create a new customer)
  • Displaying structured data (using the example of Listing a customer’s accounts)

I will also discuss a problem that can occur when developing XML and date handling code for Android and how to work around it.

Creating new customers

To add a new Activity, it is best to use the Eclipse wizard for the purpose. Right click on your source folder and choose New / Other… from the menu. In the Android category, choose “Android Activity”. I called my activity NewCustomerActivity, and based it on the blank activity template. I also deleted the options menu it created, and the method it created to initialise the menu. My layout XML looks like this:

Listing 1: The layout for NewCustomerActivity

 
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".NewCustomerActivity" >

    <TextView
        android:id="@+id/txtName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/name" />
    
    <EditText
        android:id="@+id/edtName"
        android:layout_below="@id/txtName"
        android:layout_alignLeft="@id/txtName"
        android:layout_marginTop="4dp"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:inputType="textCapWords" 
        />

    <Button 
        android:id="@+id/btnCreate"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:text="@string/create" />
    
</RelativeLayout>

I also created strings in the res/values/strings.xml file matching the two strings I used above with content “Name:” and “Create”.

The activity code is quite similar to the customer list activity code. onCreate() looks like this:

Listing 2: Initializing the NewCustomerActivity

 
	Serializer xml;
	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_new_customer);
		findViewById(R.id.btnCreate).setOnClickListener(create);
		xml = new Persister();
	}

I set the create button’s OnClickListener to an event handler I store in the field “create”. I initialise this in the field declaration using an anonymous class:

Listing 3: Handling the create button press event

 
	ProgressDialog progressDialog;
	AsyncTask<Customer, Void, Result> actionInProgress;
	private View.OnClickListener create = new View.OnClickListener() {
		
		@Override
		public void onClick(View v)
		{
			if (actionInProgress != null) return;
			
			Customer customer = new Customer();
			customer.setName(((EditText)findViewById(R.id.edtName)).getText().toString());
			actionInProgress = new CreateCustomerTask().execute(customer);
			progressDialog = new ProgressDialog(NewCustomerActivity.this);
			progressDialog.setCancelable(true);
			progressDialog.setOnCancelListener(onCancelListener);
			progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
			progressDialog.setMessage("Creating customer");
			progressDialog.show();			
		}
	};

This is quite similar to the download() method from this article, except that I create a new Customer and initialise its name field, then pass it to the execute method of CreateCustomerTask. onCancelListener is identical to the event handler of the same name in the CustomerListActivity, except that I have renamed the downloadInProgress field to actionInProgress.

CreateCustomerTask is, like the CustomerListActivity’s DownloadTask, a subclass of AsyncTask. Its structure is very similar to DownloadTask.

Listing 4: Creating the customer on the server

 
	class CreateCustomerTask extends AsyncTask<Customer, Void, Result>
	{
		@Override
		protected Result doInBackground (Customer... params)
		{
			HttpClient client = new DefaultHttpClient ();
			try
			{
				HttpPost request = new HttpPost (
					"http://192.168.1.101:8080/simplespring/xml/newCustomer");
				
				StringWriter buffer = new StringWriter();
				xml.write(params[0], buffer);
				
				StringEntity requestEntity = new StringEntity(buffer.toString());
				requestEntity.setContentType("text/xml");
				
				request.setEntity(requestEntity);
				
				HttpResponse response = client.execute (request);
				if (response.getStatusLine ().getStatusCode () != 200)
					return Result.error (
						response.getStatusLine ().getReasonPhrase ());
				
				HttpEntity entity = response.getEntity();
				String contentType = entity.getContentType().getValue();
				
				if (!contentType.startsWith("text/xml"))
					return Result.error (
						"Did not receive XML response (content type was: " + 
						contentType + ")");
				
				return xml.read(Result.class, entity.getContent());
			}
			catch (Exception e)
			{
				return Result.error (e.toString());
			}
		}

		@Override
		protected void onPostExecute(Result result)
		{
			super.onPostExecute(result);
			progressDialog.dismiss();
			actionInProgress = null;
			if (result.isError())
			{
				Toast.makeText(NewCustomerActivity.this, 
						result.getError(), 
						Toast.LENGTH_LONG).show ();
			}
			else
			{
				finish();
			}
		}
	};

Like DownloadTask, we use a DefaultHttpClient to send our request. This time, we’re sending a POST request rather than a GET request, and this means we need to encode our request’s data in XML. We use a StringWriter to capture the results of the SimpleXML serialization in a string, and then wrap this in an HttpClient StringEntity object. We give the object a content type of text/xml and send the request to the client. Checking the result from the server precedes similarly to DownloadTask. Back in the main application thread, we check for and display errors, and if the creation was successful we instruct the activity to finish, which will return us to the customer list (which will refresh automatically, so we can see our new customer). Now we just need to hook this activity in to the customer list activity so we can reach it. Add an item “New customer” to the customer list’s options menu, and handle it like this:

Listing 5: Updated menu handling for the customer list activity

 
	@Override
	public boolean onMenuItemSelected(int featureId, MenuItem item)
	{
		switch (item.getItemId())
		{
		case R.id.refresh:
			download ();
			return true;
		case R.id.newCustomer:
			startActivity (new Intent (this, NewCustomerActivity.class));
			return true;
		}
		return false;
	}

Displaying structured data in a list

When we click on customers in the customer list, we should display their full details, including their list of accounts. We’ll need a new activity to do this, so add a new blank activity as before. I called mine AccountListActivity. In the layout for the activity, I created a TextView to display the customer’s name, and then below this I created a ListView to show the customer’s list of accounts. I also created a new layout for the list items, in which I created three TextViews: one for the account number, one for the account’s balance, and one for the account’s creation date.

We don’t currently hold a creation date for the accounts, but I wanted to include this because it highlights a problem you might encounter when developing your own Android applications. So we need to modify the server to include the date. Here’s the SQL for the changes:

Listing 6: SQL for account opening dates

 
alter table accounts add opened datetime not null;
update accounts set opened=now();

And we also need to update the Account class on both server and client:

Listing 7: The Account class

@Root(name="account")
public class Account
{
	@Attribute(required=true)
	private int id;
	@Attribute(required=true)
	private double balance;
	@Element(name="customer")
	private Customer customer;
	@Attribute(required=true)
	private Date opened = new Date();

	… getter and setter methods here
}

And on the server the AccountDAO class needs updating:

Listing 8: Changed methods in the AccountDAO class

 
	private Account makeAccount (ResultSet resultSet) throws SQLException
	{
		Account result = new Account(resultSet.getInt ("account_id"));
		result.setBalance (resultSet.getDouble ("account_balance"));
		if (resultSet.findColumn ("customer_id") > 0)
			result.setCustomer (customerDAO.makeCustomer (resultSet));
		else
			result.setCustomer (customerDAO.get (resultSet.getInt ("account_customer")));
		result.setOpened (new Date(resultSet.getTimestamp ("account_opened").getTime ()));
		return result;
	}
	public boolean create (Account account) throws SQLException
	{
		try (PreparedStatement s = getConnection().prepareStatement (
			"insert into accounts (balance,customer,opened) values (?,?,?)"))
		{
			s.setDouble (1, account.getBalance());
			s.setInt (2, account.getCustomer().getId ());
			s.setTimestamp(3, new Timestamp(account.getOpened().getTime()));
			return s.execute ();
		}
	}

	public boolean update (Account account) throws SQLException
	{
		try (PreparedStatement s = getConnection().prepareStatement (
			"update accounts set balance=?, customer=?, opened=? where id=?"))
		{
			s.setDouble (1, account.getBalance());
			s.setInt (2, account.getCustomer().getId ());
			s.setTimestamp(3, new Timestamp(account.getOpened().getTime()));			
			s.setInt (4, account.getId ());
			return s.execute ();
		}
	}
	static String projection ()
	{
	    return "accounts.id account_id, accounts.balance account_balance, "+
	    		"accounts.customer account_customer, accounts.opened account_opened";
	}

To demonstrate the problem, you need to request a list of a customer’s accounts from the server. The result might look like this:

Listing 9: XML response to a customer account list request

<result success="true">
   <account id="1" balance="0.0" opened="2013-04-15 09:43:27.000 BST">
      <customer id="1" name="Joe"/>
   </account>
   <account id="2" balance="0.0" opened="2013-04-15 09:43:27.000 BST">
      <customer id="1" name="Joe"/>
   </account>
</result>

This issue is that SimpleXML will attempt to parse the values of the opened attribute using Java’s SimpleDateFormat class, but SimpleDateFormat on Android does not recognise the full list of time zones that may be produced on the server. Specifically, it does not recognize BST as a valid time zone name. To fix this problem, I modify the serialization code on the server by instructing SimpleXML to use a custom format for dates. Here’s the code for doing it:

Listing 10: Custom format for XML dates

 
public class CustomXMLFormat implements Matcher
{
	private static final class GMTDateTransform implements Transform<Date>
	{
	    ThreadLocal<SimpleDateFormat> sdf = new ThreadLocal<SimpleDateFormat> () {
	        protected SimpleDateFormat initialValue ()
	        {
	            SimpleDateFormat r = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss.SSS zzz");
	            r.setTimeZone (TimeZone.getTimeZone ("GMT"));
	            return r;
	        }
	    };

	    public Date read (String source) throws Exception 
	    {
	        return sdf.get ().parse (source);
	    }
	    public String write (Date source) throws Exception
	    {
	        return sdf.get ().format (source);
	    }
	}


	private final static GMTDateTransform gmtDateTransform = new GMTDateTransform ();
	
	@SuppressWarnings ("rawtypes")
	@Override
	public Transform match (Class cls) throws Exception
	{
		if (Date.class.isAssignableFrom (cls)) return gmtDateTransform;
		return null;
	}

}

This code is designed to be used as a callback by SimpleXML, and overrides the formatting of dates by using a SimpleDateFormat object whose time zone is set to GMT, rather than the system default time zone. Register this with SimpleXML by modifying the spring context:

Listing 11: changes to applicationContext.xml

 
    <bean id="persister" class="org.simpleframework.xml.core.Persister">
        <constructor-arg><bean class="com.example.xmlservice.CustomXMLFormat" /></constructor-arg>
    </bean>

Testing the previous request should now look something like this:

Listing 12: Updated XML response to a customer account list request

 
<result success="true">
   <account id="1" balance="0.0" opened="2013-04-15 08:43:27.000 GMT">
      <customer id="1" name="Joe"/>
   </account>
   <account id="2" balance="0.0" opened="2013-04-15 08:43:27.000 GMT">
      <customer id="1" name="Joe"/>
   </account>
</result>

Now let’s use this data. CustomerListActivity has an empty viewAccounts handler field. Change it to this:

Listing 13: On item click

	private AdapterView.OnItemClickListener viewAccounts = new AdapterView.OnItemClickListener() {

		@Override
		public void onItemClick(AdapterView<?> list, View item, int position, long uid)
		{
			Intent intent = new Intent(CustomerListActivity.this, AccountListActivity.class);
			intent.putExtra("com.example.simplespringclient.Customer", 
					customerAdapter.getItem(position));
			startActivity(intent);
		}
		
	}; 
 

In order for this to work, Customer will need to be Serializable, so add “implements Serializable” to its declaration. AccountListActivity’s onCreate then needs to extract the customer object and put the customer’s name in the TextView we created for it. We also need to retain the customer, as the download task will need the customer’s id.

Listing 14: getting the customer and using their name

 
	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_account_list);
		customer = (Customer)getIntent().getSerializableExtra(
			"com.example.simplespringclient.Customer");
		((TextView)findViewById(R.id.txtName)).setText(customer.getName());
		accountList = (ListView)findViewById(R.id.lvAccounts);
		xml = new Persister ();
		download ();
	}

The download method and its corresponding DownloadTask are almost identical to the ones used by CustomerList, except that the request URL is changed (to "…/customerAccounts?customer=" + customer.getId()) and the code we execute on success becomes:

Listing 15: Handling a successful download

 
	accountAdapter = new AccountAdapter(result.getAccounts());
	accountList.setAdapter(accountAdapter);

AccountAdapter is a custom subclass of ArrayAdapter that we’ll use to handle displaying the structured data in the list. It looks like this:

Listing 16: An ArrayAdapter subclass for displaying objects with more than one item of data

 
	class AccountAdapter extends ArrayAdapter<Account>
	{
		public AccountAdapter(List<Account> list)
		{
			super (AccountListActivity.this, R.layout.listitem_account, list);
		}
		
		@Override
		public View getView(int position, View convertView, ViewGroup parent)
		{
			if (convertView == null)
				convertView = LayoutInflater.from(AccountListActivity.this)
						.inflate(R.layout.listitem_account, null);
			
			Account account = getItem(position);
			
			((TextView)convertView.findViewById(R.id.txtId)).setText(
				""+account.getId());
			((TextView)convertView.findViewById(R.id.txtDate)).setText(
				""+account.getOpened());
			((TextView)convertView.findViewById(R.id.txtBalance)).setText(
				"€"+account.getBalance());
			
			return convertView;
		}
	}

This class uses the ArrayAdapter as its superclass to handle most of the complexity of implementing a list adapter; we only override the getView() method to bind our more complex data to the created child views rather than using the simple default implantation (which only displays a single string that is created using the target object’s toString() method). We start by checking the convertView field for null; if it is not null, we are reusing an existing child view and do not need to create a new one. If it is null, we create a new one using the LayoutInflater class and the layout template we built earlier. Then we find the three text fields that are children of our child view and set their text from fields in the account object.

Testing this, you should see the information about the customers’ accounts successfully displayed - and the dates should be in your native timezone, rather than GMT, regardless of the fact that they were transferred in GMT.

With these techniques in hand, finishing the application (e.g. by adding activities for creating new accounts and transferring balances between them) should be relatively simple.

This is all for this article, see you next time.

Also read:



My main area of specialization is Java and J2EE. I have worked on many international projects like Recorders,Websites,Crawlers etc.Also i am an Oracle Certified java professional as well as DB2 certified

What did you think of this post?
Services
[Close]
To have full access to this post (or download the associated files) you must have MrBool Credits.

  See the prices for this post in Mr.Bool Credits System below:

Individually – in this case the price for this post is US$ 0,00 (Buy it now)
in this case you will buy only this video by paying the full price with no discount.

Package of 10 credits - in this case the price for this post is US$ 0,00
This subscription is ideal if you want to download few videos. In this plan you will receive a discount of 50% in each video. Subscribe for this package!

Package of 50 credits – in this case the price for this post is US$ 0,00
This subscription is ideal if you want to download several videos. In this plan you will receive a discount of 83% in each video. Subscribe for this package!


> More info about MrBool Credits
[Close]
You must be logged to download.

Click here to login