My problem involves an activity hosting three support fragments. One is a normal programmatic fragment (let's call it a home fragment). One is a portrait fragment added on top of the home fragment when the device is orientated, and one is 'headless', to continue an async task regardless of configuration changes. Very simple, I was working off this nice example.
public class HeadlessCustomerDetailFetchFragment extends Fragment{
private RequestCustomerDetails mRequest;
private AsyncFetchCustomerDetails mAsyncFetchCustomerDetails;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
mRequest = (RequestCustomerDetails)getActivity();
}
public void startFetching(String scannedBarcode) {
if(mAsyncFetchCustomerDetails != null && mAsyncFetchCustomerDetails.getStatus() == AsyncTask.Status.RUNNING) return;
if(mAsyncFetchCustomerDetails == null || mAsyncFetchCustomerDetails.getStatus() == AsyncTask.Status.FINISHED)
mAsyncFetchCustomerDetails = new AsyncFetchCustomerDetails(getActivity(), mRequest, mPartner, scannedBarcode);
}
public void stopFetching() {
if(mAsyncFetchCustomerDetails != null && mAsyncFetchCustomerDetails.getStatus() != AsyncTask.Status.RUNNING) return;
mAsyncFetchCustomerDetails.cancel(true);
}
}
In my activity's onCreate() I create and add the headless fragment if necessary.
mHeadlessCustomerDetailFetchFragment = (HeadlessCustomerDetailFetchFragment)getSupportFragmentManager()
.findFragmentByTag(HeadlessCustomerDetailFetchFragment.class.getSimpleName());
if(mHeadlessCustomerDetailFetchFragment == null) {
mHeadlessCustomerDetailFetchFragment = HeadlessCustomerDetailFetchFragment.instantiate(this, HeadlessCustomerDetailFetchFragment.class.getName());
getSupportFragmentManager().beginTransaction()
.add(mHeadlessCustomerDetailFetchFragment, mHeadlessCustomerDetailFetchFragment.getClass().getSimpleName())
.commit();
getSupportFragmentManager().executePendingTransactions();
id = null;
}
I then launch an async task (via my startFetching() function) after a 6 second delay (for testing) kicked off in the onCreateView() of the portrait fragment that is added when the orientation changes to portrait. The orientation change is detected in the activity's onCreate():
if (savedInstanceState == null) {
// Do some initial stuff for the home fragment
}
else {
getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
//Launch portrait fragment
FragmentLauncher.launchPortraitFragment(this);
}
When the task is finished, I return to the activity and attempt to update the UI of the active portrait fragment, but the fragment manager cannot find it, findFragmentByTag() returns null.
To be clear:
Maybe retaining a fragment aggressively kills other fragments that are not retained or something to that effect?
The root of the problem is how you maintain the reference to activity inside headless fragment.
It is not clear from the provided code how you update UI after completion of AsyncTask, lets assume you use mRequest
from the first code snippet. You give mRequest
to constructor when you need new AsyncTask and use this reference after AsyncTask completes.
It is ok, when you have no screen rotation between the moment when activity is created and when UI is updated. It is because you use the reference to activity which is still active.
It is not ok if you rotate screen. You have new activity every time after rotation. But mRequest is assigned only once when you create headless fragment in first call of activity’s onCreate()
. So it contains reference to the first instance of activity which is not active after rotation. There are 2 instances of activity after rotation in your case: the first - which is referenced by mRequest and the second - which is visible and active. You can confirm this by logging the reference of activity inside onCreate
: Log.i(TAG, "onCreate: this=" + this);
and inside activity’s method which updates UI after async task: Log.i(TAG, "updating UI: this=" + this);
Besides the first activity is in Destroyed state. All fragments are detached from this activity and non retained fragments are destroyed. That’s why findFragmentByTag
returns null.
If the headless fragment is not set to retain itself, then activity’s onCreate()
recreates it in every call. So mRequest
always references the last created activity with all fragments. In this case findFragmentByTag
returns not null.
To avoid this problem I suggest:
private WeakReference<RequestCustomerDetails> mRequest;
HeadlessCustomerDetailFetchFragment
to update this reference.public void updateResultProcessor(RequestCustomerDetails requestCustomerDetails) {
mRequest = new WeakReference(requestCustomerDetails);
// Update ui if there is stored result of AsyncTask (see p.4b)
}
mRequest.get()
is not null
then update UI.mRequest.get()
is null
then store result inside headless fragment and use it in p.2.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With