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.
Great tutorial. I like the way you let the caller set a listener, which seems like a better approach than the one in Google’s blog article (http://android-developers.blogspot.com/2012/05/using-dialogfragments.html).
One question. Inside DateDialogFragment class’s newInstance() function, sDate is set twice. Why is that?
public static DateDialogFragment newInstance(Context context, int titleResource, Calendar date){
DateDialogFragment dialog = new DateDialogFragment();
sContext = context;
sDate = Calendar.getInstance(); <— ??
sDate = date;
__
sol
Good catch! There’s no reason at all, just a left over from a prior version. I corrected the code.
Your code is somewhat making sense, but I am lost as to where mDateDetail fragment is instantiated?
mDateDetail is the DateDetailFragment and it is instantiated in the DateDetailActivity class’s onCreate() method.
Actually, whats getSupportFragmentManager() as well ? I guess I don’t understand the Activity part. Mainly because I don’t see its context to the rest of the code.
Check out this link: http://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#getSupportFragmentManager()
getSupportFragmentManager applies to apps written to support pre-Honeycomb devices using the support library.
For complete context download the complete example app linked in this post
So I was pulling my hair out trying to figure out your code Kbeal, and the reason was because there was so much other code that was not needed just to get a DatePickerDialog up and running (plus I ran your example and nothing shows up). The fact that I’m only 2 weeks into Android didn’t help lol! So with your gracious tutorial here and googles outdated one as well as learning fragments, I was able to make this:
https://github.com/Zeroe31890/DateDialogFragment
I am sure its not optimized well, but it gets the job done and is should be understandable to any newbie. Feel free to use it and definitely check it out and make any edits so it can be better optimized and usable for production apps using DatePickerDialogs. And thanks for the help!
Is it just me or does the dateDialogFragmentDateSet listener get called twice? It isn’t noticeable in your example because it happens so fast.
I’m just a lowly neophyte and I didn’t start out creating your lovely app. In my version where I don’t have all the other screens, I’m not updating a screen. I’m just putting up a toast message with the date selected for simplicity. It’s noticeable there when you get the same message twice. I put in a counter just to be sure I wasn’t imagining things and sure enough the counter get incremented and the newly updated counter also displays.
Finally, I figured: Its because I’m doing it wrong somehow.
So I downloaded your code and put in a toast message and counter in the updateDate method of the DateDetailFragment class. :- same result.
So here’s the actual question: Why?
Is there added value or any additional functionality enabled by using the the DialogFragment relative to just using the DatePickerDialog? It seems DatePickerDialog works fine as is….
As you’ve probably noticed this DialogFragment displays a DatePickerDialog. We do it this way because Activity.showDialog is now deprecated. Google has recently updated their pickers documentation to reflect this. You’ll notice it looks very similar to this post.
I created my dialogfragment in almost the same way and it works, but happend that if i rotate the screen while the dialog is displayed when it is recreated the listener is not attached anymore.
It happens also for you?
Yea Riccardo I was able to recreate. Thanks for pointing that out! I’m not sure of a better fix so I implemented a workaround that dismisses the dialog when the Activity is paused. This appears to be how the Calendar app works, though the Contacts app keeps the date picker dialog displayed. Find the updated code on my GitHub, here