Action Bar & Dialogs

In a significant update to the app, we introduce navigation within the app using an Action Bar. This will allow us to create new residences, or navigate to existing ones. Additionally, we will explore a simple date picker dialog to allow the user to select a registration date for the residence.

Overview

At the end of this session the following features will have been added:

  • The action bar with menus as shown in Figure 1 Figure 1: Action bar and menu
  • Date picker as shown in Figure 2 Figure 2: Date picker to facilitate setting registration date

Resources

Continue building the MyRent app that you commenced in the previous lab.

We perform some surgery as follows on the incoming code base to allow the menus we are about to introduce to render correctly.

  • Replace ResidenceListActivity extends Activity, changing the base class to AppCompatActivity.

Moving on to ResidenceActivity, ensure the class header is as follows:

public class ResidenceActivity extends AppCompatActivity implements TextWatcher, CompoundButton.OnCheckedChangeListener

Remove the Activity import and replace with:

import android.support.v7.app.AppCompatActivity;

This change is required for backward compatibility - otherwise the action bar menu item (+) will not be displayed.

For reference, the build.gradle (Module:app) file is presented here. Note the appcompat-v7 library dependency.

apply plugin: 'com.android.application'

android {
  compileSdkVersion 23
  buildToolsVersion "23.0.3"

  defaultConfig {
    applicationId "org.wit.myrent"
    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'
}

We are now ready to proceed to introduce a new menu for display in ResidenceListActivity.

Figure 1: ResidenceListActivity menu

  • Add these string resources to res/values/strings.xml
  <string name="new_residence">New Residence</string>
  <string name="settings">Settings</string>

In the folder res/menu create a new file residencelist.xml.

Here is the file content:

Filename: residencelist.xml

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

  <item android:id="@+id/menu_item_new_residence"
        android:icon="@android:drawable/ic_menu_add"
        android:title="@string/new_residence"
        app:showAsAction="ifRoom|withText"/>


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

</menu>

Next, we bind this newly-created menu to the activity within which we wish the menu to appear, namely ResidenceListActivity.

  • Add this override to ResidenceListActivity:
  @Override
  public boolean onCreateOptionsMenu(Menu menu)
  {
    MenuInflater menuInflater = getMenuInflater();
    menuInflater.inflate(R.menu.residencelist, menu);
    return true;
  }

These imports are required:

import android.view.Menu;
import android.view.MenuInflater;

Observe in the above method, onCreateOptionsMenu, how the residencelist menu is accessed.

Figure 2 shows the relationship between the xml and display.

Figure 2: Action Bar & Menu

Build and run the application. You should see something like that shown in Figure 3.

Figure 3: Action Bar Menu

Helpers

Add a new class named IntentHelper to org.wit.android.helpers.

Filename: IntentHelper.java

package org.wit.android.helpers;
import java.io.Serializable;

import android.app.Activity;
import android.content.Intent;

public class IntentHelper
{
  public static void startActivity (Activity parent, Class classname)
  {
    Intent intent = new Intent(parent, classname);
    parent.startActivity(intent);
  }   

  public static void startActivityWithData (Activity parent, Class classname, String extraID, Serializable extraData)
  {
    Intent intent = new Intent(parent, classname);
    intent.putExtra(extraID, extraData);
    parent.startActivity(intent);
  }  

  public static void startActivityWithDataForResult (Activity parent, Class classname, String extraID, Serializable extraData, int idForResult)
  {
    Intent intent = new Intent(parent, classname);
    intent.putExtra(extraID, extraData);
    parent.startActivityForResult(intent, idForResult);
  }
}

Activities (ResidenceListActivity)

Here we shall provide the coding to respond to menu selection:

Import the helpers provided in the previous step:

import static org.wit.android.helpers.IntentHelper.startActivityWithData;
import static org.wit.android.helpers.IntentHelper.startActivityWithDataForResult;

This import statement is necessary:

import android.view.MenuItem;

Now override onOptionsItemSelected to respond to selecting the menu item to create a new residence instance:

  @Override
  public boolean onOptionsItemSelected(MenuItem item)
  {
    switch (item.getItemId())
    {
      case R.id.menu_item_new_residence: Residence residence = new Residence();
                                         portfolio.addResidence(residence);
                                         startActivityWithDataForResult(this, ResidenceActivity.class, "RESIDENCE_ID", residence.id, 0);
                                         return true;    

      default: return super.onOptionsItemSelected(item);
    }
  }

Add this line of code to onItemClick

startActivityWithData(this, ResidenceActivity.class, "RESIDENCE_ID", residence.id);

Delete remaining lines code:

Here is refactored method:

  @Override
  public void onItemClick(AdapterView<?> parent, View view, int position, long id)
  {
    Residence residence = adapter.getItem(position);
    startActivityWithData(this, ResidenceActivity.class, "RESIDENCE_ID", residence.id);
  }

We no longer use test data. Delete this line from Portfolio:

Delete the now redundant method, also in Portfolio: generateTestData

One small final change to org.wit.myrent.models.Residence:

  • Add a default value for geoloction in the default constructor:
    geolocation = "52.253456,-7.187162";

This will ensure the the residence list will always display some meaningful data.

Here is the modified constructor:

  public Residence()
  {
    id = unsignedLong();
    date = new Date().getTime();
    geolocation = "52.253456,-7.187162";
  }

Check that the app works as expected:

  • Launch the app and select the menu item (+)
  • Optionally, make some changes to the residence data in the detail view
  • Switch back to the list view using the back-button.

You should be presented with something similar to that shown in Figure 1.

Figure 1: Using menu to add new residence

Dialog

The dialog in Figure 1 is an instance of DatePickerDialog which is a simple dialog containing a DatePicker.

  • A DatePicker is a widget for selecting a date.

Figure 1: DatePicker widget

  • As shown in Figure 2, the DatePicker is contained in ResidenceActivity.

Figure 2: Object diagram showing DatePickerDialog

Activities (ResidenceActivity)

We shall now implement the code to allow a user to set the registration date of a residence.

In org.wit.myrent.activities.ResidenceActivity add these import statements:

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import android.app.DatePickerDialog;
import android.view.View;
import android.view.View.OnClickListener;

Delete line disabling date button:

Change the signature of ResidenceActivity class by adding

  • View.OnClickListener
  • DatePickerDialog.OnDateSetListener

to the list of implemented interfaces:

public class ResidenceActivity extends Activity implements TextWatcher, OnCheckedChangeListener, View.OnClickListener, DatePickerDialog.OnDateSetListener
{
...
...
}

This change will trigger errors. Add unimplemented methods manually or using the Android Studio hot keys to autogenerate the methods (as described in detail in previous labs such as in step 9 MyRent-00):

  • public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth)
  • public void onClick(View v)

Here is the implementation of these methods:

  @Override
  public void onClick(View view)
  {
    switch (view.getId())
    {
      case R.id.registration_date      : Calendar c = Calendar.getInstance();
                                         DatePickerDialog dpd = new DatePickerDialog (this, this, c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH));
                                         dpd.show();
                                         break; 
    }
  }
  @Override
  public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth)
  {
    Date date = new GregorianCalendar(year, monthOfYear, dayOfMonth).getTime();
    residence.date = date.getTime();
    dateButton.setText(residence.getDateString());
  }

Set the date button listener in onCreate:

dateButton  .setOnClickListener(this);

Launch the app and test that this code works as intended:

  • Create a new residence
  • Change the registration date
  • Return to the list view and create a further residence.
  • Return to the list view, click on the first residence, which opens its detail view, and observe that it retains the modified date.

Figure 1: Date picker in action

For reference, here is the completed ResidenceActivity class:

package org.wit.myrent.activities;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.DatePicker;
import android.widget.EditText;

import org.wit.myrent.R;
import org.wit.myrent.app.MyRentApp;
import org.wit.myrent.models.Portfolio;
import org.wit.myrent.models.Residence;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import android.app.DatePickerDialog;
import android.view.View;
import android.view.View.OnClickListener;


public class ResidenceActivity extends AppCompatActivity implements TextWatcher,
    CompoundButton.OnCheckedChangeListener,
    View.OnClickListener,
    DatePickerDialog.OnDateSetListener

{
  private EditText geolocation;
  private Residence residence;

  private CheckBox rented;
  private Button dateButton;

  private Portfolio portfolio;

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

    geolocation = (EditText) findViewById(R.id.geolocation);
    residence = new Residence();

    // Register a TextWatcher in the EditText geolocation object
    geolocation.addTextChangedListener(this);

    dateButton = (Button) findViewById(R.id.registration_date);
    dateButton  .setOnClickListener(this);

    rented = (CheckBox) findViewById(R.id.isrented);

    MyRentApp app = (MyRentApp) getApplication();
    portfolio = app.portfolio;
    Long resId = (Long) getIntent().getExtras().getSerializable("RESIDENCE_ID");
    residence = portfolio.getResidence(resId);
    if (residence != null) {
      updateControls(residence);
    }
  }

  public void updateControls(Residence residence) {
    geolocation.setText(residence.geolocation);
    rented.setChecked(residence.rented);
    dateButton.setText(residence.getDateString());
  }

  @Override
  public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

  }

  @Override
  public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

  }

  @Override
  public void afterTextChanged(Editable editable) {
    residence.setGeolocation(editable.toString());
  }

  @Override
  public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
    Log.i(this.getClass().getSimpleName(), "rented Checked");
    residence.rented = isChecked;
  }

  @Override
  public void onClick(View view) {
    switch (view.getId())
    {
      case R.id.registration_date      : Calendar c = Calendar.getInstance();
        DatePickerDialog dpd = new DatePickerDialog (this, this, c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH));
        dpd.show();
        break;
    }
  }

  @Override
  public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
    Date date = new GregorianCalendar(year, monthOfYear, dayOfMonth).getTime();
    residence.date = date.getTime();
    dateButton.setText(residence.getDateString());
  }
}

The application at the end of this lab is available for reference here: myrent-03