Objectives

Introduce a second screen to display the list of donations made by the user. In addition, enable our app its first "Model" objects, which will be used to store the individual donations made by the user.

Donation-01 Solutions

In this lab we shall continue work on the NUC devices.

You are recommend to tag each step.

Continue building on your previous app. Alternatively use this version, but if you do then it will be necessary to change the remote tracking repo (see Appendix).

donation-android-2017

Launch Android Studio and open this project.

If the project is not present in the Recent Projects panel, open the project as shown in Figure 1:

Figure 1: Open and existing Android Studio project

You may be presented with a message as shown in Figure 2 indicating an unregistered version control system (VCS).

  • Click Add root.

Figure 2: Configure git VCS

Once you have opened the project in Android Studio you are ready to proceed with the lab, the first part of which is to provide solutions to the exercises at the end of Donation-01.

Package Name

This version is a little misconfigured - we have a leftover - the package name 'com.example.donation' from the project generation wizard.
This can be changed as follows:

  • With the project imported to Android Studio, select Project in the Project Structure pane.
  • Expand the folders in app/java to reveal com.example.donation packages (java classes and the tests). Figure 1: Project Structure pane

  • Create a new package app.donation in androidTest/java.

    • Drag the file ExampleInstrumentedTest to this new package.
  • Create a new package app.donation in test/java.
    • Drag the file ExampleUnitTest to this new package.
  • Create a new package app.donation in main/java.

    • Drag file Donate to this new package.
  • The three original packages, com.example.donation should be deleted automatically without user intervention. Figure 2: Create new packages and populate by drag and drop

  • Ensure that the package name has been changed to app.donation in AndroidManifest.xml: Figure 3: Updated AndroidManifest.xml

  • Change the applicationID to app.donation in build.gradle (Module: app).

    • Sync Now if prompted. Figure 4: Update build.gradle
  • Check that the import statement in Donate.java is as follows instead of "com.example.donation.R":

import app.donation.R;
  • Clean the project: Figure 5: Clean project

  • Test by rebuilding (Build | Rebuild) and running (Run | Run 'app').

Menu

In lab01, we choose an Empty Activity as the starting point for our activity_donate layout. No menu was associated with it, which means that no menu resource directory was created in the res folder. In this step, we will first create this menu resource directory and then add a menu resource to it (and our activity_donate layout).

Creating the menu resource directory

Right click on the res directory and select (New | Android Resource Directory).

When the dialog window appears, choose menu as the resource type. Accept the defaults by clicking the OK button.

Creating a menu layout

Right click on your new menu directory and select (New | Menu Resource File).

When the dialog window appears, enter menu_donate as the file name and click the OK button.

Adding items to the menu

Open res/values/Strings.xml and introduce two new String resources like this:

    <string name="menuSettings">Settings</string>
    <string name="menuReport">Report</string>

Open 'menu_donate.xml' in the res/menu folder. Modify this file by adding these two menu items:

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

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

(Make sure it is within the <menu> element)

Return to design view for menu_donate.xml and it should resemble this:

Figure 1 - Menu_Donate

In Donate.java, add a new method called onOptionsItemSelected and include the following code:

    @Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
        switch (item.getItemId())
        {
            case R.id.menuReport:
                Toast.makeText(this, "Report Selected", Toast.LENGTH_SHORT).show();
                break;
            case R.id.menuSettings:
                Toast.makeText(this, "Settings Selected", Toast.LENGTH_SHORT).show();
                break;
        }
        return true;
    }

An import statement is required for MenuItem:

import android.view.MenuItem;

If you ran this code now, you would not see your menu. We need to "inflate" the menu in order to see it. To do this, add the following code to Donate.java, along with the necessary import statements:

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu items for use in the action bar
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.menu_donate, menu);
        return super.onCreateOptionsMenu(menu);
    }

Run the app and when you press the menu button (or the overflow menu) and select 'Report', you should see the toast message appear.

Likewise, when you select 'Settings', you should see a toast message indicating that you selected Settings.

Reports Activity

Before we start to design a new activity, Add a string resource in res/values/strings.xml:

<string name="reportTitle">Report</string>

Design a new layout called activity_report. Do this by locating the res/layout folder and selecting new->layout resource file:

Call the layout file activity_report.

We would like the resource to look like this:

Figure 1: Report activity design view

For the moment, to achieve this - just replace the xml with the following:

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

    <TextView
        android:id="@+id/reportTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentEnd="true"
        android:layout_alignParentTop="true"
        android:text="@string/reportTitle"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <ListView
        android:id="@+id/reportList"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignStart="@+id/reportTitle"
        android:layout_below="@+id/reportTitle" >

    </ListView>

</RelativeLayout>

You should see the design render as above.

We can now introduce a new Class into app.donation to render this activity:

package app.donation;

import app.donation.R;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class Report extends AppCompatActivity
{
  ListView listView;

  static final String[] numbers = new String[] {
      "Amount, Pay method",
      "10,     Direct",
      "100,    PayPal",
      "1000,   Direct",
      "10,     PayPal",
      "5000,   PayPal"};

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

    listView = (ListView) findViewById(R.id.reportList);
    ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,  android.R.layout.simple_list_item_1, numbers);
    listView.setAdapter(adapter);
  }
}

This will display a hard-coded lists of donations.

Back in Donation class, change the Donate activity to load this view when 'Report' selected from menu:

  @Override
  public boolean onOptionsItemSelected(MenuItem item)
  {
    switch (item.getItemId())
    {
      case R.id.menuReport : startActivity (new Intent(this, Report.class));
                             break;
    }
    return true;
  }

This requires an import statement:

import android.content.Intent;

All of this will not work until you add the activity specification to the AndroidManifest.xml file:

        <activity
            android:name="app.donation.Report"
            android:label="@string/donateTitle" >
        </activity>

The above must be carefuly positioned - this is the full AndroidManifest.xml file here:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="app.donation">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name="app.donation.Donate">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name="app.donation.Report"
            android:label="@string/donateTitle">
        </activity>

    </application>

</manifest>

Note the location of the new element.

Try it all now - it should load. Here is an example using the inbuilt emulator (AVD).

Figure 2: Report rendered on Android AVD

Application Object

In order to keep our application design coherent, we now bring in an 'Application' object.

Create a new class called `DonationApp':

Incorporate this class here:

package app.donation;

import android.app.Application;
import android.util.Log;

public class DonationApp extends Application
{
  @Override
  public void onCreate()
  {
    super.onCreate();
    Log.v("Donate", "Donation App Started");
  }
}

Application objects need to be referenced in the AndroidManifest.xml

android:name="app.donation.DonationApp"

It is inserted into the application element - here is the full manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="app.donation">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        android:name="app.donation.DonationApp">

        <activity android:name="app.donation.Donate">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name="app.donation.Report"
            android:label="@string/donateTitle">
        </activity>

    </application>

</manifest>

Make sure the 'Donation App Started' appears in the logs when the application is started:

The filter used here is:

Donation Model

Create a new class called Donation:

package app.donation;

public class Donation
{
  public int    amount;
  public String method;

  public Donation (int amount, String method)
  {
    this.amount = amount;
    this.method = method;
  }
}

This is a revised version of DonationApp class:

package app.donation;

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

import android.app.Application;
import android.util.Log;
import android.widget.Toast;

public class DonationApp extends Application
{
  public final int       target       = 10000;
  public int             totalDonated = 0;
  public List <Donation> donations    = new ArrayList<Donation>();

  public boolean newDonation(Donation donation)
  {
    boolean targetAchieved = totalDonated > target;
    if (!targetAchieved)
    {
      donations.add(donation);
      totalDonated += donation.amount;
    }
    else
    {
      Toast toast = Toast.makeText(this, "Target Exceeded!", Toast.LENGTH_SHORT);
      toast.show();
    }
    return targetAchieved;
  }

  @Override
  public void onCreate()
  {
    super.onCreate();
    Log.v("Donate", "Donation App Started");
  }
}

It manages a list of donations. It also centralises the 'makeDonation' as as a method.

Refactored Donate

The Donate activity can now be completely re factored to make use of the DonationApp object.

First, introduce this new member into the class:

  private DonationApp app;

and in the onCreate meethod, initialize app:

    app = (DonationApp) getApplication();

Finally, we can replace the donatButtonPressed method with this simplified version:


  public void donateButtonPressed (View view)
  {
    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);
    }
  }

Here is the complete Donate class if there is a problem:

package app.donation;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.NumberPicker;
import android.widget.ProgressBar;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
import app.donation.R;

public class Donate extends AppCompatActivity {

    private int          target = 10000;
    private RadioGroup   paymentMethod;
    private ProgressBar  progressBar;
    private NumberPicker amountPicker;
    private EditText     amountText;
    private TextView     amountTotal;
    private DonationApp  app;

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

        app           = (DonationApp)  getApplication();
        paymentMethod = (RadioGroup)   findViewById(R.id.paymentMethod);
        progressBar   = (ProgressBar)  findViewById(R.id.progressBar);
        amountPicker  = (NumberPicker) findViewById(R.id.amountPicker);
        amountTotal   = (TextView)     findViewById(R.id.amountTotal);
        amountText    = (EditText)     findViewById(R.id.amountText);

        amountPicker.setMinValue(0);
        amountPicker.setMaxValue(1000);
        progressBar.setMax(target);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
        switch (item.getItemId())
        {
            case R.id.menuReport:
                startActivity(new Intent(this, Report.class));
                break;
            case R.id.menuSettings:
                Toast.makeText(this, "Settings Selected", Toast.LENGTH_SHORT).show();
                break;
        }
        return true;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu items for use in the action bar
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.menu_donate, menu);
        return super.onCreateOptionsMenu(menu);
    }

    public void donateButtonPressed (View view) {
        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);
        }
    }

}

Refactored Report

We now rework Report to render the actual donations - held in the DonationApp list.

First some layout additions. Include these new string in strings.xml

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

In the layout folder, create a new layout called row_layout.xml - replace the generated contents with the following:

<?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_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:layout_marginStart="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_marginStart="106dp"
        android:layout_toEndOf="@+id/row_amount"
        android:text="@string/defaultMethod" />

</RelativeLayout>

The designer should look like this:

Finally, rework Report class to remove the hard coded values - and use a different 'adapter'

This is the complete class here:

package app.donation;

import app.donation.R;

import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;

import java.util.List;

public class Report extends AppCompatActivity
{
  private ListView    listView;
  private DonationApp app;

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

    app = (DonationApp) getApplication();

    listView = (ListView) findViewById(R.id.reportList);
    DonationAdapter adapter = new DonationAdapter (this, app.donations);
    listView.setAdapter(adapter);
  }
}

class DonationAdapter extends ArrayAdapter<Donation>
{
  private Context context;
  public List<Donation> donations;

  public DonationAdapter(Context context, List<Donation> donations)
  {
    super(context, R.layout.row_layout, donations);
    this.context   = context;
    this.donations = donations;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent)
  {
    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    View view       = inflater.inflate(R.layout.row_layout, parent, false);
    Donation donation   = donations.get(position);
    TextView amountView = (TextView) view.findViewById(R.id.row_amount);
    TextView methodView = (TextView) view.findViewById(R.id.row_method);

    amountView.setText("" + donation.amount);
    methodView.setText(donation.method);

    return view;
  }

  @Override
  public int getCount()
  {
    return donations.size();
  }
}

We will discuss this version in depth in class.

If all goes well - then you should be able to make donations, and and the see a list of these in the report activity.

Repository

Here we shall describe how you can break the association with GitHub that you established to download the baseline donation-android-2016 and push your refactored application to a new repository that you will create on your Bitbucket account.

Access your Bitbucket account and create a repository named donation-android-2016

  • If a similarly named repo already exists on your Bitbucket account then change its name to something like donation-android-v1:
    • This may easily be done by:
      • clicking on the repo to be renamed,
      • clicking on the gear wheel located on the bottom left hand side,
      • changing the name under Repository details | Name
      • Pressing the Save repository details button.

Figure 1

On the NUC cd into donation-android-2016 and run the following commands in sequence.

git remote rm origin

This last command removes the association with GitHub.

Now bring your local repo up to date, associate it with the newly created repo on Bitbucket and push all:


git add .

git commit -m "Donation-02 lab complete"

Before running this next command it will be necessary to replace the placeholders jdoe@wit.ie and jdoe with the email you registered on Bitbucket and your login id:

git remote add origin https://jdoe@wit.org/jdoe/donation-android-2016.git <------- See Figure 2

Finally, push your repo to Bitbucket:


git push -u origin --all  

git push -u origin --tags

You will be asked for your Bitbucket password.

Use the clone statement to complete the expression git remote add origin above. See Figure 2.

Figure 2

On your PC cd to where you locate your repos and run the SSH clone command. You have already have installed ssh on your pc during the summer school.

  • This will pull the latest commit of donation-android-2016.

Exercises (DonationV3.0)

Project so far:

Select Releases, followed by V2.0. In this step, you will be building V3.0 of Donation.

Exercise 1

Run the app and insert amounts of varying lengths (1, 222, 23, 2323). Note that the second column - payment method - may be displayed at different positions. If this happens, fix it.

Hint: each row is laid out by a row_donate.xml layout. The easiest way to fix this would be to experiment with the layout, and have the text fields aligned with the edges and not with each other.

Exercise 2

When a donation is accepted, set the amount on screen to 0 (in both picker and text field).

Exercise 3

When you navigate from the Donate activity to reports, there is no menu available. Bring in a menu, with two options 'Settings' and 'Donate' - Donate should bring you back to the donate screen. Settings should display a Toast message indicating that Settings was selected.

Exercise 4

Introduce a new welcome screen - which should display a greeting + give the user 2 options (as simple buttons)

  • Signup
  • Login

When Login or Signup is pressed, the app should take you directly to the Donate activity (for the moment).

Exercise 5

Introduce a Signup Activity (use the Empty Activity template), which should present the user with:

  • First Name
  • Last Name
  • Email
  • Password

  • a 'Register' button.

Pressing Register should take you directly to "Donate" activity. Also, refactor the Welcome screen such that the 'signup' button takes you to this screen.

Exercise 6:

Introduce a Login activty, which should just look for

  • email
  • password

  • a 'Login' button

Pressing Login should take you directly to "Donate" activity.

Exercise 7

Bring in a new menu option - 'logout'. It should take you to the welcome screen, and should be available from the donate and report activities.

Exercise 8

Introduce a 'User' into the models package to represent the user in the usual way. Maintain a list of Users in the DonationApp object. Whenever anyone registers, then create a new User object in this list.

Exercise 9

Implement the Login activity, to now only let users in to Donate if they are registered (i.e. a matching email + password in the list of users maintained by DonationApp)

Exercise 10

If you have any "RelativeLayout" layouts, convert them to "ConstraintLayout". There is a conversion function in Android Studio to do this for you.

Useful commands

Linux

You create a folder with the mkdir command:

mkdir <new folder>

For example:

mkdir backup-projects

You may remove a folder and all its contents with this (dangerous) command:

rm -rf <folder name>

You may rename a file or folder with the mv command. For example to change the name of an existing file-a to file-b:

mv file-a file-b

Git

If you wish take control of an application downloaded from a third party repo then it will be necessary to change the origin as follows:

  • cd into the project in a terminal
  • run this command:
git remote rm origin

Add an alternative, for example, as follows where https is the method of transfer:

git remote add origin https://<your domain>@bitbucket.org/<your domain>/<app name>

gitp is our customized command to pretty-print the logs of the most recent 10 commits.

alias gitp="git log --pretty=format:'%C(yellow)%h %<(24)%C(red)%ad  %Creset%s' --date=local --max-count=10"

This command outputs all the tags and their associated messages:

git tag -n

Figure 1: Print commit logs and tags and their messages

Here are the commands to configure your git installation:

git user.name  = "your name here"  <------ use double quotes
git user.email = "your email here" <------ use double quotes
// Check
git config [--global] user.name   
git config [--global] user.email

You are not alone if you have difficulty understanding man pages. Here's a simple guide (that comes without a guarantee):

Beginners Guide to man Pages