The Android Tutorial Series Part 2 – Calculator App

My previous blog post provided an introduction to the Android platform with a characteristic Hello World example. This post intends to continue our exploration further by creating a simple calculator application using the Android platform. A calculator application is already present in the emulator environment but I thought it would be a good starting point to gain an improved understanding into the platform.

My development environment remains unchanged to the one mentioned in Part 1 of the Android tutorial series.

Let’s begin by creating a new Android application named Calculator1.0. To create the application, in the eclipse menu select File -> New -> Other -> Android Project. In the project creation wizard enter the following values:

  • Project Name: Calculator1.0
  • Build Target: Android 4.0
  • Application Name: Retain the project name. The developer is free to choose a different name.
  • Package Name: com.calculator
  • Minimum SDK: 14

The Calculator1.0 project has been created. In the eclipse package explorer, verify if the following folders are created. There should be a src folder which contains com.calculator.CalculatorActivity class. The res folder should have two sub-folders in it namely layout and values. The layout folder will have main.xml file and values folder will have strings.xml.

If the assertions made above are true, the project has been successfully created. Next, let’s proceed to adding the calculator specific functionality to the project. My aim is to create a simple application which accepts two input values and the mathematical operation to be applied on the two values.

Let’s begin by first creating the user interface. To facilitate user interface aspect separation, Android implements user interface using three components, the static definition of user interface in res/layout/main.xml, labeling and aligned aspects in res/values/strings.xml and the dynamic user interface aspects using the com.calculator.CalculatorActivity class.

First let’s begin with res/values/strings.xml. Here’s the source code of strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Calculator 1.0</string>
    <string name="operation">Select Operation</string>
    <string name="FirstInput">Input 1</string>
    <string name="SecondInput">Input 2</string>
    <string name="welcomeMessage">Welcome to Calculator</string>
    <string name="spacer">----------------------</string>
    <string name="calculate">Calculate</string>
    <string name="opPrompt">Select Operation</string>
    <string-array name="operations">
        <item >Multiply</item>
        <item >Divide</item>
        <item >Add</item>
        <item >Subtract</item>
    </string-array>
    <string name="answer">Answer:</string>
	<string name="textSize">16sp</string>
</resources>

The strings.xml acts as a repository which provides text strings for your application. The app_name resource is created by default; other resources are added as a part of the Calculator application customization. The utilization of the resources defined in the strings.xml will be elaborated upon as a part of the main.xml explanation. Two resources to keep an eye on in strings.xml are operations and textSize. The string array ‘operations’ defines the drop-down string values for the ‘operation’ field of the calculator. The textSize resource was planned to be used for standardizing the text size definition of Android widgets. Unfortunately this did not work. As per research the appropriate method to implement this is using a dimens.xml in values folder. Details are provided here. Will look into that in a subsequent post.

Let’s move on to res/layout/main.xml file.

<?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="vertical" 
    android:paddingLeft="6px" 
    android:textSize="10sp">

    <TextView
        android:id="@+id/textViewWelcomeMsg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/welcomeMessage"
        android:textAppearance="?android:attr/textAppearanceLarge" 
        android:paddingBottom="10px"/>

    <!-- <TextView
        android:id="@+id/textViewSpacer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/spacer"
        android:textAppearance="?android:attr/textAppearanceLarge" />
	-->

    <TextView
        android:id="@+id/textViewInput1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/FirstInput" />

    <EditText
        android:id="@+id/editTextInput1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:inputType="numberDecimal" 
        android:textSize="16sp" 
        android:hint="Enter first input">
        <requestFocus />
    </EditText>

    <TextView
        android:id="@+id/textViewInput2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/SecondInput" />

    <EditText
        android:id="@+id/editTextInput2"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:inputType="numberDecimal"  
        android:textSize="16sp" 
        android:hint="Enter second input"
        />
		
    <Spinner
        android:id="@+id/spinnerOperation"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:prompt="@string/opPrompt"  
        android:textSize="14sp"
        android:entries="@array/operations"
        android:drawSelectorOnTop="true"
        android:spinnerMode="dropdown"
        android:dropDownWidth="wrap_content"
       
	/>
    <!-- spinnerMode=dropdown|dialog -->
		
    <Button
        android:id="@+id/buttonCalculate"
        android:layout_width="175px"
        android:layout_height="wrap_content"
        android:text="@string/calculate"
        android:onClick="onCalculateClick" 
        android:textSize="16sp"
        android:layout_gravity="center_horizontal" 
        />

    <TextView
        android:id="@+id/textViewAns"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/answer" 
        android:textSize="20sp" 
        android:paddingTop="20px"
        android:textColor="#FFFFFF"/>
</LinearLayout>

The root level element is LinearLayout. The LinearLayout format is one of the many layout formats provided by Android. For details on other formats refer here. The LinearLayout component aligns its constituent components in a single direction i.e. vertically or horizontally. The alignment is dependent on the definition of LinearLayout’s orientation attribute. In our example we have used the vertical alignment. The rest of LinearLayout’s attributes are self-explanatory. In our example we have used four types of Android widgets. The first is the TextView, second is EditText, third Spinner and fourth is Button.

The TextView component displays text to the user. Let’s have a look at the first TextView component defined in the main.xml and its attribute values.

    <TextView
        android:id="@+id/textViewWelcomeMsg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/welcomeMessage"
        android:textAppearance="?android:attr/textAppearanceLarge" 
        android:paddingBottom="10px"/>

The id attribute establishes the unique name of the component. This value is extremely useful in dynamically searching for a specific component. The values associated with layout_width or layout_height are standard and are explained here. The ‘wrap_content’ means that the component will sized just large enough to encapsulate its contents.The value associated to the text attribute is the textual content which is displayed in the user interface. To associate the text attribute’s value with resource defined in the strings.xml we use the special notation of @string/. The value following the / character map to the resource name defined in the strings.xml; in the present case we have @string/welcomeMessage. One more point to notice is the value defined for textAppearance attribute is ?android:attr/textAppearanceLarge. This means we will be using the Android device specific value for textAppearance. There are a number of device specific values available. Details are documented here.

The next component/widget to look at is EditText. Let’s use the EditText component with id editTextInput1 from the main.xml.

    <EditText
        android:id="@+id/editTextInput1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:inputType="numberDecimal" 
        android:textSize="16sp" 
        android:hint="Enter first input">
        <requestFocus />
    </EditText>

EditText enhances the TextView widget by providing the ability to be editable. The next attribute to look at is inputType. We have used inputType value as numberDecimal. This means that the EditText will only accept numeric characters or a decimal character. The inputType supports the following different values. The hint attribute holds the text that is displayed to user before he/she submits any input to the field.

Next let’s look at the third component Spinner.

    <Spinner
        android:id="@+id/spinnerOperation"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:prompt="@string/opPrompt"  
        android:textSize="14sp"
        android:entries="@array/operations"
        android:drawSelectorOnTop="true"
        android:spinnerMode="dropdown"
        android:dropDownWidth="wrap_content"     
	/>

The Spinner widget basically provides a control that can display a drop-down list. The prompt attribute contains the text that is displayed on the Spinner. The entries points to an array resource holding all the drop-down values. In our example it refers to the operations string array. The spinnerMode attribute accepts two values drop-down or dialog. Run the application with both modes to understand the difference. The prompt text appears in the drop down in the ‘dialog’ mode and does not appear in the ‘dropdown’ mode. I have not been able to figure out why my drop-down does not appear like a traditional drop-down with a down arrow button, instead there appears a small triangle at the right hand bottom corner of the spinner widget.

The final component is the button.

    <Button
        android:id="@+id/buttonCalculate"
        android:layout_width="175px"
        android:layout_height="wrap_content"
        android:text="@string/calculate"
        android:onClick="onCalculateClick" 
        android:textSize="16sp"
        android:layout_gravity="center_horizontal" 
        />

The user is expected to key in the two input values, select the mathematical operation and click the button to trigger the computation process. The Button provides an attribute layout_gravity to decide the placement of the Button in the UI screen. The possible values for layout_gravity are provided here. To trigger the computation process the Button widget is provided with an attribute onClick which maps to the button click event. The string associated to the onClick method consists of a method implemented on the Activity instance holding the Button widget. The expected method name and signature is

public void onCalculateClick(View view) {
}

To complete the final piece of the Calculator Application, below is the source code of the CalculatorActivity class.

package com.calculator;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
//import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

public class CalculatorActivity extends Activity {
	
	private static final String MULTIPLY = "Multiply";
	private static final String DIVIDE   = "Divide";
	private static final String ADD      = "Add";
	private static final String SUBTRACT = "Subtract";
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        /*Spinner spinner = (Spinner) findViewById(R.id.spinnerOperation);
        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.operations,
                android.R.layout.simple_spinner_dropdown_item);
        spinner.setAdapter(adapter);*/
    }
    
    public void onCalculateClick(View view) {
    	switch (view.getId()) {
    	
	    	case R.id.buttonCalculate:
	    		EditText text1 = (EditText)findViewById(R.id.editTextInput1);
	    		String text1Val = text1.getText().toString();
	    		EditText text2 = (EditText)findViewById(R.id.editTextInput2);
	    		String text2Val = text2.getText().toString();
	    		Spinner spinner = (Spinner)findViewById(R.id.spinnerOperation);
	    		String value = (String)spinner.getSelectedItem();
	    		
	    		if (value == null || text1Val == null || text2Val == null) {
	    			Toast.makeText(view.getContext(), "Please select input values.",   Toast.LENGTH_LONG).show();
	    		}
	    		
	    		float input1Val = Float.parseFloat(text1Val);
	    		float input2Val = Float.parseFloat(text2Val);
	    		
	    		
	    		String retVal = calc(input1Val, input2Val, value);
    		
	    		TextView tv = (TextView)findViewById(R.id.textViewAns);
	    		tv.setText("Answer: " + retVal);
    	}
    }
    	
    private String calc(float input1, float input2, String operation) {
		
    	if (MULTIPLY.equals(operation)) {
			return Float.toString(input1 * input2);
		} else if (DIVIDE.equals(operation)) {
			if (input2 == 0) {
				return "Not A Number";
			}
			return Float.toString(input1 / input2);
		} else if (ADD.equals(operation)) {
			return Float.toString(input1 + input2);
		} else if (SUBTRACT.equals(operation)) {
			return Float.toString(input1 - input2);
		}
		
		return "Improper operation!";
	}   
}

The implementation code of the onCalculateClick and calc methods are self-explanatory. Please note the commented out code involving the ArrayAdapter. This is an alternative method for populating the drop-down dynamically.

Overall the structure of the Android application is similar to a Flex application. Probably also to Windows Presentation Foundation or Silverlight application; although I do not have any hands-on experience on those. The added advantage of Android is that one does not have to learn a new programming language unlike Flex where one needs to learn ActionScript.

Run the Android application in the eclipse environment. In case you are rerunning the application, ensure that you do not close the emulator, for details refer here. On trying to start application directly, I experienced a number of errors. Two errors are shown below:

[2011-11-11 23:27:33 - Calculator] Failed to install Calculator.apk on device 'emulator-5554!
[2011-11-11 23:27:33 - Calculator] (null)
[2011-11-11 23:27:33 - Calculator] Launch canceled!

[2011-11-11 23:33:13 - Calculator] Failed to install Calculator.apk on device 'emulator-5554': device not found
[2011-11-11 23:33:13 - Calculator] com.android.ddmlib.InstallException: device not found
[2011-11-11 23:33:13 - Calculator] Launch canceled!
[2011-11-14 11:41:05 - Calculator1.0] emulator-5554 disconnected! Cancelling 'com.calculator.CalculatorActivity activity launch'!
[2011-11-14 11:48:22 - Calculator1.0] Failed to install Calculator1.0.apk on device 'emulator-5554!
[2011-11-14 11:48:22 - Calculator1.0] (null)
[2011-11-14 11:48:22 - Calculator1.0] Failed to install Calculator1.0.apk on device 'emulator-5554': EOF
[2011-11-14 11:48:22 - Calculator1.0] com.android.ddmlib.InstallException: EOF
[2011-11-14 11:48:22 - Calculator1.0] Launch canceled!

These issues were fixed by trying to run the application again. However the behavior of the application continued to be erratic.

Finally I started using the following procedure. In case I did not have the emulator running; I started it by running the Hello World application of my previous blog post. After it was successfully running, I ran my new Calculator application in the already started emulator. It has consistently run fine. Here’s the expected console output:

[2011-11-16 18:21:40 - Calculator1.0] Android Launch!
[2011-11-16 18:21:40 - Calculator1.0] adb is running normally.
[2011-11-16 18:21:40 - Calculator1.0] Performing com.calculator.CalculatorActivity activity launch
[2011-11-16 18:21:45 - Calculator1.0] Uploading Calculator1.0.apk onto device 'emulator-5554'
[2011-11-16 18:21:45 - Calculator1.0] Installing Calculator1.0.apk...
[2011-11-16 18:21:52 - Calculator1.0] Success!
[2011-11-16 18:21:52 - Calculator1.0] Starting activity com.calculator.CalculatorActivity on device emulator-5554
[2011-11-16 18:21:53 - Calculator1.0] ActivityManager: Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.calculator/.CalculatorActivity }

This approach has been useful for debugging as well. I was trying to run the Calculator application independently and it was unable to run on the emulator. I had by oversight placed the onClick attribute in the Spinner instead of the Button. This was causing the application to fail. To add to my woes the LogCat was showing nothing giving me no idea as to the reason for the error. I used my new approach of having Hello World app to get my emulator up and running. Next I ran the Calculator application, the exception trace immediately appeared in LogCat allowing me to easily debug the problem.

That’s all for the moment. In the next blog post I will try to skim below the Android layout surface.

Advertisements

5 thoughts on “The Android Tutorial Series Part 2 – Calculator App

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s