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.
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).
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:
You may be presented with a message as shown in Figure 2 indicating an unregistered version control system (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.
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:
Expand the folders in app/java to reveal com.example.donation packages (java classes and the tests).
Create a new package app.donation in androidTest/java.
Create a new package app.donation in main/java.
The three original packages, com.example.donation should be deleted automatically without user intervention.
Ensure that the package name has been changed to app.donation in AndroidManifest.xml:
Change the applicationID to app.donation in build.gradle (Module: app).
Check that the import statement in Donate.java is as follows instead of "com.example.donation.R":
import app.donation.R;
Clean the project:
Test by rebuilding (Build | Rebuild) and running (Run | Run 'app').
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).
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.
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.
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:
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.
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:
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).
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:
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.
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);
}
}
}
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.
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
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.
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.
Project so far:
Select Releases, followed by V2.0. In this step, you will be building V3.0 of Donation.
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.
When a donation is accepted, set the amount on screen to 0 (in both picker and text field).
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.
Introduce a new welcome screen - which should display a greeting + give the user 2 options (as simple buttons)
When Login or Signup is pressed, the app should take you directly to the Donate activity (for the moment).
Introduce a Signup Activity (use the Empty Activity template), which should present the user with:
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.
Introduce a Login activty, which should just look for
password
a 'Login' button
Pressing Login should take you directly to "Donate" activity.
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.
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.
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)
If you have any "RelativeLayout" layouts, convert them to "ConstraintLayout". There is a conversion function in Android Studio to do this for you.
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
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:
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
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):