Getting Choosy with Dates and Times
A Task Reminder application without a way to set the date and time is a poor Task Reminder application — it would only be a simple task list application.
If you’ve programmed dates and times in another programming language, you realize that building a mechanism for a user to enter the date and time can be a painstaking process. The Android platform comes to your rescue by providing two classes to assist you: DatePicker
and TimePicker
. These pickers also provide built-in classes for opening a dialog box where the user selects a date and time. Therefore, you can either embed the DatePicker
or TimePicker
into your application’s views or use the DialogFragment
classes.
Creating picker buttons
The reminder_edit.xml
file contains mechanisms to help show the DatePicker
and TimePicker
(under the EditText
definitions described earlier). These two buttons have labels above them, as shown in Listing 11-1.
Listing 11-1: The Date and Time Buttons with Their Corresponding TextView Labels
<TextView android:layout_width=”wrap_content” →1
android:layout_height=”wrap_content”
android:text=”@string/date” />
<Button →4
android:id=”@+id/reminder_date”
android:layout_height=”wrap_content”
android:layout_width=”wrap_content”
/>
<TextView android:layout_width=”wrap_content” →9
android:layout_height=”wrap_content”
android:text=”@string/time” />
<Button →12
android:id=”@+id/reminder_time”
android:layout_height=”wrap_content”
android:layout_width=”wrap_content”
/>
The code lines are explained in this list:
→1 The TextView
label for the Date button; displays the value of “Reminder Date”
according to the string resource
→4 Defines a button that the user clicks to open the DatePicker DialogFragment
(as explained in the following section)
→9 The TextView
label for the Time button; displays the value of “Reminder Time”
according to the string resource
→12 Defines a button that the user clicks to open the TimePickerDialogFragment
(explained in the section, “Creating the time picker”)
Creating the date picker
A user who clicks the Date button should be able to edit the date, as described in the following several sections.
Setting up the Date button click listener
To set up the Date button click listener, open the activity where your code will be placed. For the Task Reminder application, open the ReminderEditFragment.java
file.
Add the code lines shown in bold in Listing 11-2 to the onCreateView()
method.
Listing 11-2: Implementing the Date Button Click Listener
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.reminder_edit, container, false);
mTitleText = (EditText) v.findViewById(R.id.title);
mBodyText = (EditText) v.findViewById(R.id.body);
mDateButton = (Button) v.findViewById(R.id.reminder_date);
mTimeButton = (Button) v.findViewById(R.id.reminder_time);
mConfirmButton = (Button) v.findViewById(R.id.confirm);
mDateButton.setOnClickListener(new View.OnClickListener() {
→13
@Override
public void onClick(View v) {
→15
showDatePicker();
→16
}
});
return v;
}
The numbered lines are described in this list:
→13 Sets the onClickListener()
for the mDateButton
. The onClickListener()
executes when the button is clicked. The action that takes place on the button click is shown on line 16.
→15 Overrides the default click behavior of the button so that you can provide your own set of actions to perform. The View v
parameter is the view that was clicked.
→16 Defines what you want to happen when the button is clicked; calls a method on the showDatePicker()
fragment, as explained in the later section “Creating the showDatePicker()
method.”
Creating the DatePickerDialogFragment
The Android operating system comes supplied with a built-in Date PickerDialog
that lets users select (rather than type) a date. It doesn’t come wrapped neatly in a fragment, so you have to do it yourself.
Create a new file, name it DatePickerDialogFragment
, and add the following code:
package com.dummies.android.taskreminder;
import android.app.DatePickerDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.support.v4.app.DialogFragment; →6
public class DatePickerDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) { →10
Bundle args = getArguments(); →11
Fragment editFragment = getFragmentManager() →12
.findFragmentByTag(
ReminderEditFragment.DEFAULT_EDIT_FRAGMENT_TAG);
OnDateSetListener listener = (OnDateSetListener) editFragment; →15
return new DatePickerDialog(getActivity(), listener, →17
args.getInt(ReminderEditFragment.YEAR),
args.getInt(ReminderEditFragment.MONTH),
args.getInt(ReminderEditFragment.DAY));
}
}
Here’s how the code works:
→6 Be sure to use the android.support.v4.app.*
imports and not their equivalents from android.app
.
→10 The method that’s called when Android wishes to display the DatePicker
dialog box. This method creates it and returns it.
Sometimes you may need to use savedInstanceState
to restore the state from previous instances. However, in this case, the dialog box already does it for you, so you can safely ignore savedInstanceState
in this method.
→11 Constructor-like arguments for a fragment are passed via set Arguments()
, not via the fragment’s constructor, so this line retrieves those arguments using getArguments()
, which you need on line 17.
→12 Asks the FragmentManager
to find the fragment named DEFAULT_EDIT_FRAGMENT_TAG
, which is the ReminderEditFragment
.
→15 Casts the editFragment
to an OnDateSetListener
. The dialog box created by the onCreateDialog
needs the OnDateSetListener
object to inform the ReminderEditFragment
when the user has picked a date.
→17 This line calls the DatePickerDialog
constructor and passes getActivity()
, the current context listener obtained on line 12, and args.getInt(ReminderEditFragment.YEAR)
, args.getInt(ReminderEditFragment.MONTH)
, args.getInt(ReminderEditFragment.DAY)
, the year, month, and day, as specified in the arguments to this fragment.
Creating the showDatePicker() method
After you have a DatePickerDialogFragment
class, you can create an instance of it and show it to the user. The date button’s onClickListener
called showDatePicker
, so you can implement showDatePicker()
now. Add the following code to the ReminderEditFragment
class after your onCreateView()
:
//
// Dialog Constants
//
static final String YEAR = “year”;
static final String MONTH = “month”;
static final String DAY = “day”;
static final String HOUR = “hour”;
static final String MINS = “mins”;
static final String CALENDAR = “calendar”;
private void showDatePicker() {
FragmentTransaction ft = getFragmentManager().beginTransaction(); →11
DialogFragment newFragment = new DatePickerDialogFragment(); →12
Bundle args = new Bundle(); →13
args.putInt(YEAR, mCalendar.get(Calendar.YEAR)); →14
args.putInt(MONTH, mCalendar.get(Calendar.MONTH));
args.putInt(DAY, mCalendar.get(Calendar.DAY_OF_MONTH));
newFragment.setArguments(args); →17
newFragment.show(ft, “datePicker”); →18
}
This is how the code works:
→11 Implements a fragment transaction for the new fragment.
→12 Creates a DatePickerDialogFragment
instance.
→13 Creates the fragment constructor arguments in a bundle.
→14 Sets the year, month, and day of the dialog box fragment to the year, month, and day set in the mCalendar
object.
→17 Sets the fragment’s arguments to the values in the bundle created in line 4.
→18 Shows the DatePicker
dialog box fragment, which allows the dialog box to open onscreen. Because show()
calls commit()
, you don’t need to call it explicitly.
Creating the time picker
The TimePickerDialogFragment
allows users to select a time to be reminded of a pending task.
Setting up the Time button click listener
Setting up a TimePickerDialogFragment
is almost identical to setting up a DatePickerDialogFragment
. You first declare the onClickListener()
for the Time button. In ReminderEditFragment.onCreateView()
, add the following code snippet right before the return at the end:
mTimeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showTimePicker();
}
});
This method is the same as the Date button’s onClickListener()
, except that you’re calling showTimePicker()
instead of showDatePicker()
.
Creating the showTimePicker() method
To help you create the showTimePicker()
method, the full method definition, with code, is shown in Listing 11-3.
Listing 11-3: The showTimePicker() Method
private void showTimePicker() {
FragmentTransaction ft = getFragmentManager().beginTransaction();
DialogFragment newFragment = new TimePickerDialogFragment(); →3
Bundle args = new Bundle();
args.putInt(HOUR, mCalendar.get(Calendar.HOUR_OF_DAY)); →5
args.putInt(MINS, mCalendar.get(Calendar.MINUTE));
newFragment.setArguments(args);
newFragment.show(ft, “timePicker”); →8
}
The code in Listing 11-3 is fairly straightforward because it’s almost identical to that of the showDatePicker()
method. However, you can see differences on these lines:
→3 Creates a new instance of TimePickerDialogFragment
.
→5 Sets the arguments for the TimePickerDialogFragment
to be the calendar’s hour and minute components.
→8 Shows the fragment using the tag “timePicker”
, which isn’t visible to the user.
Creating the TimePickerDialogFragment
The Android operating system comes supplied with a built-in TimePicker Dialog
that lets users select (rather than type) a time. Like the Date PickerDialog
, the TimePickerDialog
doesn’t come wrapped neatly in a fragment, so you have to do it yourself.
The code for the TimePickerDialogFragment
is nearly identical to the DatePickerDialogFragment
except that it wraps a TimePickerDialog
instead of a DatePickerDialog
:
package com.dummies.android.taskreminder;
import android.app.Dialog;
import android.app.TimePickerDialog;
import android.app.TimePickerDialog.OnTimeSetListener;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
public class TimePickerDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle args = getArguments();
OnTimeSetListener listener = (OnTimeSetListener) getFragmentManager()
.findFragmentByTag(
ReminderEditFragment.DEFAULT_EDIT_FRAGMENT_TAG);
return new TimePickerDialog(getActivity(), listener,
args.getInt(ReminderEditFragment.HOUR),
args.getInt(ReminderEditFragment.MINS), false);
}
}
Adding the fragment to handle date picker and time picker callbacks
The remaining step in getting your date and time pickers working is to implement the OnDateSetListener
and OnTimeSetListener
so that the dialog box stores the new date and time when the user chooses a date or time. You store that value in a Calendar
object. It requires three steps:
1. Add the OnDateSetListener
and OnTimeSetListener
interfaces to the ReminderEditFragment
.
2. Add a Calendar
object called mCalender
to store the values that the user sets.
3. Set the mCalendar
object when the user picks a date or time, and then update the date and time buttons on the user interface with the new values.
From Listing 11-4, add the code in bold to the ReminderEditFragment
class to get your date and time pickers working.
Listing 11-4: Adding the OnDateSetListener and OnTimeSetListener interfaces to the ReminderEditFragment class
public class ReminderEditFragment extends Fragment
implements
OnDateSetListener, OnTimeSetListener
{ →2
private static final String DATE_FORMAT = “yyyy-MM-dd”;
private static final String TIME_FORMAT = “kk:mm”;
private Calendar mCalendar;
→7
@Override
public void onDateSet(DatePicker view, int year, int monthOfYear,
int dayOfMonth) {
→11
mCalendar.set(Calendar.YEAR, year);
mCalendar.set(Calendar.MONTH, monthOfYear);
mCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
updateButtons();
→15
}
@Override
public void onTimeSet(TimePicker view, int hour, int minute) {
→19
mCalendar.set(Calendar.HOUR_OF_DAY, hour);
mCalendar.set(Calendar.MINUTE, minute);
updateButtons();
}
private void updateButtons() {
→25
// Set the time button text
SimpleDateFormat timeFormat = new SimpleDateFormat(TIME_FORMAT);
String timeForButton = timeFormat.format(mCalendar.getTime());
mTimeButton.setText(timeForButton);
// Set the date button text
SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
String dateForButton = dateFormat.format(mCalendar.getTime());
mDateButton.setText(dateForButton);
}
}
Listing 11-4 works this way:
→2 Implements the OnDateSetListener
and OnTimeSetListener
callback objects.
→7 Stores the user’s date and time selections with a standard Java Calendar
object.
→11 Implements the onDateSet()
method of the OnDateSet Listener
. When the user sets the date in the dialog box, the Calendar
object is updated to reflect the new date.
→15 Updates the user interface buttons to reflect the new date and time.
→19 Sets the hour and minutes of the Calendar
object for the onTimeSet()
method of the OnTimeSetListener
.
→25 Updates the buttons to reflect the values selected by the user. The Calendar
object is converted into a couple of text strings — one for the date button and one for the time button — which are then used to set the text on those buttons. The first Simple DateFormat
uses TIME_FORMAT
to produce a time string in the format kk:mm
; the second SimpleDateFormat
uses DATE_FORMAT
to produce a date string in the format yyyy-MM-dd
.
Visit http://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html
for more details about SimpleDateFormat
date and time formatting.
To add the mCalendar
object, add the following lines to onCreate()
:
if (savedInstanceState != null
&& savedInstanceState.containsKey(CALENDAR)) { →2
mCalendar = (Calendar) savedInstanceState.getSerializable(CALENDAR); →3
} else {
mCalendar = Calendar.getInstance(); →5
}
The code works this way:
→2 This line checks a savedInstanceState
for a CALENDAR
field indicating that an earlier activity was saved and temporarily destroyed, such as rotating from Landscape mode to Portrait mode.
If you’re creating a brand-new instance of this fragment, saved InstanceState
is null and the application moves to line 5.
→3 If the savedInstanceState
includes a CALENDAR
field, this line pulls out the associated Calendar
object and sets mCalendar
to that value.
→5 When you’re creating a ReminderEditFragment
from scratch and you aren’t resurrecting a previous instance, this line sets mCalendar
to a new Calendar
instance (obtained by calling Calendar.getInstance()
). New Calendar
instances always default to the current date and time.
Now the mCalendar
instance is initialized.
Because onCreate()
is looking for a calendar instance in savedInstance State
on lines 1-2, you have to save your mCalendar
object to the saved InstanceState
whenever your activity is destroyed. That way, onCreate()
can pick up the saved value if the activity is ever re-created. To do so, add the following method to your class:
@Override
public void onSaveInstanceState(Bundle outState) { →2
super.onSaveInstanceState(outState);
// Save the calendar instance in case the user changed it →4
outState.putSerializable(CALENDAR, mCalendar);
}
The code works this way:
→2 onSaveInstanceState()
is a special method that Android calls whenever it’s about to destroy a fragment. You can store any data that you might be using, so if Android ever needs to re-create the same activity, it has all the necessary information.
Most views already know how to store their state and resurrect themselves, and they do it in the call to super.onSaveInstance State()
.
→4 The only task you have to handle manually is the mCalendar
field, so this line stores it in the bundle to use later when the activity is re-created.
Calendar objects can be serialized (stored as data), so this line uses the putSerializable()
method to save them.
Find out more information about Java serialization at http://java.sun.com/developer/technicalArticles/Programming/serialization
.
You can save all kinds of other types into bundles, such as ints, longs, strings, parcelables, and other exotic elements, so check http://d.android.com/reference/android/os/Bundle.html
to see the full list.