Search code examples
javaandroidorientation-changesdatepickerdialog

How to make DatePickerDialog maintaining selected user date when changing screen orientation?


In an opened DatePickerDialog, when screen orientation is changed, it reset the selected user data.

(the DatePickerDialog does NOT CLOSE and it does not maintain the selected data)

Code:

public class ActivityNeki extends FragmentActivity {
    DialogFragment newDF = null;
    private int datY, datM, datD;

    @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
        setContentView(R.layout.activity);
        if(savedInstanceState == null){ setTheData(); writeTheData(); }
    }

    @Override protected void onSaveInstanceState(Bundle outState) {
        outState.putInt("izY", datY); outState.putInt("izM", datM); outState.putInt("izD", datD);
        super.onSaveInstanceState(outState);
    }
    @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState);
        datY = savedInstanceState.getInt("izY"); datM = savedInstanceState.getInt("izM"); datD = savedInstanceState.getInt("izD");
        writeTheData();
    }

    public void onClickOpenDPD(View view) {    // the method that is caled from XML onClick
        class MyDialogFragment extends DialogFragment {
            @Override public void onDestroyView() {
                if (getDialog() != null && getRetainInstance()) getDialog().setDismissMessage(null);
                super.onDestroyView();
            }
            @Override public void onCreate(Bundle state) { super.onCreate(state);
                setRetainInstance(true);
            }
            @Override public Dialog onCreateDialog(Bundle state) {
                DatePickerDialog dpd = new DatePickerDialog( getActivity(), new DatePickerDialog.OnDateSetListener() {
                    @Override public void onDateSet(DatePicker view, int leto, int mesec, int dan) {
                        datY = leto; datM = mesec; datD = dan;
                        writeTheData();
                } }, datY, datM, datD);
                return dpd;
            }
        }
        newDF = new MyDialogFragment();
        newDF.show( getSupportFragmentManager(), null );
    }
    public void setTheData(){
        Calendar c = Calendar.getInstance();
        datY = c.get(Calendar.YEAR); datM = c.get(Calendar.MONTH); datD = c.get(Calendar.DAY_OF_MONTH);
    }
    public void writeTheData(){  /* writes the data in a txtView */ }
}

suggestion me, how to solve this issue?


Solution

  • A properly implemented DialogFragment will be able to restore the state without any additional work on your side. The main problem in your code is the non-static inner class. This is flawed for 2 reasons:

    1. A non-static inner class naturally keeps a reference to its outer class (see link in #2). This is in fact a reference to your activity, your context, the views; basically everything that the user is facing. When the device is rotated or the user switches to another app, then your app is most likely shut down. However, a retained fragment will be kept alive, which can be very useful sometimes. But since it's an inner class in your case the garbage collection is not allowed to trash the context, activity, etc. because there is still an active reference around. Thus you are leaking memory. And if you access this context (e.g. to update a view) after application recreation you'll get a runtime exception telling you that you're dealing with an outdated context/view. You might wanna check out WeakReferences if you really want to go down that road.

      https://stackoverflow.com/a/10968689/1493269

    2. Another reason why non-static inner classes are dangerous is that they never provide a default constructor. But the Android framework requires every Fragment to have an empty constructor in order to instantiate it behind the scenes. If your app is teared down and restarted (e.g. on rotation change), then Android will re-instantiate your Fragment via the compulsory default constructor and later restore the state via the serialized bundle. If you do not have a default constructor: runtime exception. You kind of work around of this by retaining your fragment, but I explained in 1) why this is bad as well.

      http://thecodersbreakfast.net/index.php?post/2011/09/26/Inner-classes-and-the-myth-of-the-default-constructor

    Moral of the story: Be careful with non-static inner classes.

    How your code should be working:

    ActivityNeki.java

    public class ActivityNeki extends FragmentActivity implements DatePickerDialog.OnDateSetListener
    {
        @Override
        protected void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity);
    
            Fragment fragment = getSupportFragmentManager().findFragmentByTag( "my_dialog_tag" );
    
            if( fragment != null )
            {
                ( (MyDialogFragment) fragment ).listener = this;
            }
        }
    
        // Called from xml
        public void onClickOpenDPD(View view)
        {    
           MyDialogFragment.newInstance( x, x, x, this ).show( getSupportFragmentManager(), "my_dialog_tag" );
        }
    
        public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth)
        {
            // Do your crazy callback stuff
        }
    }
    

    MyDialogFragment.java

    class MyDialogFragment extends DialogFragment
    {
        public static MyDialogFragment newInstance( int datY, int datM, int datD, DatePickerDialog.OnDateSetListener listener )
        {
            Bundle bundle = new Bundle( 3 );
            bundle.putInt( "y", datY );
            bundle.putInt( "m", datM );
            bundle.putInt( "d", datD );
    
            MyDialogFragment fragment = new MyDialogFragment();
            fragment.setArguments( bundle );
            fragment.listener = listener;
            return fragment;
        }
    
        public DatePickerDialog.OnDateSetListener listener = null;
    
        // Not entirely sure if this is still necessary    
        @Override
        public void onDestroyView()
        {
            if(getDialog() != null && getRetainInstance())
            {
                getDialog().setDismissMessage(null);
                super.onDestroyView();
            }
        }
    
        @Override
        public Dialog onCreateDialog(Bundle state)
        {
            return new DatePickerDialog(
                getActivity(),
                listener,
                getArguments().getInt( "y" ),
                getArguments().getInt( "m" ),
                getArguments().getInt( "d" )
            );
        }
    }
    

    Please excuse minor errors, as I wrote this off the top of my head without testing.