I have a fragment without view which I use to host an AsyncTask. I want to retain the fragment object during configuration changes (eg. orientation change). To my understanding using setRetainInstance(true) in the onCreate() method of the fragment should be enough. But here is my problem:
First of let me post some code I'm using.
My activity uses this in its onCreate() method
protected void onCreate(Bundle savedState)
{
super.onCreate(savedState);
/* omitted - other initialization that takes place */
fetcher = (DirectionsFetcherFragment)
manager.findFragmentByTag(DIRECTIONS_FETCHER_FRAGMENT);
if (fetcher == null)
{
Log.d(TAG, "Creating new DirectionsFetcherFragment instance");
fetcher = DirectionsFetcherFragment.newInstance( /*omitted*/);
transaction = manager.beginTransaction();
transaction.add(fetcher, DIRECTIONS_FETCHER_FRAGMENT);
transaction.commit();
}
}
and here are some samples from the fragment code
class DirectionsFetcherFragment extends Fragment
{
/* string constants omitted */
public static DirectionsFetcherFragment newInstance(/*omitted*/)
{
DirectionsFetcherFragment instance = new DirectionsFetcherFragment();
Bundle data = new Bundle();
/* omitted - adding the necessary data to the bundle */
instance.setArguments(data);
return instance;
}
public DirectionsFetcherFragment()
{
super();
/* omitted - setting default values for member variables */
}
#Override
public void onCreate(Bundle savedInstance)
{
Log.wtf(TAG, "onCreate()");
super.onCreate(savedInstance);
setRetainInstance(true);
Bundle arguments = getArguments();
/* omitted - other initializations tasks that take place */
}
}
Here's what's happening. On the initial activity creation the fetcher reference is null as expected and thus I create a new fragment and add it via a transaction. When/if I rotate the device though, when the activity's onCreate() method runs the fetcher reference is null, no fragment is returned by findFragmentByTag(). In the LogCat I can clearly see that the fragment's onDetach() -> onAttach() methods are run for the first fragment, but for some reason the fragment is then garbage collected and a new fragment is created.
Here is the LogCat output:
D/DirectionsViewer﹕ Initializing from intent <<-- Initial activity creation
D/DirectionsViewer﹕ Creating new DirectionsFetcherFragment instance <<-- Fragment creation
A/DirectionsFetcherFragment﹕ onAttach()
A/DirectionsFetcherFragment﹕ onCreate()
V/DirectionsViewer﹕ onStart()
V/DirectionsViewer﹕ onResume()
V/DirectionsViewer﹕ onSaveInstanceState() <<-- Rotationing and saving instance
A/DirectionsFetcherFragment﹕ onDetach() <<-- Fragment correctly detaches
D/DirectionsViewer﹕ Restoring from saved instance <<-- Activity restoration
D/DirectionsViewer﹕ Creating new DirectionsFetcherFragment instance <<-- Why is this happenning, shouldn't I get a reference to the existing fragment?
A/DirectionsFetcherFragment﹕ onAttach()
A/DirectionsFetcherFragment﹕ onCreate()
V/DirectionsViewer﹕ onStart()
V/DirectionsViewer﹕ onResume()
V/DirectionsViewer﹕ Deleting DirectionsViewer instance <<-- This is the old activity that gets garbage collected
A/DirectionsFetcherFragment﹕ Deleting DirectionsFetcherFragment instance <<-- This is the old fragment instance that gets garbage collected
Am I missing something? Thanks in advance for any answers.
Related
When my activity containing viewpager is killed by system in background and then restores its state, fragments are correctly created and viewpager adapter can also point to them correctly.
But when I get a fragment reference and try to access its fields, they are all null (checked by using breakpoint).
I checked this by placing breakpoints in fragment onCreateView() and in my activity button's clickListener.
((WelcomeFragment)homeActivityFragmentPageAdapter.getItem(POSITION_HOME)).setdata(myData);
Now this method will through null pointer exception since setdata(data) is internally accessing arraylist field of fragment.
This creates a problem for me since, my activity has to continuously feed network data to the fragment by calling its public method (as suggested by documentation).
How to insure that after state restored; correct instance is pointed in my activity.
Try to use instantiateItem adapter method instead getItem.
((WelcomeFragment)homeActivityFragmentPageAdapter.instantiateItem(mViewPager, POSITION_HOME)).setdata(myData);
Method getItem is overrided method, and common use is creation of child fragments.
EDIT:
In case of the question's scenario, you also need to store the state of FragmentStatePagerAdapter manually:
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState
.putParcelable("pages",homeActivityFragmentPageAdapter.saveState());
super.onSaveInstanceState(savedInstanceState);
}
Then you can retrieve the state in oncreate:
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
homeActivityFragmentPageAdapter.restoreState(savedInstanceState.getParcelable("pages"),this.getClassLoader());
welcomeFragment = (WelcomeFragment) homeActivityFragmentPageAdapter.instantiateItem(mViewPager, POSITION_HOME);
}
else { //simply create a new instance here}
homeActivityFragmentPageAdapter.addFragmentToAdapter(welcomeFragment);
homeActivityFragmentPageAdapter.notifyDataSetChanged();
}
I know this question is very common and I have read so many different answers but none fits in my problem. In my application, I have an activity and in rhye activity I load a fragment. I also send some data(in the form of Bundle) to the fragment. So my Problem is when the screen is rotated, I save the fragment in onSaveInstanceState Activity method and check in onCreate Method weather savedInstance is null or not and on that basis I load the fragment.
Activity code :
#Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
super.onSaveInstanceState(outState, outPersistentState);
outState.putParcelable(Const.TAG_REQ_CUSTOM,DetailsItems);
outState.putString(Const.TAG_FLOW, Const.TAG_MAIN_FLOW);
getSupportFragmentManager().putFragment(outState,"current_fragment",fragment);
}
onCreate Method :
if (findViewById(R.id.fragment_frame) != null) {
if (savedInstanceState != null) {
// this invoke when screen rotate but the app crash
DetailsItems = savedInstanceState.getParcelable(Const.TAG_REQ_CUSTOM);
String flow = savedInstanceState.getString(Const.TAG_FLOW);
ft = getSupportFragmentManager().getFragment(savedInstanceState,"current_fragment");
mFragmentManager=getSupportFragmentManager();
mFragmentTransaction = mFragmentManager.beginTransaction();
bundle= new Bundle();
bundle.putString(Const.TAG_FLOW, flow);
bundle.putParcelable(Const.TAG_REQ_BOOKING_DETAILS, bookingDetailsItems);
ft.setArguments(bundle);
mFragmentTransaction.replace(R.id.fragment_frame, ft).commit();
}else{
// load fragment on first time
}
}
So my Question is: Where do I have to save the custom Object(in parent Activity or in fragment) ?
When my saved Instance is not null than app crashesh and logs is :
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Class java.lang.Object.getClass()' on a null object reference
You should use ViewModel. ViewModel is specifically made for this purpose.
From the docs:
ViewModel is a class that is responsible for preparing and managing the data for an Activity or a Fragment. It also handles the communication of the Activity / Fragment with the rest of the application (e.g. calling the business logic classes).
use this code in Activity :
if (findViewById(R.id.fragment_frame) != null) {
if (savedInstanceState != null) {
fragment =getSupportFragmentManager().getFragment(savedInstanceState,"current_fragment");
}else{
// load fragment on first time
}
}
and in fragment :
//save custom object
#Override
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
outState.putParcelable("key",customObject);
}
//now retrieve here
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null)
customObject= savedInstanceState.getParcelable("key");
}
Take a look at onRetainNonConfigurationInstance() and getLastNonConfigurationInstance()
From docs:
Called by the system, as part of destroying an activity due to a configuration change, when it is known that a new instance will immediately be created for the new configuration. You can return any object you like here, including the activity instance itself, which can later be retrieved by calling getLastNonConfigurationInstance() in the new activity instance.
As an alternative to an Intent, i'm saving data in a retained headless Fragment during Activity re-creation (my saved object can be pretty large and it wouldn't fit the size limit of an Intent, and i think this is a faster approach than serializing-deserializing into JSON for example).
I've got the idea from this Google documentation, although my implementation is a bit different.
The Fragment:
public class DataFragment extends Fragment {
private Data data;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
public void setData(Data data) {
this.data = data;
}
public Data getData() {
return data;
}
}
I save my data to this Fragment in the onSaveInstanceState() method of my Activity:
#Override
protected void onSaveInstanceState(Bundle outState) {
FragmentManager fm = getSupportFragmentManager();
dataFragment = (DataFragment) fm.findFragmentByTag(TAG_DATA);
if (dataFragment == null) {
dataFragment = new DataFragment();
fm.beginTransaction().add(dataFragment, TAG_DATA).commitNow();
}
dataFragment.setData(myData);
super.onSaveInstanceState(outState);
}
And the relevant part of onCreate():
Data data;
FragmentManager fm = getSupportFragmentManager();
dataFragment = (DataFragment) fm.findFragmentByTag(TAG_DATA);
if (dataFragment == null) {
// the Fragment is not attached, fetching data from DB
DatabaseManager dbm = DatabaseManager.getInstance(this);
data = dbm.getData();
} else {
// the Fragment is attached, fetching the data from it
data = dataFragment.getData();
fm.beginTransaction().remove(dataFragment).commitNow();
}
This works flawlessly on orientation changes.
The problem is, sometimes, when my app is in the background and i'm returning to it, dataFragment.getData() returns null.
In other words, in the following line in onCreate() sometimes data is null:
data = dataFragment.getData();
How is this possible?
It does not throw a NullPointerException, so dataFragment is not null for sure.
Why did its initialized instance variable became null?
What you experience is PROCESS DEATH.
Technically it's also called "low memory condition".
The retained fragment is killed along with the application, but the FragmentActivity recreates your retained fragment in super.onCreate(), so you'll find it by its tag but the data in it won't be initialized.
Put the app in background then press the red X in the bottom left in Android Studio to kill the process. That recreates this phenomenon.
NOTE: After AS 4.0, if you launch your app from AS, then "Terminate" will trigger Force Stop (which does not produce this phenomenon). But if you launch your app from LAUNCHER on the phone after that, then you'll get this phenomenon.
if activity is recreated after it was previously destroyed, you are able to saved your state from the Bundle that the system passes your activity. Both the onCreate() and onRestoreInstanceState() callback methods receive the same Bundle that contains the instance state information.
Obviously yours onCreate() method is called whether the system is creating a new instance of your activity or recreating a previous one, you need to check whether the state Bundle is null before you attempt to read it. If it is null, then the system is creating a new instance of the activity, instead of restoring a previous one that was destroyed.
in oncreate():
Data data;
FragmentManager fm = getSupportFragmentManager();
dataFragment = (DataFragment) fm.findFragmentByTag(TAG_DATA);
**if (dataFragment.getData()== null) {**
// the Fragment is not attached, fetching data from DB
DatabaseManager dbm = DatabaseManager.getInstance(this);
data = dbm.getData();
} else {
// the Fragment is attached, fetching the data from it
data = dataFragment.getData();
fm.beginTransaction().remove(dataFragment).commitNow();
}
I have an Activity called BaseActivity, hosting multiple fragments.
The BaseActivity has a public field Object owhich is accessed by the fragments by calling Object o = ((BaseActivity) getActivity()).o; This is initialized in the fragments onCreate.
This works but I have problems with runtime configuration changes. It seems that the Fragments onCreate is called before the BaseActivitys onCreate, so I cant retain the Object from the Bundle i saved in onSaveInstanceState.
Is there a way I can make sure the acitivty can retain its object from the saved Bundle before the Fragment tries to access it?
Try this ..
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Object o = ((BaseActivity) getActivity()).o;
}
This makes sure that the activity's onCreate has completed executing.
i have a listFragment this is my onActivityCreated:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
utenti=savedInstanceState.getParcelableArrayList("array");
}else{
threadutenti= (GetUtenti) new GetUtenti(this.getActivity(), utenti).execute();
}
When i rotate my device i have first savedInstanceState non null (i got it) but after onActivityCreated is called with onActivityCreated null!! why? i want get my object utenti on ListFragment and not on my activity..
This can happen if you're always creating a new fragment in your Activity#onCreate() callback.
If it's true, check this answer. Your Fragment#onActivityCreated will be called twice:
First time triggered by FragmentActivity#onStart's call to mFragments.dispatchActivityCreated(). It will pass the saved instance.
Second time triggered by FragmentActivity#onStart's call to mFragments.execPendingActions(). This time without saved instance (since it is being called in response to new Fragment addition).