Skip to content

Android DatePickerDialog and the DialogFragment

2011 November 15

Introduction:

Starting with Honeycomb and continuing in Ice Cream Sandwich, the Android SDK has introduced several new UI concepts to support larger screens such as tablets. Perhaps the most notable new UI feature is the Fragment. Along with the updates to move developers towards fragments and size-independent layouts, Google has deprecated the methods commonly used to display Dialogs from Activities (see Activity.showDialog() and Activity.onCreateDialog()). These methods were convient; displaying any type of Dialog was possible by simply overriding onCreateDialog(). In order to provide a better user experience when displaying dialogs from Fragments, Google has provided the DialogFragment. I have yet to find any decent tutorials working with DialogFragments, but this code sample provided in the DialogFragment reference suffices.

I began to utilize this sample to provide a DatePickerDialog via DialogFragment. There exists one glaring problem with this code sample and applying it to a DatePicker: We need callbacks! After the user selects a date, we need to do something with that date. I set off to work up a useful DialogFragment that displayed a DatePicker.

My Solution:

Based on the sample code provided by Google, I came with up with a reusable DialogFragment that provided all the necessary DatePicker functionality. I could have continued with their theme and added the callback interface to the static inner class, but this will be far too reusable to nest it in some later irrelevant Activity. I instead went with a first class….class approach to be a little more flexible. On with it!

Step 1: Create a new class

Create a new class which inherits from DialogFragment and define a newInstance() method. Not much goes on in newInstance(), just some initialization.

public class DateDialogFragment extends DialogFragment {
 
    public static String TAG = "DateDialogFragment";
 
    static Context sContext;
    static Calendar sDate;
    static DateDialogFragmentListener sListener;
 
    public static DateDialogFragment newInstance(Context context, int titleResource, Calendar date){
		DateDialogFragment dialog  = new DateDialogFragment();
 
	sContext = context;
        sDate = date;
 
	Bundle args = new Bundle();
	args.putInt("title", titleResource);
	dialog.setArguments(args);
	return dialog;
    }
}

Step 2: Implement onCreateDialog()

Here’s where we can return the DatePickerDialog.

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    return new DatePickerDialog(sContext, dateSetListener, sDate.get(Calendar.YEAR), sDate.get(Calendar.MONTH), sDate.get(Calendar.DAY_OF_MONTH));
}

Note dateSetListener being passed as the callback, I will detail this later.

Step 3: Define callback interface

Our DateDialogFragment needs a way to report back to its calling Fragment or Activity so we define an interface with a single callback method.

public interface DateDialogFragmentListener{
    public void dateDialogFragmentDateSet(Calendar date);
}

We also give callers a setDateDialogFragmentListener() method.

public void setDateDialogFragmentListener(DateDialogFragmentListener listener){
    sListener = listener;
}

Here’s what it looks like from the Activity or Fragment.

//create a new DateDialogFragment
DateDialogFragment ddf = DateDialogFragment.newInstance(this, R.string.set_date, date);
 
//assign a new DateDialogFragmentListener
ddf.setDateDialogFragmentListener(new DateDialogFragmentListener() {
    //fired when user selects date
    @Override
    public void dateDialogFragmentDateSet(Calendar date) {
        // update the fragment
        mDateDetailFragment.updateDate(date);
    }
});

Step 4: Connect listeners

Now that we have defined a listener interface for the calling Activity or Fragment to receive notifications from our custom DateDialogFragment, we need to connect our DateDialogFragment to the DatePickerDialog we have created inside of it. I alluded to this in Step 2, where the dateSetListener object was being passed in the DatePickerDialog constructor.

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    return new DatePickerDialog(sContext, dateSetListener, sDate.get(Calendar.YEAR), sDate.get(Calendar.MONTH), sDate.get(Calendar.DAY_OF_MONTH));
}

dateSetListener is a DatePickerDialog.OnDateSetListener, the callback interface given to us by the default Android DatePickerDialog. In our DateDialogFragment class, we implement this interface and simply fire our own DateDialogFragmentListener.dateDialogFragmentDateSet() (see Step 3), passing along the date from the DatePickerDialog.OnDateSetListener. This completes the marriage between our DateDialogFragment and the DatePickerDialog within.

private DatePickerDialog.OnDateSetListener dateSetListener =
    new DatePickerDialog.OnDateSetListener() {
 
	@Override
	public void onDateSet(DatePicker view, int year, int monthOfYear,
			int dayOfMonth) {
 
                //create new Calendar object for date chosen
                //this is done simply combine the three args into one
		Calendar newDate = Calendar.getInstance();
		newDate.set(year, monthOfYear, dayOfMonth);
		//call back to the DateDialogFragment listener
		sListener.dateDialogFragmentDateSet(newDate);
 
	}
};

Step 5: Show a DateDialogFragment

Now that we have defined our DialogFragment subclass that provides a DatePickerDialog, we can use it by invoking it from an Activity.

//create new DateDialogFragment
DateDialogFragment ddf = DateDialogFragment.newInstance(this, R.string.set_date, date);
 
ddf.setDateDialogFragmentListener(new DateDialogFragmentListener() {
 
    @Override
    public void dateDialogFragmentDateSet(Calendar date) {
        // update the fragment
	mDateDetailFragment.updateDate(date);
    }
});
 
ddf.show(getSupportFragmentManager(), "date picker dialog fragment");

And that does it. You now have a reusable DialogFragment that provides a familiar DatePickerDialog with all its expected behavior.

Note: This solution is backwards compatible for pre-Honeycomb Android. Simply use the Android compatibility package.

For complete source code of a simple app that makes use of the DateDialogFragment, click here.

This bundle is invalid. Apple is not currently accepting applications…

2011 October 27
by kyle

I seem to be having my share of environmental issues this week. This time its with xcode while trying to upload an update to the app store.

The error states ‘This bundle is invalid. Apple is not currently accepting applications built with this version of the SDK, xcode.’ Off to Google I go. There was plenty of help for this issue.

Since I had published the app last, I had updated Xcode to 4.2 with support for iOS 5.0. So like commenters recommend (albeit referring to older iOS and Xcode versions), I set my Base SDK to 5.0, rebuild and upload. Same message. ‘This bundle is invalid. Apple is not currently’ yada yada. So my Base SDK is updated, and I have Xcode 4.2, and panic sets in. What if my bundle ID has been borked and I can’t deliver the update to my client’s customers?

I browse to the Xcode download site, verify the most recent version with mine. Matches. More panic. But look closely at my build number:

Xcode Current Install Build

Xcode Current Install Build

vs that found on the download site:

Xcode Latest Build

Xcode Latest Build

Ahhh different build numbers. So I download the newest Xcode. All of it. Please Apple, give us an update mechanism. Install the new Xcode, build, upload, enjoy.

In summation:

If you’re receiving the error: ‘This bundle is invalid. Apple is not currently accepting applications built with this version of the SDK, xcode’ check your Base SDK to be sure its the most recent, and check for a newer Xcode build paying attention to the build numbers instead of version number only.

Eclipse ERROR: Unknown option ‘–no-crunch’ When Building Android Project

2011 October 20

I recently updated Eclipse via its automatic updates to the most current version. I also downloaded the brand new Ice Cream Sandwich SDK. Somewhere along the way, some Eclipse packages became out of date or misaligned.

I was making some updates to my app when suddenly Eclipse would not build the project. The error given was

ERROR: Unknown option '--no-crunch'

After performing several project clean/build and Eclipse restart cycles, I finally found the right google search term to lead me to this post.

Here is the correct answer in a bit more visual presentation.

1. Open SDK Manager. In Eclipse goto Window -> Android SDK Manager. There’s also a handy button for it in the Eclipse toolbar.

eclipse_toolbar

Eclipse Toolbar Android SDK Manager


2. Uncheck ‘Installed’
3. Choose ‘Repository’
4. Install the latest versions of ‘Android SDK Tools’, ‘Android SDK platform-tools’, and ‘Android Support Package’.
Android SDK Manager

Android SDK Manager


5. Once they’re finished installing, restart Eclipse for good measure.

That should take care of the strange build error and send you on to finding actual bugs.

Using JExcelApi in an Android App

2011 October 2

A key feature in one of my Android projects is the ability to export the app’s data into a Microsoft Excel® readable spreadsheet. Unfortunately the app’s exports require formatting so a simple CSV file is out of the question. I need true to form .xlsx (or .xls) files.

Some quick research yielded The Apache POI Project. I proceeded to download the library and import into my existing Android project. Much to my dismay, Eclipse began throwing build errors stating something like ‘Conversion to Dalvik format failed with error 2′. As it turns out, the Apache POI library as downloaded is not compatible with Android and the Dalvik VM.

The problem is that Apache POI makes use of several API’s that are not available in Android (StAX, java.awt, others?). You see in order to fit and perform well on mobile devices, Android makes use of a smaller subset of standard Java packages¹. A few posts here and there hinted that it may be possible to build Apache POI for Android, but I simply do not have the experience building such a massive project for a different platform. Note: If anyone has Apache POI built and working for Android please share your experiences. I will gladly provide what little information I have on the task.

Not to be deterred I kept searching for a suitable alternative and was relieved to find JExcelApi. This library turned out to be a great fit for my project since it was much smaller than Apache POI having only bits necessary to create spreadsheet files (as opposed to the full Microsoft Office® suite in Apache POI). Perhaps the only disadvantage of JExcelApi is its inability to create the newer .xlsx files using the ooxml file format. I was limited to the binary .xls format. This is a minor issue at the moment since all major office suites plan to continue .xls support.

How do you use JExcelApi in an Android project? I’m glad you asked. Let’s get to it.

1. Download JExcelApi.

Navigate to jexcelapi.sourceforge.net and download the latest release.

2. Add the library to your Android project.

Add the downloaded .jar file like any other. For Eclipse users here is a brief how-to. Detailed instructions are beyond the scope of this post.

3. Create a Workbook object.

a. Use a temp file!

It turns out that JExcelApi is a resource hog when creating files. This is no good in a mobile environment so some quick research yielded this fix. WorkbookSettings.setUseTemporaryFileDuringWrite(true);

b. Create standard java.io.File.
c. Create a new WritableWorkbook.

/**
 * 
 * @param fileName - the name to give the new workbook file
 * @return - a new WritableWorkbook with the given fileName 
 */
public WritableWorkbook createWorkbook(String fileName){
    //exports must use a temp file while writing to avoid memory hogging
    WorkbookSettings wbSettings = new WorkbookSettings(); 				
    wbSettings.setUseTemporaryFileDuringWrite(true);   
 
    //get the sdcard's directory
    File sdCard = Environment.getExternalStorageDirectory();
    //add on the your app's path
    File dir = new File(sdCard.getAbsolutePath() + "/JExcelApiTest");
    //make them in case they're not there
    dir.mkdirs();
    //create a standard java.io.File object for the Workbook to use
    File wbfile = new File(dir,fileName);
 
    WritableWorkbook wb = null;
 
    try{
	//create a new WritableWorkbook using the java.io.File and
	//WorkbookSettings from above
	wb = Workbook.createWorkbook(wbfile,wbSettings); 
    }catch(IOException ex){
	Log.e(TAG,ex.getStackTrace().toString());
	Log.e(TAG, ex.getMessage());
    }
 
    return wb;	
}

4. Create a Sheet

Given a WritableWorkbook, creating a new sheet in that workbook is as trivial as providing a sheet name and index. The index is used to place the sheet along the bottom tabs in the workbook.

/**
 * 
 * @param wb - WritableWorkbook to create new sheet in
 * @param sheetName - name to be given to new sheet
 * @param sheetIndex - position in sheet tabs at bottom of workbook
 * @return - a new WritableSheet in given WritableWorkbook
 */
public WritableSheet createSheet(WritableWorkbook wb, 
    String sheetName, int sheetIndex){
    //create a new WritableSheet and return it
    return wb.createSheet(sheetName, sheetIndex);
}

5. Write Cells

Now that we have a worksheet within a workbook, we can write data to the cells. The most simple way of doing this in JExcelApi is to create a new Label object.

/**
 * 
 * @param columnPosition - column to place new cell in
 * @param rowPosition - row to place new cell in
 * @param contents - string value to place in cell
 * @param headerCell - whether to give this cell special formatting
 * @param sheet - WritableSheet to place cell in
 * @throws RowsExceededException - thrown if adding cell exceeds .xls row limit
 * @throws WriteException - Idunno, might be thrown
 */
public void writeCell(int columnPosition, int rowPosition, String contents, boolean headerCell,
  	WritableSheet sheet) throws RowsExceededException, WriteException{
    //create a new cell with contents at position
    Label newCell = new Label(columnPosition,rowPosition,contents);
 
    if (headerCell){
    	//give header cells size 10 Arial bolded 	
    	WritableFont headerFont = new WritableFont(WritableFont.ARIAL, 10, WritableFont.BOLD);
    	WritableCellFormat headerFormat = new WritableCellFormat(headerFont);
    	//center align the cells' contents
    	headerFormat.setAlignment(Alignment.CENTRE);
        newCell.setCellFormat(headerFormat);
    }
 
    sheet.addCell(newCell);
}

Note the use of WritableFont and WritableCellFormat to influence the appearance of the cells. JExcelApi provides all the necessary methods to format your sheets. To see all of the formatting possibilites JExcelApi offers, consult the documentation downloaded in step 1.

And that’s it!

Nothing complicated is needed to use JExcelApi in an Android project. It happily (unlike Apache POI) just works. Just remember to make use of WorkbookSettings.setUseTemporaryFileDuringWrite(true) as mentioned above. I found that it doesn’t take many files/exports to eat up all available memory on a Motorola Droid. This fix is sure to have a performance hit since it must write to device storage. That being said, JExcelApi has handled exporting 5-8 files with 20-40 rows times 4 sheets of data each. Extensive testing with sufficiently large data sets has not yet been performed. Testing was done on a Motorola Droid and lesser phones; adjust performance expectations accordingly when trying on your device. It does seem beneficial to place file creation and writing on a separate thread. For the use case I mentioned above I haven’t felt obligated to do so, but this feature will likely make its way into a future release.

1 – http://www.zdnet.com/blog/burnette/java-vs-android-apis/504