Donation-03 Exercise Solutions

A total of 5 exercises were set at the end of the earlier lab - Donation-03.

Here we present sample solutions to exercises 2 and 5. The other exercises require experimentation and studying the official Android documentation, links to which have been provided.

Exercise 2 Solution

[2] Add an activity named Report to the application android-linear-layout available here:

https://github.com/wit-ictskills-2016/android-linear-layout.git
  • Provide menu access, following the same pattern as existing app. This is a worthwhile exercise as it will give you experience in composing an activity, menu layout, adding string resources and configuring the manifest file.

  • As layout content add the solution in step 01 of Donation 03. This will result in the display of a single list entry in the activity.

Solution

We begin with a layout. In this we provide two arbitrary TextView widgets in a file res/layout/activity_report.xml

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

  <TextView
      android:id="@+id/row_amount"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignParentLeft="true"
      android:layout_alignParentTop="true"
      android:layout_marginLeft="48dp"
      android:layout_marginTop="20dp"
      android:text="@string/defaultAmount" />

  <TextView
      android:id="@+id/row_method"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignBaseline="@+id/row_amount"
      android:layout_alignBottom="@+id/row_amount"
      android:layout_alignParentRight="true"
      android:layout_marginRight="79dp"
      android:text="@string/defaultMethod" />

</RelativeLayout>

Then add a menu item to res/menu/menu_global.xml.

  <item
      android:id="@+id/report"
      android:orderInCategory="100"
      android:title="@string/report"/>

In res/values/strings.xml provide the string resources referenced in the above 2 xml code blocks.

  <string name="report">Report</string>
  <string name="defaultAmount">00</string>
  <string name="defaultMethod">N/A</string>

Add an activity named Report to the folder java/app/layouts.

package app.layouts;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;

public class Report extends AppCompatActivity
{

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

  @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_global, menu);
    return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
      case R.id.donate:
        startActivity(new Intent(this, Donate.class));
        break;
      case R.id.accept:
        startActivity(new Intent(this, Accept.class));
        break;
      case R.id.message:
        startActivity(new Intent(this, Message.class));
        break;
      case R.id.donation:
        startActivity(new Intent(this, Donation.class));
        break;
    }
    return true;
  }

}

Introduce a new element representing the Report activity in the manifest file:

    <activity
        android:name="app.layouts.Report"
        android:label="@string/report">
    </activity>

Finally, add a Report menu handler in the previously existing activities, namely Accept, Donate, Donation, Message, Reject as follows:

      case R.id.report:
        startActivity(new Intent(this, Report.class));
        break;

Exercise 5 Solution (Anonymous class)

[5] Setting listeners was introduced in the slide deck A First Android Application on slides numbers 27 and 28. Three different styles were mentioned. We have already seen how setting a listener explicitly in a resource file is implemented in the donation app. This is illustrated in the code extract here taken from the file res/layout/activity_donate.xml (donateButtonPressed).

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

Create two branches in your donation app, one named listener_1, the other listener_2.

  • Checkout out listener_1 branch.
  • Implement the listener for donationButtonPressed using an anonymous class.
  • Test, add and commit to your repo and push the new branch to your remote.
  • Now check out listener_2 branch.
  • Implement the listener using the listener interface.
  • Test, add and commit to your repo and push the new branch to your remote.

Solution (Anonymous class)

Delete this line from the Button xml definition:

      android:onClick="donateButtonPressed"

Make the following changes to app/donation/activity/Donate.java:

  • In onCreate register a listener on Button donateButton. Begin with this skeleton code:
    donateButton.setOnClickListener(new View.OnClickListener() {

    });

This will generate an error that is resolved by implementing the sole View.OnClickListener method, onClick.

Figure 1: Error generated requiring interface method implementation to resolve

Figure 2: View.OnClickListener sole method onClick requires implementation

    donateButton.setOnClickListener(new View.OnClickListener() {


      @Override
      public void onClick(View v) {

      }
    });

Note the parameter type of onClick is View. A Button type is a View as may be seen in Figure 3, an extract from the official Android documentation.

The method setOnClickListener belongs to the View class and since a Button is a view, invoking the method on a Button object is legal.

See documentation: Button and View.OnClickListener.

See the View class for documentation on setOnClickListener, in particular noting the required parameter type.

Figure 3: A Button is a View

The usual method of completing this would be to introduce the handler code in the onClick method. This would result in the following where we are using the body of the existing method donateButtonPressed.

    donateButton.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        String method = paymentMethod.getCheckedRadioButtonId() == R.id.PayPal ? "PayPal" : "Direct";
        int donatedAmount = amountPicker.getValue();
        if (donatedAmount == 0) {
          String text = amountText.getText().toString();
          if (!text.equals("")) {
            donatedAmount = Integer.parseInt(text);
          }
        }
        if (donatedAmount > 0) {
          app.newDonation(new Donation(donatedAmount, method));
          progressBar.setProgress(app.totalDonated);
          String totalDonatedStr = "$" + app.totalDonated;
          amountTotal.setText(totalDonatedStr);
        }

        amountText.setText("");
        amountPicker.setValue(0);
      }
    });

Test this by registering a user, signing in and making a donation. Use the debugger, placing a break point within onClick(View v) and observing that the execution path routes correctly. Check the Report screen to verify the donation is displayed.

We can remove much of this ugliness by actually invoking the existing donateButtonPressed method. The listener registration then becomes:

    donateButton.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        donateButtonPressed(v);
      }
    });

This is a considerable improvement. But if, as is typically the situation, there are several View event handlers within the activity then the onClick method is repeated for each handler. We show how to avoid this code repetition in the next step by using the interface approach (also known as the delegated method or pattern) to setting listeners.

Exercise 5 Solution (Interface)

Change the class Donate header so that it implements the View.OnClickListener interface.


public class Donate extends AppCompatActivity implements View.OnClickListener {
  ...
  ...
}

The error is resolved by implementing View.OnClickListener method as shown in Figures 1 and 2 here.

Figure 1: Error generated requiring interface method implementation to resolve

Figure 2: View.OnClickListener sole method onClick requires implementation

In onCreate register the event handler:

    donateButton.setOnClickListener(this);

Recall that the parameter type of setOnClickListener is View.OnClickListener. Because Donate now implements View.OnClickListener, this is a valid parameter.

Complete the implementation of onClickView:


  @Override
  public void onClick(View v) {
    switch (v.getId()) {
      case R.id.donateButton:
        donateButtonPressed(v);
    }
  }

This onClick implementation can now handle all View events. All that is required is to add the appropriate case block.

Test this by registering a user, signing in and making a donation. Use the debugger, placing a break point within onClick(View v) and observing that the execution path routes correctly. Check the Report screen to verify the donation is displayed.