Objectives

We continue the implementation of Donation to including the welcome, sign up and login. Support these views with a User model, and validate the users on log in. Continue to build on your Donation repository.

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 they layout, and have the text fields aligned with the edges and not with each other.

Solution

This is a revised version of the row_layout.xml file:

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

Exercise 2

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

Solution

Add these two lines at the end of Donate.donateButtonPressed()

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

Exercise 3

When you navigate from the Donate activity to reports, there will be no menu available. Bring in a menu, with two options 'Settings' and 'Donate' - Donate should bring you back to the donate screen.

Solution

Introduce a new string constant into strings.xml:

    <string name="menuDonate">Donate</string>

Then a new menu - menu_report.xml - in the correct res folder:

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

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

</menu>

The report activity will then require the following two methods.

  • Warning: When introducing these methods into Report.java ensure you locate them within the Report class, not within the DonationAdapter class that you may have chosen to locate in the same compilation unit (the file Report.java).
  @Override
  public boolean onCreateOptionsMenu(Menu menu)
  {
    getMenuInflater().inflate(R.menu.menu_report, menu);
    return true;
  }

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

These imports are required:

import android.content.Intent;
import android.view.MenuItem;
import android.view.Menu;

If you experiment and subclass Activity rather than AppCompatActivity, you will note that the menu may not appear. The AppCompat library implements a 'backport' of the menu bar to older editions.

Exercise 4: Welcome

Continue from the end of Donation 2.

donation-android-2016-2

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

  • Signup
  • Login

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

This is the new layout:

Filename: activity_welcome.xml

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

    <Button
        android:id="@+id/welcomeLogin"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="102dp"
        android:text="@string/welcomeLogin" />

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

</RelativeLayout>

It requires these two strings in strings.xml:

    <string name="welcomeLogin">Login</string>
    <string name="welcomeSignup">Sign up</string>

Here is a minimal version of the activity:

package app.donation;

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

public class Welcome extends AppCompatActivity
{
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_welcome);
  }
}

(We continue to use AppCompatActivity)

We would like this to be the first activity to appear when the app is launched. This is configured in AndroidManifest.xml.

  • Here is the existing manifest file:
<?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:theme="@style/AppTheme"
        android:name="app.donation.DonationApp">
        <activity
            android:name="app.donation.Donate"
            android:label="@string/app_name" >
            <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>

In the above, you can see that "Donate" is the MAIN activity, and we have no entry for Welcome yet. Here is a revision to do what we want:

<?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:theme="@style/AppTheme"
        android:name="app.donation.DonationApp">
        <activity
            android:name="app.donation.Welcome"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="app.donation.Donate"
            android:label="@string/app_name" >
        </activity>
        <activity
            android:name="app.donation.Report"
            android:label="@string/donateTitle" >
        </activity>
    </application>

</manifest>

We have three activities - with Welcome denoted as the Main via these attributes:

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

Notice that we have 'donateTitle' as the Label for the Report activity. It is currently set to:

            android:label="@string/donateTitle" >

Perhaps we should change this now to:

            android:label="@string/app_name" >

It may make sense to have a different label for each activity as you evolve the app.

Finally, we need an event handler for the Login button.

  • Edit activity_welcome and introduce a new field for the login button
    android:onClick="loginPressed"
    <Button
        android:id="@+id/welcomeLogin"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="102dp"
        android:onClick="loginPressed"
        android:text="@string/welcomeLogin" />

You will notice that the editor may recognize that you do not have any method called loginPressed in the Welcome class yet.

Bring in the method into Welcome activity now:

  public void loginPressed (View view) 
  {
    Toast toast = Toast.makeText(this, "Login Pressed!", Toast.LENGTH_SHORT);
    toast.show();
  }

These import statements are necessary:

import android.view.View;
import android.widget.Toast;

Test all of this now and make sure the toast message appears.

Finally, replace the loginPressed method implementation with the code to actually start the donate activity:

   startActivity (new Intent(this, Donate.class));

Import the Intent class and delete the import for Toast which is no longer needed here:

import android.content.Intent;

Launch the app again, press Login: the Donation view should open.

This completes exercise 4.

Exercise 5 - Part 1 Signup Layout

Introduce a Signup Activity, 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 'Sign up' button takes you to this, the sign up, screen.

Solution

In the Exercise 4 we created and activity by separately creating layout and a class, and building each from scratch. This time we will use the create activity wizard. In doing so, keep a close eye the files the wizard generates or modifies.

Select the app.activities package, and select "File->New->Other" ... and follow the following entries:

Figure 1: Select activities folder & from context menu activate activity creation wizard Figure 2: Customize activity (note we change Menu Resource Name) Figure 3: Combined Design-Text view of new file activity_signup.xml Figure 4: Text view of new file activity_signup.xml Figure 5: Default Design view

Inspect each of the files altered or added to by the wizard:

  • AndroidManifest.xml
  • strings.xml
  • activity_signup.xml
  • menu_signip.xml
  • Signup.java

Open the layout designer, and design a screen (activity_signup.xml) to look like this:

Figure 7: Signup screen

In particular, that we do not have a 'label' for first name (like we would have in a web app), but rather set a 'Hint'. This can be set in the Properties window as shown in Figure 8.

Figure 8: Using a hint or placeholder instead of a label

When placing the text field on the canvas, select "Person Name" from the 'Text Widgets' palette

This is the layout so far:

<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/signupTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="32dp"
        android:layout_marginTop="28dp"
        android:text="@string/signupTitle"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <TextView
        android:id="@+id/signupSubtitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/signupTitle"
        android:layout_below="@+id/signupTitle"
        android:layout_marginLeft="55dp"
        android:layout_marginTop="30dp"
        android:text="@string/signupSubtitle"
        android:textAppearance="?android:attr/textAppearanceSmall" />

    <EditText
        android:id="@+id/firstName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_below="@+id/signupSubtitle"
        android:layout_marginTop="40dp"
        android:ems="10"
        android:hint="@string/signupFirstname"
        android:inputType="textPersonName" />

</RelativeLayout>

and strings.xml has these supporting definitions:

    <string name="title_activity_signup">Signup</string>
    <string name="signupTitle">Sign up for the Donation App</string>
    <string name="signupSubtitle">Enter details below</string>
    <string name="signupFirstname">First name</string>

Observe that we have removed the wizard-generated 'hello_world' resource from strings.xml.

Complete the layout now, select "Person Name", "Email" and "Password" from the text widgets pallette:

Figure 9d: Completing Signup screen

The email and password ids are exceptionally "Email" and "Password" (leading upper case letter). This is to avoid conflict with built in ids.

Set the 'Hints' as you go, and fix up the strings as we have been doing. This is the final version of the layout:

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

    <TextView
        android:id="@+id/signupTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="32dp"
        android:layout_marginTop="28dp"
        android:text="@string/signupTitle"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <TextView
        android:id="@+id/signupSubtitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/signupTitle"
        android:layout_below="@+id/signupTitle"
        android:layout_marginLeft="55dp"
        android:layout_marginTop="30dp"
        android:text="@string/signupSubtitle"
        android:textAppearance="?android:attr/textAppearanceSmall" />

    <EditText
        android:id="@+id/firstName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_below="@+id/signupSubtitle"
        android:layout_marginTop="40dp"
        android:ems="10"
        android:hint="@string/signupFirstname"
        android:inputType="textPersonName"/>

        <requestFocus />

    <EditText
        android:id="@+id/lastName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/firstName"
        android:layout_alignParentRight="true"
        android:layout_below="@+id/firstName"
        android:ems="10"
        android:hint="@string/signupLastName"
        android:inputType="textPersonName" >

    </EditText>

    <EditText
        android:id="@+id/Email"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/lastName"
        android:layout_alignParentRight="true"
        android:layout_below="@+id/lastName"
        android:ems="10"
        android:hint="@string/signupEmail"
        android:inputType="textEmailAddress" />

    <EditText
        android:id="@+id/Password"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/Email"
        android:layout_alignParentRight="true"
        android:layout_below="@+id/Email"
        android:ems="10"
        android:hint="@string/signupPassword"
        android:inputType="textPassword" />

    <Button
        android:id="@+id/signup"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="28dp"
        android:text="@string/signupRegister" />

</RelativeLayout>

and the supporting Strings:

  • Note: do not duplicate string resources.
    ...
    <string name="signupTitle">Sign up for the Donation App</string>
    <string name="signupSubtitle">Enter details below</string>
    <string name="signupFirstname">First name</string>
    <string name="signupLastName">Last Name</string>
    <string name="signupEmail">Email</string>
    <string name="signupPassword">Password</string>
    <string name="signupRegister">Register</string>

Exercise 5 - Part 2 Signup Activity Class

(continued)

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

Solution

The previous step generated this Activity class:

package app.activities;

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

import app.donation.R;

public class Signup extends AppCompatActivity

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

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.signup, 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);
    }
}

We are not going to support a menu in this class - so we will delete the onCreateOptionsMenu and onOptionsItemSelected methods now.

Similarly, we can delete the generate menu - menu_signup.xml - in the menu folder.

Now we can set our event handler in the activity_signup.xml layout:

    <Button
        android:id="@+id/signup"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="28dp"
        android:onClick="signupPressed"
        android:text="@string/signupRegister" />

and implement the event handler:

  public void signupPressed (View view) 
  {
    startActivity (new Intent(this, Donate.class));
  }

We need to wire up the 'Signup' button from the welcome screen to take us to the signup activity.

Open the welcome layout, and introduce the click handler:

    <Button
        android:id="@+id/welcomeSignup"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:onClick="signupPressed"
        android:text="@string/welcomeSignup" />

Then, in Welcome activity - implement the handler:

  public void signupPressed (View view) 
  {
    startActivity (new Intent(this, Signup.class));
  }

Add an activity node to the manifest file:

    <activity
        android:name="app.donation.Signup"
        android:label="@string/app_name" >
    </activity>

Run the app now, and verify that the navigation operates as expected. Also, try the 'back' button, and the menu button. Try various combination of these to see how it operates.

Exercise 6: : Login

Introduce a Login activty, which should just look for

  • email
  • password

  • a 'Login' button

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

Solution

Create new activity using the 'Wizard' - called "Login". Do not select "Login" form the wizard, keep the activity 'Blank'. Ensure you select java/app/donation in the Android Tools Window before activating the wizard to ensure the new class is correctly located:

Figure 1: Correctly locate new Login class

Also, ensure that Login is correctly referenced in the manifest file as follows:

        <activity
            android:name=".Login"
            android:label="@string/title_activity_login" >
        </activity>

Edit the layout, and design a screen to look like that shown in Figure 3 below.

Figure 3: Login screen

This is the layout that may be generated:

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

    <TextView
        android:id="@+id/loginTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:layout_marginTop="18dp"
        android:text="@string/loginTitle"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <TextView
        android:id="@+id/loginSubtitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/loginTitle"
        android:layout_alignParentRight="true"
        android:layout_below="@+id/loginTitle"
        android:text="@string/loginSubtitle"
        android:textAppearance="?android:attr/textAppearanceSmall" />

    <EditText
        android:id="@+id/loginEmail"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/loginSubtitle"
        android:layout_alignRight="@+id/loginSubtitle"
        android:layout_below="@+id/loginSubtitle"
        android:layout_marginTop="17dp"
        android:ems="10"
        android:hint="@string/loginEmail"
        android:inputType="textEmailAddress" >

        <requestFocus />
    </EditText>

    <EditText
        android:id="@+id/loginPassword"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/loginEmail"
        android:layout_alignRight="@+id/loginEmail"
        android:layout_below="@+id/loginEmail"
        android:ems="10"
        android:hint="@string/loginPassword"
        android:inputType="textPassword" />

    <Button
        android:id="@+id/login"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="@string/loginSignin" />

</RelativeLayout>

Add the supporting strings, removing Hello World default string:

    <string name="title_activity_login">Login</string>
    <string name="loginTitle">Login to Donation</string>
    <string name="loginSubtitle">You must be registered</string>
    <string name="loginSignin">Sign in</string>
    <string name="loginEmail">Email</string>
    <string name="loginPassword">Password</string>

There is how we would like the class to be implemented:

package app.donation;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import app.donation.R;

public class Login extends AppCompatActivity
{
  @Override
  protected void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_login);
  }

  public void signinPressed (View view)
  {
    startActivity (new Intent(this, Donate.class));
  }
}

Locate the button resource in the activity_login.xml resource:

    <Button
        android:id="@+id/login"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:onClick="signinPressed"
        android:text="@string/loginSignin" />

.. and introduce the onClick handler (shown above).

Now, redirect the welcome screen to take the user to this screen when 'Login' is pressed. Open Welcome.java:

  public void loginPressed (View view) 
  {
    startActivity (new Intent(this, Login.class));
  }

And redirect the Signup screen to the Welcome screen once the Register button is pressed:

  public void signupPressed (View view)
  {
    startActivity (new Intent(this, Welcome.class));
  }

The navigation is a little tricky to get right - so verify that you can launch and follow from one screen to the next.

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.

Solution

Introduce a new string resource for the menu item in strings.xml:

    <string name="menuLogout">Logout</string>

In both menu_donate.xml and menu_report.xml (the menus), bring in a new option:

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

Run the app now, and verify that these options appear.

Then we need to implement the control flow. In the donate activity class:

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

and Report:

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

Test this now.

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.

Solution

Bring in this new class the project

package app.donation;

public class User 
{
  public String firstName;
  public String lastName;
  public String email;
  public String password;

  public User(String firstName, String lastName, String email, String password)
  {
    this.firstName = firstName;
    this.lastName = lastName;
    this.email = email;
    this.password = password;
  } 
}

In DonationApp class, incorporate a new collection of Users as a member of the class:

  public List <User> users = new ArrayList<User>();

.. and a new method to add a user to this collection:

  public void newUser(User user)
  {
    users.add(user);
  }

Now in the Signup activity - we can retrieve the fields from the widgets, create a new User object and store it in the DonationApp object:

  public void signupPressed (View view) 
  {
    TextView firstName = (TextView)  findViewById(R.id.firstName);
    TextView lastName  = (TextView)  findViewById(R.id.lastName);
    TextView email     = (TextView)  findViewById(R.id.Email);
    TextView password  = (TextView)  findViewById(R.id.Password);

    User user = new User (firstName.getText().toString(), lastName.getText().toString(), email.getText().toString(), password.getText().toString());

    DonationApp app = (DonationApp) getApplication();
    app.newUser(user);

    startActivity (new Intent(this, Welcome.class));
  }
}

Test this now.

Exercise 9

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

Solution

Here is a new method for DonationApp that will validate users :

  public boolean validUser (String email, String password)
  {
    for (User user : users)
    {
      if (user.email.equals(email) && user.password.equals(password))
      {
        return true;
      }
    }
    return false;
  }

Then we can check in Login activity that the entered details match a user:

  public void signinPressed (View view) 
  {
    DonationApp app = (DonationApp) getApplication();

    TextView email     = (TextView)  findViewById(R.id.loginEmail);
    TextView password  = (TextView)  findViewById(R.id.loginPassword);

    if (app.validUser(email.getText().toString(), password.getText().toString()))
    {
      startActivity (new Intent(this, Donate.class));
    }
    else
    {
      Toast toast = Toast.makeText(this, "Invalid Credentials", Toast.LENGTH_SHORT);
      toast.show();
    }
  }

Run this now - and you should be able to only log in if the user has been registered correctly.

Refactor Package Structure

Currently our project package structure looks like this:

This is a reasonable number of classes in the app.donation package, but we might consider it a bit cluttered.

Here is a refactoring into three packages - this was achieved by:

1. Selecting activity and creating three new packages inside it as shown here:

2. Drag and drop the classes into the appropriate packages

Proceed with caution!

If drag and drop seems to be unavailable - exit and restart Android Studio, as the refactoring options may be temporarily disabled.

Drag/drop/refactor will make sure all the imports are correctly inserted - including changing AndroidManifest.xml, however you may need to allow time for the refactoring to 'catch up' (it is making significant changes) before you press on to the next one.

This represents a cleaner structure, and a better foundation should we introduce more models and activities.

Solution

Project so far:

If you have been checking in this lab as you go along, you may wish to save your work to github or bitbucket at this stage.

The simplest way of doing this is to first 'uncouple' the project from its current repo:

git remote rm origin

Note that this does not eliminate the commit history - it is all preserved intact locally in the .git folder. Now create a repo on one of the above services.

Once it is created, we associate this remote 'origin' with our local repo:

git remote add origin !!https url of repo!!

And finally, we can push the repo:

git push -u origin --all

Tags should be pushed separately:

git push --tags

You will be asked for your password for the remote service before the push will be executed.

Exercises

[1] Layouts

Clone this repository:

https://github.com/wit-ictskills-2016/android-linear-layout.git

Import into Android Studio and run.

Exercise the menu options and note the layout differences in the various screens (activities).

Experiment by making changes and observing results to the following:

  • android:gravity
  • android:layout_weight
  • android:layout_width="match_parent"
  • android:layout_height="wrap_content"

[2] Add an activity to the above repo (android-linear-layout) named Report.

  • Provide menu access, following 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.

[3] To obtain an idea of the extent and capability of TextView, briefly examine the official documentation page: TextView.

[4] Scan the official documentation for Layouts, in particular so as to obtain an overview of Linear, Relative and ListView layouts, those that we shall mostly be using in this lab series.

[5] Setting listeners was introduced in the slide deck A First Android Application on slides numbers 27 and 28. Three different styles we 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 android-linear-layout 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.