Objectives

Here we proceed to explore in detail the creating of a simple activity class + the associated layout. This process will seem quite complex on first contact - but as we proceed through the labs over the next few sessions the deep structure should start to emerge.

Create Project

Create a new Android application as demonstrated in the following 5 screenshots:

Figure 1: Welcome Screen Android Studio - Start new project Figure 2: Configure new project Figure 3: Target Android Devices Figure 4: Add an Blank Activity Figure 5: Customize the Activity

Note the 'Minimum Required SDK' which may be different from the default (Figure 3).

The opening project perspective is shown in Figure 6.

Figure 6: Donation project perspective

The project will look like this:

Figure 7: Donation project structure

With the activity_donate open in the visual designer:

Figure 8: activity_donate design view

It is important to become familiar with the structure and purpose of the three panes surrounding the Donation 'canvas':

Palette:

Figure 9: Palette

Component Tree (Outline)

Figure 10: Component tree

Properties

Figure 11: Properties

These views are closely related - and you will need to monitor the information displayed there continually as you evolve the appearance of your activities screens.

Before proceeding, open the file build.gradle in the Editor and modify as shown in Figure 12 and the code below:

Figure 12: Gradle file

apply plugin: 'com.android.application'

android {
  compileSdkVersion 23
  buildToolsVersion "23.0.3"

  defaultConfig {
    applicationId "com.example.donation"
    minSdkVersion 19
    targetSdkVersion 23
    versionCode 1
    versionName "1.0"
  }
  buildTypes {
    release {
      minifyEnabled false
      proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
  }
}

dependencies {
  compile fileTree(dir: 'libs', include: ['*.jar'])
  testCompile 'junit:junit:4.12'
  compile 'com.android.support:appcompat-v7:23.4.0'
}

Note that two files named build.gradle are present. Ensure you change the file within the app folder as shown in Figure 12.

  • The settings in this file determine the lowest standard device on which the donation app will run.
    • In this case it should install and run, at a minimum, on a device specified Android 4.1 API (Jelly Bean).

Layout Donation Activity

For this lab, our objective is to reproduce in Android this feature from the web app:

Figure 1: Objective is to recreate this make donation page from web app

In Android Studio, delete the current 'Hello World' text, and drag and drop a new'LargeText' form widget onto the canvas.

Figure 2: Replace default Hello World with Large Text widget from palette

Figure 3: Use handles to extend widget full screen width

Figure 4: Double click on widget to open text-id edit window

Figure 5: Open Resources window

Figure 6: Define a new resource name-value pair representing Welcome Homer greeting

Figure 7: Name referenced in strings.xml file Figure 8: Delete redundant resource

Note carefully the following features:

  • the guides tieing the text to the left, top and right corner
  • in Component Tree - the name of the control has been changed from a default to 'donateTitle'. This is changed by selecting the item in outline, and selecting 'Edit ID' from the context menu.
  • in Properties - where we entered 'Welcome Homer' into the text field

Recreate the above precisely.

You may toggle between the design and textual representation of the screen using the tabs located towards the foot of the Android Studio perspective as shown in Figure 9.

Figure 9: Toggle between textual and design representation of screen

Closely inspect the following two files:

res/layout/activity_dontate.xml

<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:paddingLeft="@dimen/activity_horizontal_margin"
                android:paddingRight="@dimen/activity_horizontal_margin"
                android:paddingTop="@dimen/activity_vertical_margin"
                android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".Donate">

  <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:textAppearance="?android:attr/textAppearanceLarge"
      android:text="@string/donateTitle"
      android:id="@+id/donateTitle"
      android:layout_alignParentTop="true"
      android:layout_alignParentStart="true"
      android:layout_alignParentLeft="true"
      android:layout_marginTop="23dp"
      android:layout_alignParentEnd="true"
      android:layout_alignParentRight="true"
      />
</RelativeLayout>

res/values/strings.xml

<resources>
    <string name="app_name">Donation</string>
    <string name="action_settings">Settings</string>
    <string name="donateTitle">Welcome Homer</string>
</resources>

Note the relationship between 'donateTitle' in both files. Also note we have deleted the superfluous 'hello_world' string left over from the generated app as indicated in Figure 8.

Bring in the following string into the donate activity now - (medium text) - and follow the same procedure as above. The designer should look like this:

Figure 10: Add new string

and our XML files will look like this:

<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:paddingLeft="@dimen/activity_horizontal_margin"
                android:paddingRight="@dimen/activity_horizontal_margin"
                android:paddingTop="@dimen/activity_vertical_margin"
                android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".Donate">

  <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:textAppearance="?android:attr/textAppearanceLarge"
      android:text="@string/donateTitle"
      android:id="@+id/donateTitle"
      android:layout_alignParentTop="true"
      android:layout_alignParentStart="true"
      android:layout_marginTop="23dp"
      android:layout_alignParentEnd="true"
      android:layout_alignParentLeft="true"
      android:layout_alignParentRight="true"
      />

  <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:textAppearance="?android:attr/textAppearanceMedium"
      android:text="@string/donateSubtitle"
      android:id="@+id/donateSubTitle"
      android:layout_marginTop="22dp"
      android:layout_below="@+id/donateTitle"
      android:layout_alignParentStart="true"
      android:layout_alignRight="@+id/donateTitle"
      android:layout_alignParentRight="@+id/donateTitle"
      android:layout_alignParentLeft="true"
      />

</RelativeLayout>
<resources>
    <string name="app_name">Donation</string>
    <string name="action_settings">Settings</string>
    <string name="donateTitle">Welcome Homer</string>
    <string name="donateSubtitle">Please give generously</string>
</resources>

Donate Button

Place a button directly on to the activity - attached to the bottom of the screen as shown:

Figure 1: Add new button

Following a similar procedure as in the previous step, rename the button and add an id, as shown in Figures 2 and 3.

Figure 2: Rename button name-value pair - donateButton | Donate Figure 3: Donate button

activity_donate.xml

<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:paddingLeft="@dimen/activity_horizontal_margin"
                android:paddingRight="@dimen/activity_horizontal_margin"
                android:paddingTop="@dimen/activity_vertical_margin"
                android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".Donate">

  <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textAppearance="?android:attr/textAppearanceLarge"
    android:text="@string/donateTitle"
    android:id="@+id/donateTitle"
    android:layout_alignParentTop="true"
    android:layout_alignParentStart="true"
    android:layout_marginTop="23dp"
    android:layout_alignParentEnd="true"
    android:layout_alignParentLeft="true"
    android:layout_alignParentRight="true"
    />

  <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textAppearance="?android:attr/textAppearanceMedium"
    android:text="@string/donateSubtitle"
    android:id="@+id/donateSubTitle"
    android:layout_marginTop="22dp"
    android:layout_below="@+id/donateTitle"
    android:layout_alignParentStart="true"
    android:layout_alignRight="@+id/donateTitle"
    android:layout_alignParentRight="@+id/donateTitle"
    android:layout_alignParentLeft="true"
    />

  <Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/donateButton"
    android:id="@+id/donateButton"
    android:layout_alignParentBottom="true"
    android:layout_centerHorizontal="true" />

</RelativeLayout>

strings.xml

<resources>
    <string name="app_name">Donation</string>
    <string name="action_settings">Settings</string>
    <string name="donateTitle">Welcome Homer</string>
    <string name="donateSubtitle">Please give generously</string>
    <string name="donateButton">Donate</string>
</resources>

If there is a deviation from the above - retrace your steps (delete the button) until you can match the above.

We can now switch our attention to the Java Activity class Donate:

package com.example.donation;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.widget.Button;
import android.util.Log;

public class Donate extends AppCompatActivity
{

  @Override
  protected void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_donate);
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu)
  {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.menu_donate, menu);
    return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item)
  {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();

    //noinspection SimplifiableIfStatement
    if (id == R.id.action_settings)
    {
      return true;
    }

    return super.onOptionsItemSelected(item);
  }
}

The reference to the menu item above requires a new folder in res named menu containing a file named menu_donate.xml with the following content:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      xmlns:tools="http://schemas.android.com/tools"
      tools:context=".Donate">

  <item android:id="@+id/action_settings"
        android:title="@string/action_settings"
        android:orderInCategory="100"
        app:showAsAction="never"/>
</menu>

For any 'controls' a user can interact with we usually find it useful to associate a class member with that object. Currently we have one only on - a Button. The text fields we dont consider 'interactive' as such, so we will not include those.

Insert the following new field into the class:

  private Button donateButton;

The class will have to be imported. The class name will always match the name in the Palette:

Figure 4: Palette

We are free to call the variable anything we like. However, in order to keep confusion to a minimum, always call the variable by the same name you used in the Component Tree view:

Figure 5: Component Tree view

In onCreate - we need to initialise this variable:

    donateButton = (Button) findViewById(R.id.donateButton);

We might also add a logging message so we can have some feedback as the app launches:

    Log.v("Donate", "got the donate button");

We should check the donate button actually exists before logging our success:

    if (donateButton != null)
    {
      Log.v("Donate", "Really got the donate button");
    }

This is the complete activity class:

package com.example.donation;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.widget.Button;
import android.util.Log;

public class Donate extends AppCompatActivity {

    private Button donateButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_donate);

        donateButton = (Button) findViewById(R.id.donateButton);
        if (donateButton != null) {
            Log.v("Donate", "Really got the donate button");
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_donate, menu);
        return true;
    }
}

Finding the log message can be very difficult, unless you set a filter.

In the 'LogCat' view in Android Studio, create a filter like this:

Figure 6: Configure LogCat filter

If you then select the filter, we should see our message:

Figure 7: LogCat output

Documentation

The android documentation is particularly helpful and well designed. These are the two key starting points:

The first is designed to be read though as a guide, perhaps independent of any work in Android Studio. You should get into the habit of devoting an hour or two a week just reading this section.

The Reference guide should always be open as you are working on labs or projects, and you should make a serious effort to get to grips with at least some of the information here.

Taking the Button class we have just started using. We can immediately find the reference just by knowing the import statement in our Activity class:

import android.widget.Button;

.. translates to

(note the last three segments match the package name). Open this page now. Read just as far as the "Button Style" heading. There seems to be two ways of learning when an button event occurs. The first method is using the event handler/listener - but a second easier method is also available.

Try this now. Bring in a new method into Donate class:

  public void donateButtonPressed (View view) 
  {
    Log.v("Donate", "Donate Pressed!");
   }

Import the View class:

import android.view.View;

Then, edit the activity_donate xml file - and add a new attribute into the Button xml fragment:

    <Button
        android:id="@+id/donateButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:text="@string/donateButton" 
        android:onClick="donateButtonPressed"/>

(the very last entry)

Save everything and execute the app, and monitor the log as you press the button:

Figure 1: LogCat message generated on Donation button press

We now have our first interaction working!

Research + New Control Layout

Recall the UI we are trying to implement:

Figure 1: The Web app UI we are attempting to implement in Android

We need a Radio Buttons, some sort of selection/combo box + a progress bar. These can be found in various locations in the palette:

Figure 2: RadioGroup located in Container panel in palette Figure 2: Radio buttons & progress bar located in Widgets panel in palette Figure 3: Numberpicker located in Expert panel in palette

RadioGroup, ProgressBar and NumberPicker seem likely candidates. The names of these controls are exactly as advertised, and we can find them as illustrated in the preceeding Figures 2, 3 & 4. To verify this, try importing them at the top of the Donate activity class:

import android.widget.RadioGroup;
import android.widget.NumberPicker;
import android.widget.ProgressBar;

... and we can bring in three fields into the class:

  private RadioGroup   paymentMethod;
  private ProgressBar  progressBar;
  private NumberPicker amountPicker;

We can also open up three pages of documentation - which we can reverse engineer from the package/class names:

Note this time we have gone to the Activity class before actually creating the controls. We should do this now - and remember to use the same names (for the IDs) as we create the controls.

Figure 4: Radio group, progress bar & number picker added Figure 5: Component Tree representing current layout

Getting the layout +id names as shown above may take some practice. However, it is an essential skill to get on top of, even it it takes a lot of trial and error.

For reference purposes (try to do it your self), these are the relevant generated xml files:

<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=".Donate" >

    <TextView
        android:id="@+id/donateTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:text="@string/donateTitle"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <TextView
        android:id="@+id/donateSubtitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_below="@+id/donateTitle"
        android:text="@string/donateSubtitle"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <RadioGroup
        android:id="@+id/paymentMethod"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/progressBar"
        android:layout_alignLeft="@+id/donateSubtitle"
        android:layout_below="@+id/donateSubtitle"
        android:layout_marginLeft="14dp"
        android:layout_marginTop="26dp"
        android:layout_toLeftOf="@+id/amountPicker" >

        <RadioButton
            android:id="@+id/PayPal"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:checked="true"
            android:text="@string/PayPal" />

        <RadioButton
            android:id="@+id/Direct"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/Direct" />

    </RadioGroup>

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/donateButton"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="67dp" />

    <NumberPicker
        android:id="@+id/amountPicker"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignRight="@+id/donateSubtitle"
        android:layout_alignTop="@+id/paymentMethod" />

    <Button
        android:id="@+id/donateButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_marginBottom="15dp"
        android:onClick="donateButtonPressed"
        android:text="@string/donateButton" />

</RelativeLayout>
<resources>
  <string name="app_name">Donation</string>
  <string name="action_settings">Settings</string>
  <string name="donateTitle">Welcome Homer</string>
  <string name="donateSubtitle">Please give generously</string>
  <string name="donateButton">Donate</string>
  <string name="PayPal">PayPal</string>
  <string name="Direct">Direct</string>
</resources>

If we have our naming conventions right - then we can bind to these new controls in onCreate:

    paymentMethod = (RadioGroup)   findViewById(R.id.paymentMethod);
    progressBar   = (ProgressBar)  findViewById(R.id.progressBar);
    amountPicker  = (NumberPicker) findViewById(R.id.amountPicker);

This is the complete Donate class:

package com.example.donation;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.RadioGroup;
import android.widget.NumberPicker;
import android.widget.ProgressBar;

public class Donate extends AppCompatActivity
{

    private Button donateButton;
    private RadioGroup paymentMethod;
    private ProgressBar progressBar;
    private NumberPicker amountPicker;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_donate);

        donateButton = (Button) findViewById(R.id.donateButton);
        paymentMethod = (RadioGroup)   findViewById(R.id.paymentMethod);
        progressBar   = (ProgressBar)  findViewById(R.id.progressBar);
        amountPicker  = (NumberPicker) findViewById(R.id.amountPicker);

        amountPicker.setMinValue(0);
        amountPicker.setMaxValue(1000);

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_donate, menu);
        return true;
    }


    public void donateButtonPressed (View view)
    {
        Log.v("Donate", "Donate Pressed!");
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings)
        {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

NumberPicker

This is our reference documentation:

which is a little overwhelming. Back in the guides:

we might find some useful tutorial type introduction to this control - under 'User Interface' - 'Input Controls'

.. and this is the page on 'pickers'

This documentation is concerned with Fragments - a concept that may be difficult to grasp initially, and also explores the usage of date and time pickers.

We can get up and running without this much fuss. Returning to the documentation, these three methods should be sufficient initially:

Observe that in the method onCreate, in the previous step, we have initialized the number picker minium and maxium permitted values:

    amountPicker.setMinValue(0);
    amountPicker.setMaxValue(1000);

Modify donateButtonPressed as shown here and verify that when Donate button is is pressed the method is invoked, outputting the message + value as shown in the Logcat window:

  public void donateButtonPressed (View view) 
  {
    int amount = amountPicker.getValue();
    Log.v("Donate", "Donate Pressed! with amount " + amount);
   }

Run this now - and verify that it operates as expected (see the actual amounts in the log file).

Radio Button

In donateButtonPressed we need to discover which payment method has been selected. Our RadioGroup documentation is here:

This looks like the method we need:

This is a revised version of donateButtonPressed()

  public void donateButtonPressed (View view) 
  {
    int amount = amountPicker.getValue();
    int radioId = paymentMethod.getCheckedRadioButtonId();
    String method = "";
    if (radioId == R.id.PayPal)
    {
      method = "PayPal";
    }
    else
    {
      method = "Direct";
    }
    Log.v("Donate", "Donate Pressed! with amount " + amount + ", method: " + method);
   }

Run it now and verify we are getting the correct logs.

Figure 1: logcat output

We can simplify it somewhat by reducing the if statement to a singlle line:

    String method = radioId == R.id.PayPal ? "PayPal" : "Direct";

This is the Java ternary operator:

This is the complete activity class:

package com.example.donation;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.RadioGroup;
import android.widget.NumberPicker;
import android.widget.ProgressBar;

public class Donate extends AppCompatActivity
{

  private Button donateButton;
  private RadioGroup paymentMethod;
  private ProgressBar progressBar;
  private NumberPicker amountPicker;

  @Override
  protected void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_donate);

    donateButton = (Button) findViewById(R.id.donateButton);
    paymentMethod = (RadioGroup)   findViewById(R.id.paymentMethod);
    progressBar   = (ProgressBar)  findViewById(R.id.progressBar);
    amountPicker  = (NumberPicker) findViewById(R.id.amountPicker);

    amountPicker.setMinValue(0);
    amountPicker.setMaxValue(1000);

  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu)
  {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.menu_donate, menu);
    return true;
  }


  public void donateButtonPressed (View view)
  {
    int amount = amountPicker.getValue();
    int radioId = paymentMethod.getCheckedRadioButtonId();
    String method = radioId == R.id.PayPal ? "PayPal" : "Direct";
    Log.v("Donate", "Donate Pressed! with amount " + amount + ", method: " + method);
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item)
  {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();

    //noinspection SimplifiableIfStatement
    if (id == R.id.action_settings)
    {
      return true;
    }

    return super.onOptionsItemSelected(item);
  }
}

Progress

The progress bar documentation:

offers us advice on using the progress bar in multi-threaded application. Not quite what we are ready for yet! (but file it away for future reference).

These two methods are probably what we need:

First we would need to equip our activity with the ability to remember the donation amounts:

  private int          totalDonated = 0;

Lets set max progress bar to 10000 in onCreate:

    progressBar.setMax(10000);

.. and set the progress in donateButtonPressed:

    totalDonated = totalDonated + amount;
    progressBar.setProgress(totalDonated);

Try this now and observe the progress bar.

Figure 1: Progress bar in action

This is the complete class:

package com.example.donation;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.RadioGroup;
import android.widget.NumberPicker;
import android.widget.ProgressBar;

public class Donate extends AppCompatActivity
{

  private Button       donateButton;
  private RadioGroup   paymentMethod;
  private ProgressBar  progressBar;
  private NumberPicker amountPicker;
  private int          totalDonated;

  @Override
  protected void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_donate);

    donateButton  = (Button) findViewById(R.id.donateButton);
    paymentMethod = (RadioGroup)   findViewById(R.id.paymentMethod);
    progressBar   = (ProgressBar)  findViewById(R.id.progressBar);
    amountPicker  = (NumberPicker) findViewById(R.id.amountPicker);

    amountPicker.setMinValue(0);
    amountPicker.setMaxValue(1000);
    progressBar.setMax(10000);

  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu)
  {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.menu_donate, menu);
    return true;
  }


  public void donateButtonPressed (View view)
  {
    int amount = amountPicker.getValue();
    int radioId = paymentMethod.getCheckedRadioButtonId();
    String method = radioId == R.id.PayPal ? "PayPal" : "Direct";
    totalDonated = totalDonated + amount;
    progressBar.setProgress(totalDonated);
    Log.v("Donate", "Donate Pressed! with amount " + amount + ", method: " + method);
    Log.v("Donate", "Current total " + totalDonated);
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item)
  {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();

    //noinspection SimplifiableIfStatement
    if (id == R.id.action_settings)
    {
      return true;
    }

    return super.onOptionsItemSelected(item);
  }
}

Here is another version of exactly the same class:

package com.example.donation;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.RadioGroup;
import android.widget.NumberPicker;
import android.widget.ProgressBar;

public class Donate extends AppCompatActivity
{

  private Button       donateButton;
  private RadioGroup   paymentMethod;
  private ProgressBar  progressBar;
  private NumberPicker amountPicker;
  private int          totalDonated;

  @Override
  protected void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_donate);

    donateButton  = (Button) findViewById(R.id.donateButton);
    paymentMethod = (RadioGroup)   findViewById(R.id.paymentMethod);
    progressBar   = (ProgressBar)  findViewById(R.id.progressBar);
    amountPicker  = (NumberPicker) findViewById(R.id.amountPicker);

    amountPicker.setMinValue(0);
    amountPicker.setMaxValue(1000);
    progressBar.setMax(10000);

  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu)
  {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.menu_donate, menu);
    return true;
  }


  public void donateButtonPressed (View view)
  {
    totalDonated  = totalDonated + amountPicker.getValue();
    String method = paymentMethod.getCheckedRadioButtonId() == R.id.PayPal ? "PayPal" : "Direct";
    progressBar.setProgress(totalDonated);

    Log.v("Donate", amountPicker.getValue() + " donated by " +  method + "\nCurrent total " + totalDonated);

  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item)
  {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();

    //noinspection SimplifiableIfStatement
    if (id == R.id.action_settings)
    {
      return true;
    }

    return super.onOptionsItemSelected(item);
  }
}

Examine them carefully. What are the differences? Why make these changes?

Note also the careful attention to spacing and alignment in the code. Not just correct indentation, but continual attention to structuring each method carefully, removing duplication and unnecessary code and formatting/aligning the declarations and assignment statements in a table like structure:

Visible here:

  private int          totalDonated = 0;

  private RadioGroup   paymentMethod;
  private ProgressBar  progressBar;
  private NumberPicker amountPicker;

and here:

    paymentMethod = (RadioGroup)   findViewById(R.id.paymentMethod);
    progressBar   = (ProgressBar)  findViewById(R.id.progressBar);
    amountPicker  = (NumberPicker) findViewById(R.id.amountPicker);

and here:

    totalDonated  = totalDonated + amountPicker.getValue();
    String method = paymentMethod.getCheckedRadioButtonId() == R.id.PayPal ? "PayPal" : "Direct";

Android code can become very verbose and complex. Carefully formatting is essential if you are not to be overwhelmed.

Exercises

Exercise 1:

Here is an archive of lab so far:

You may use this as a reference if you have any unresolved anomalies in your code.

The archive is tagged at end of each lab step. To obtain a snapshot at the end of, for example, step 2, simply run the command git checkout 01_02. In this case, 01_02 is a tag where 01 represents the first lab in the donation series of labs and 02 represents step 2.

You should now put your Donation application under git version control. You are already familiar with this procedure:

  • cd into the project on your computer in a terminal
  • create a suitable .gitignore file (sample below)
  • run git add .
  • commit with a suitable message
  • create a repository on your Bitbucket account named `donation-android-2016'
  • follow the Bitbucket documented procedure to establish tracking between local and remote repos
  • run the push command to initialize the remote repo with the latest local snapshot.

Use this repository pair to maintain a record of the Android Donation app as you develop it further during the course.

Sample .gitignore file:

#built application files
*.apk
*.ap_

# files for the dex VM
*.dex

# Java class files
*.class

# generated files
bin/
gen/

# Local configuration file (sdk path, etc)
local.properties

# Windows thumbnail db
Thumbs.db

# OSX files
.DS_Store

# Android Studio
# https://www.jetbrains.com/idea/help/project.html
*.iml
.idea/workspace.xml
.gradle
build/

Exercises 2:

Consider an alternative to the NumberPicker - specifically one of the "Text Fields" controls:

Figure 1: Textfields

These are mostly EditView objects:

Redesign the activity to take a value from the picker or directly from a text view:

Figure 2: Textview alternative number input

If the number picker is set to zero, then attempt to get a number from the text view.

Here is a hint (a version of donatButonPressed that does what we want):

  public void donateButtonPressed (View view) 
  {
    String method = paymentMethod.getCheckedRadioButtonId() == R.id.PayPal ? "PayPal" : "Direct";
    progressBar.setProgress(totalDonated);

    int donatedAmount =  amountPicker.getValue();
    if (donatedAmount == 0)
    {
      String text = amountText.getText().toString();
      if (!text.equals(""))
        donatedAmount = Integer.parseInt(text);
    }
    totalDonated  = totalDonated + donatedAmount;
    Log.v("Donate", amountPicker.getValue() + " donated by " +  method + "\nCurrent total " + totalDonated);
   }

Exercise 3:

Revise the app such that when the target is achieved (10000) - then no more donations accepted, and the user is made aware of this.

Hint - here is how you can display a simple alert:

      Toast toast = Toast.makeText(this, "Target Exceeded!", Toast.LENGTH_SHORT);
      toast.show();

Exercise 4:

Show on screen at all times the total amount donated.

You will use standard TextView for this:

You already have a number of these on screen. Your layout could be revised to look like this:

Figure 3: Total so far displayed