سؤال

I am using the v4 Support library in our app, and I have a step-based process in the app that is working fine under normal circumstances. However, we have a requirement that everything needs to work and not crash under memory pressure situations. So I'm using the SetAlwaysFinish library to help with this (https://github.com/bricolsoftconsulting/SetAlwaysFinish). It's been extremely helpful in pinpointing areas the need to detect and handle these kinds of situations, but I've run into one that has me stumped. Keep in mind this works fine under normal circumstances, but I'm explicitly testing with the "setAlwaysFinish" = ON.

Here's my setup: I have a "ProcessActivity" class that hosts the layout for a ViewPager. The ViewPager has an Adapter set, which contains my list of Fragments that will encompass the process. This is in the onCreate() method:

ProcessActivity:

createSteps();  // this creates/populates the ArrayList "processSteps" with Fragments
theAdapter = new ProcessAdapter(this, processSteps); // the ProcessAdapter class extends FragmentStatePagerAdapter, with an additional list of Fragment steps
theViewPager.setAdapter(theAdapter);

There are other pieces, but that is the core of how it's setup. During one of the Fragment steps, I actually need to "break" from the step process and push out to an Activity temporarily to do some logic (and then subsequently return to the step process). I do this as follows, with a ForResult, since I need to be able to handle an OK/Cancel action in the activity when it finishes:

Step4Fragment:

Intent intent = new Intent(getActivity(), ThePushActivity.class);
intent.putExtra(blah..);
startActivityForResult(intent, THE_REQCODE);

Once it pushed onto this view, sometimes the onDestroy() method of both the ProcessActivity and Step4Fragment are called (due to the alwaysFinish). Sometimes it seems to work fine and it calls back into the Step-Fragment process. But usually, what it does is that it will call the ProcessActivity's onDestroy() method, and then it will re-call the onCreate() method with the bundle saved-instance-state populated. This will call the step-creation code above, and it puts the app in a funky state where the last step the user was on is showing, but behind the scenes it's actually on the first step (fragments are, at that point, out of whack and disconnected), and a crash inevitably occurs. It seems at this point, the Step4Fragment is completely disjointed and will crash somewhere if you try to do anything, even though it seems as if it got re-instantiated correctly

Any thoughts on best ways to address this? I think it's fine if I find a way to even just reset things so that it kicks the user back to the first step of the process if a memory issue is occurring. However, my whole concern is the crash, of course.

Let me know if I need to provide more specifics. Thanks in advance for any help!

UPDATE #1: Just a quick update that I've noticed that the Fragments are re-instantiated and initialized, and it properly falls into the "current" Fragment's onActivityResult() method and does what it needs to do properly. It seems where the disconnect lies is in the base ProcessActivity class, following one of these scenarios. The ViewPager layout shows properly, but everything outside of the ViewPager is not correct (i.e. it is indicating that it's on the 1st step of the process, when it should indicate that it's on the 4th, and the navigation buttons are showing for the 1st step rather than the 4th).

So I'm guessing I need to somehow properly set those elements manually. In digging deeper into this, I may be leaving out pieces of code that are needed for someone to actively help with this. If I could somehow access the state of a ViewPager field, which contains the ability to get the "currently shown fragment", before this whole onDestroy()/onCreate() was called, possibly from the savedInstanceState bundle? That would probably solve my issue. But from inspecting this bundle upon debugging, I pretty much only see the Fragments themselves, and their respective states. I'll keep digging, though.

Regardless, please let me know if anyone has any ideas on how this kind of thing should properly be done (at a high level, of course).

UPDATE #2 I'm seeing that even tho the "current" Fragment seems to be initiated correctly, and the view shows correctly, everything is detached. I can't call any methods on getResources() or getActivity(), etc. I actually was able to get the ProcessActivity 'working' properly based on saving off the step index (int) into the savedInstanceState bundle, and then reloading the UI elements around it. However, I still have this blocker with the current Fragment being detached from the Activity, even though it's seemingly re-instantiated properly.

UPDATE #3 When I follow the direction of this post: ViewPager and fragments — what's the right way to store fragment's state?, I end up with an immediate exception when I try to do the first putFragment(). The exception is: "IllegalStateException: Fragment Step4Fragment is not currently in the FragmentManager". I'm thinking this may have to do with the fact that I'm only keeping one Fragment to the left and one fragment to the right 'active' at any one time (i.e. offScreenPageLimit).

هل كانت مفيدة؟

المحلول 2

Turns out that the solution to this was similar to the solution presented in this post: ViewPager and fragments — what's the right way to store fragment's state?

I had a couple things going on that needed to change. First off, I needed to move the Fragments to fields rather than temp variables. Secondly, I needed to avoid instantiating new Fragments each time in the onCreate(). Instead, it should try to pull them from the savedInstanceState as such:

fragment1 = (Fragment1Step) getSupportFragmentManager().getFragment(savedInstanceState, Fragment1Step.class.getName());
fragment2 = (Fragment2Step) getSupportFragmentManager().getFragment(savedInstanceState, Fragment2Step.class.getName());
etc...

and then right after this, I have subsequent checks for each of them; if they are null (i.e. coming in fresh and not from a saved instance), then they will be instantiated new:

if (fragment1 == null) {
    fragment1 = new Fragment1Step();
} 
if (fragment2 == null) {
    fragment2 = new Fragment2Step();
} 

In order for this to work, of course, these should also be saved off in the onSaveInstanceState() method:

try {
    getSupportFragmentManager().putFragment(outState, Fragment1Step.class.getName(), fragment1);
} catch (Exception e) {
    // do nothing
}
try {
    getSupportFragmentManager().putFragment(outState, Fragment2Step.class.getName(), fragment2);
} catch (Exception e) {
    // do nothing
}
etc...

The reason why I have these in try/catch blocks is because our ViewPager only has an offScreenPageLimit of 1, so some of these will throw exceptions upon the "putFragment()" call if they are not currently in the 'active' stack of Fragments being shown.

It's not extremely pretty, and there may be a better way to handle this.. but this seems to work fine with this now in place.

نصائح أخرى

Are you sure that your Fragment isn't being re-instantiated?

All subclasses of Fragment must include a public empty constructor. The framework will often re-instantiate a fragment class when needed, in particular during state restore, and needs to be able to find this constructor to instantiate it. If the empty constructor is not available, a runtime exception will occur in some cases during state restore.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top