0

I know this has been discussed here many times already, but I still did not find a solution to this. I am creating "wizard" component for Android using ViewPager which contains some fragments (how many fragments depends on developer who will use this extension), and I need to save the wizard state. Only way I found was to force developers to declare static fragments when using this extension, but I really don't like that (because it is more complicated for developers and I want to make it as easy to use as possible).

My extension class:

public class WizardActivity extends AppCompatActivity {

    private WizardAdapter wizardAdapter;
    private WizardPager wizardPager;
    private Button buttonNext;
    private Button buttonPrevious;
    private int wizardPagerID;
    private int buttonNextID;
    private int buttonPreviousID;
    private int layoutID;
    private boolean swipeEnabled;

    public WizardActivity(int layoutID, int buttonNext, int buttonPrevious, int wizardPager, boolean swipeEnabled) {
        super();
        this.wizardAdapter = new WizardAdapter(getSupportFragmentManager());
        this.wizardPagerID = wizardPager;
        this.buttonNextID = buttonNext;
        this.buttonPreviousID = buttonPrevious;
        this.layoutID = layoutID;
        this.swipeEnabled = swipeEnabled;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(layoutID);

        wizardPager = (WizardPager) findViewById(wizardPagerID);
        buttonNext = (Button) findViewById(buttonNextID);
        buttonPrevious = (Button) findViewById(buttonPreviousID);

        wizardPager.setAdapter(wizardAdapter);
        wizardPager.setSwipeEnabled(swipeEnabled);

        buttonNext.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (onNextButton(wizardPager.getCurrentItem())) {
                    wizardPager.showNext();
                    refreshButtons();
                }
            }
        });

        buttonPrevious.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(onPreviousButton(wizardPager.getCurrentItem())) {
                    wizardPager.showPrevious();
                    refreshButtons();
                }
            }
        });

        if(savedInstanceState != null) {
            wizardPager.setCurrentItem(savedInstanceState.getInt("page", 0));
        }

        refreshButtons();
    }

    @Override
    public void onBackPressed() {
        if (wizardPager.getCurrentItem() == 0) {
            super.onBackPressed();
        } else {
            wizardPager.showPrevious();
            refreshButtons();
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("page", wizardPager.getCurrentItem());
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        refreshButtons();
    }

    public WizardPager getWizardPager() { return wizardPager; }
    public WizardAdapter getWizardAdapter() { return wizardAdapter; }
    public Button getButtonNext() { return buttonNext; }
    public Button getButtonPrevious() { return buttonPrevious; }

    public void addWizardFragment(WizardFragment fragment) { wizardAdapter.addFragment(fragment); }

    public boolean showNextFragment() {
        return onNextButton(wizardPager.getCurrentItem()) && wizardPager.showNext();
    }
    public boolean showPreviousFragment() {
        return onPreviousButton(wizardPager.getCurrentItem()) && wizardPager.showPrevious();
    }

    public void refreshButtons() {
        WizardFragment currentFragment = wizardAdapter.getItem(wizardPager.getCurrentItem());
        buttonPrevious.setEnabled(currentFragment.isPreviousEnabled());
        buttonPrevious.setText(currentFragment.getPreviousText());
        buttonNext.setEnabled(currentFragment.isNextEnabled());
        buttonNext.setText(currentFragment.getNextText());
    }

    public boolean onNextButton(int currentItem) {
        return true;
    }

    public boolean onPreviousButton(int currentItem) {
        return true;
    }
}

Other developer's class can look like this:

public class SetupActivity extends WizardActivity {

    private static SetupFragment setupFragment = new SetupFragment();
    private static SetupLicenseFragment setupLicenseFragment = new SetupLicenseFragment();
    private static SetupLoginFragment setupLoginFragment = new SetupLoginFragment();

    public SetupActivity() {
        super(R.layout.activity_setup, R.id.button2, R.id.button3, R.id.pager, false);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        String previousText = getString(R.string.setup_button_previous);
        String nextText = getString(R.string.setup_button_next);
        String finishText = getString(R.string.setup_button_finish);

        setupFragment.setupFragment(previousText, nextText, false, true);
        setupLicenseFragment.setupFragment(previousText, nextText, true, false);
        setupLoginFragment.setupFragment(previousText, finishText, true, false);

        addWizardFragment(setupFragment);
        addWizardFragment(setupLicenseFragment);
        addWizardFragment(setupLoginFragment);

        super.onCreate(savedInstanceState);
    }

    @Override
    public boolean onNextButton(int currentItem) {
        if(currentItem == 2) {
            Intent intent = new Intent(SetupActivity.this, MainActivity.class);
            startActivity(intent);
            finish();
            return false;
        }
        return super.onNextButton(currentItem);
    }
}

(WizardAdapter contains ArrayList of WizardFragments)

Is there a way to save the fragments added by the other developers without making it harder for the developers?

Ladas125
  • 265
  • 3
  • 14
  • Probably you should pass whole adapter from SetupActivty and populate fragments after activity recreated, take a look on this http://stackoverflow.com/questions/11631408/android-fragment-getactivity-sometime-returns-null – Georgy Gobozov Oct 14 '15 at 14:14

1 Answers1

0

Making fragments static fields inside your activity class is rather bad idea, android will recreate fragment during config changes so your static instances will get destroyed. This is true for non retained fragments, but retained fragments are usefull mostly for keeping data and not for building UIs.

I suggest you recreate your wizard manager inside WizardActivity.OnCreate by using getFragmentManager().findFragmentByTag. Tags should be stored during initial addWizardFragment( ... ); calls from base activity - you can easily get them from passed in fragment instance. Of course all the other state should be saved using normal procedure as described in Handling Runtime Changes.

Your clients should not call addWizardFragment during config changes. So they should write rather:

if ( savedInstanceState == null ) {
    addWizardFragment(setupFragment);
    addWizardFragment(setupLicenseFragment);
    addWizardFragment(setupLoginFragment);
}
marcinj
  • 48,511
  • 9
  • 79
  • 100