Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

FragmentPagerAdapter not restoring fragments upon Orientation Change

EDIT: I found out that the Activity is saving the instance, but the Fragments saved data is not making it up to the Activity savedInstanceState. I'm saving the current time in the outState, but its not making its way all the way up, as the activity has nothing in its SavedInstanceState for the time and returns 'null' for the time if I print it to the logcat....

I am building an application that has the a countup and countdown timer built in. The basic hosting activity for the timers is a FragmentActivity which hosts a FragementPagerAdapter that inflates two fragments within the code (I do not give an id to the fragments within the XML as they are not defined as fragments within the .xml). Everything works great until an orientation change and then the activity looks like it looses contact with the old fragments and just chooses to create new ones. This means that any current countdown is lost and any time chosen is also lost upon configuration change. I will need to keep the count going (if its started) and any numbers currently displayed....

I know that the Adapter is supposed to handle these things on its own, and I'm setting the SetRetainInstance(true) in both the OnCreate and OnCreateView.

I put in hooks into the Fragment code to let me know whenever the saveInstanceState is NOT null so at least I know what is going on, but it seems like the instance is always NULL, and it creates from scratch...always.

Some other solutions have me overriding the instantiateItem, but it seems that its is only used for getting callbacks reset, others changing a setting in the Manifest(frowned upon).... Other solutions look to me like I have things setup right...but obviously, I'm messing something up along the way. I'm only giving code for the FragmentActivity, FragementPagerAdapter, and a bit of the Fragment code as I don't want to spam the post with code that may not be the issue.

The FragmentActivity

public class Timer_Main extends FragmentActivity {
    ViewPager pager;
    Timer_Pager mAdapter;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.timer_pager);

        mAdapter = new Timer_Pager(this, getSupportFragmentManager());

        pager = (ViewPager) findViewById(R.id.timer_pager_display);
        pager.setAdapter(mAdapter);

    }

    public static String getTitle(Context ctxt, int position) {
                // TODO Auto-generated method stub
        return null;
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("pageItem", pager.getCurrentItem());

    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);

    }
}    

The FragementPagerAdapter

public class Timer_Pager extends FragmentPagerAdapter{
    Context ctxt = null;
    public Timer_Pager(Context ctxt, FragmentManager fm) {
        super(fm);
        this.ctxt = ctxt;
        }


    @Override
    public Fragment getItem(int position) {

        switch (position) {
        case 0: {
            return(Countdown_Fragment.newInstance());
        }
        case 1:
            return(Countup_Fragment.newInstance());
        }
        return null;
    }

    @Override
    public String getPageTitle(int position) {

        switch (position) {
        case 0: 
            return(String.format(ctxt.getString(R.string.Countdown_label)));
        case 1: 
            return(String.format(ctxt.getString(R.string.Countup_label)));
        }
        return null;
    }

    @Override
    public int getCount() {
        // doing only three pages, right now...one for countdown, one for countup,         and one for Tabata.
        return 2;
    }

    private static String makeFragmentName(int viewId, int position)
    {
         return "android:switcher:" + viewId + ":" + position;
    }
}    

The Fragment has a bit more code in it, so I'll push in just what should be the core of the problem....if more is needed, I'll splice it in.

public class Countdown_Fragment extends Fragment implements OnClickListener {
    Calendar CountdownTime = Calendar.getInstance();
    static AutoResizeTextView tv;
    static ImageButton HoursUp;
    static ImageButton HoursDown;
    static ImageButton MinutesUp;
    static ImageButton MinutesDown;
    static ImageButton SecondsUp;
    static ImageButton SecondsDown;
    static ImageButton Reset;
    static ImageButton StartPausecount;
    static ImageButton Stopcount;
    static Boolean Arewecounting = false;
    static Boolean Arewecountingdown = false;
    static AutoResizeTextView ThreetwooneGO;
    static MyCountdownTimer Countdown_Timer_Activity;
    ThreetwooneCount Start_countdown;
    static Long MillstoPass;

    static Countdown_Fragment newInstance() {

        Countdown_Fragment frag = new Countdown_Fragment();
        return (frag);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {

        outState.putString("CurrentTimer", (String) tv.getText());
        Log.v("status", "saved fragment state" + tv.getText());
        super.onSaveInstanceState(outState);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        setRetainInstance(true);
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {

            setRetainInstance(true);
            View result = inflater.inflate(R.layout.countdown_timer_layout,
                container, false);
        tv = (AutoResizeTextView) result.findViewById(R.id.timer_textview);
        tv.setOnClickListener(new View.OnClickListener() {
            public void onClick(View arg0) {
                new TimePickerDialog(getActivity(), t, 0, 0, true).show();
            }
        });
        if (savedInstanceState != null) {
            Log.v("status", "fragment is NOT empty");
            tv.setText(savedInstanceState.getString("CurrentTimer",
                    tv.toString()));
        } else {
            Log.v("status", "fragment is empty");
        //  tv.setText("00:00:00");

        }
        tv.resizeText();

        ThreetwooneGO = (AutoResizeTextView) result
                .findViewById(R.id.timer_countdown_text);

        HoursUp = (ImageButton) result.findViewById(R.id.hours_up);
        HoursDown = (ImageButton) result.findViewById(R.id.hours_down);
        MinutesUp = (ImageButton) result.findViewById(R.id.minutes_up);
        MinutesDown = (ImageButton) result.findViewById(R.id.minutes_down);
        SecondsUp = (ImageButton) result.findViewById(R.id.seconds_up);
        SecondsDown = (ImageButton) result.findViewById(R.id.seconds_down);
        Reset = (ImageButton) result.findViewById(R.id.reset);
        StartPausecount = (ImageButton) result
                .findViewById(R.id.startpausecount);
        Stopcount = (ImageButton) result.findViewById(R.id.stopcount);

        HoursUp.setOnClickListener(this);
        HoursDown.setOnClickListener(this);
        SecondsUp.setOnClickListener(this);
        SecondsDown.setOnClickListener(this);
        MinutesUp.setOnClickListener(this);
        MinutesDown.setOnClickListener(this);
        Reset.setOnClickListener(this);
        StartPausecount.setOnClickListener(this);
        Stopcount.setOnClickListener(this);

        return (result);
    }

    public void chooseTime(View v) {
        new TimePickerDialog(getActivity(), t, 0, 0, true).show();
    }

    TimePickerDialog.OnTimeSetListener t = new TimePickerDialog.OnTimeSetListener() {
        public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
            CountdownTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
            CountdownTime.set(Calendar.MINUTE, minute);
            CountdownTime.set(Calendar.SECOND, 0);
            CountdownTime.set(Calendar.MILLISECOND, 0);

            updateLabel();
        }
    };

    private void updateLabel() {

        String entiretime;
        entiretime = String.format("%tT", CountdownTime);
        tv.setText(entiretime);
    }

Here is my timer that I'm using for Countdown....

public class MyCountdownTimer {

private long millisInFuture;
private long countDownInterval;
Countdown_Fragment ctxt;
AutoResizeTextView Timer_edit;
private volatile boolean IsStopped = false;

public MyCountdownTimer(long pMillisInFuture, long pCountDownInterval,
        AutoResizeTextView Artv) {
    this.millisInFuture = pMillisInFuture;
    this.countDownInterval = pCountDownInterval;

    Timer_edit = Artv;

}

public void Start() {

    final Handler handler = new Handler();
    final Runnable counter = new Runnable() {

        public void run() {
            if (IsStopped == false) {
                if (millisInFuture <= 0) {
                    Countdown_Fragment.done_counting();
                } else {
                    long sec = millisInFuture / 1000;
                    Timer_edit.setText(String.format("%02d:%02d:%02d",
                            sec / 3600, (sec % 3600) / 60, (sec % 60)));
                    millisInFuture -= countDownInterval;
                    handler.postDelayed(this, countDownInterval);
                }
            }
        }
    };

    handler.postDelayed(counter, countDownInterval);
}

public void Cancel() {
    IsStopped = true;
    Timer_edit = null;
    Log.v("status", "Main Timer cancelled");
    // this.ctxt = null;

}
public long whatisourcount(){
    return(millisInFuture);
}
}
like image 379
Jeff DePiazza Avatar asked Aug 25 '13 04:08

Jeff DePiazza


1 Answers

As it turns out, the RetainInstance was causing a conflict between itself and the ViewPager/Adapter/FragmentManager doing their things . Removing it caused the Pager to properly rebuilt the Fragment, including the TextView I had, where it did not before. I also start to recieve a Bundle in the OnCreateView, where that was always null before with the RetainInstance set to True.

I had to remove the RetainInstance and utilize the OnSaveInstanceState and OnCreateView to pass in the current status of the Fragment before it was destroyed, and then re-create it in OnCreateView to reset the Fragment to its state before it was destroyed.

I was hoping that the Runnable that I was using to do the countdown would survive, or I would be able to reattach it, but I couldn't find a way. I had to save the current count in Milliseconds, and pass back to the Fragment to continue where it left off. Its not that big of a deal, but I am curious to see if you can truely re-attach all those things. The Runnable DOES still continue after the config change, but it doesn't update anything on the UI anymore, so I try to cancel the callbacks and null it when I'm inside OnSaveInstanceState.

I'm also reading items where I only need to use RetainInstance for items that have a AsyncTask attached or another similar item....otherwise, just rebuild it within the code.

like image 174
Jeff DePiazza Avatar answered Oct 19 '22 13:10

Jeff DePiazza