when is onAttach invoked? - android

The android documentation says that The onAttach() callback is invoked when the fragment has been added to a FragmentManager and is attached to its host activity.
A piece of my code in the activity:
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragment_container);
if(fragment == null){
fragment = new CrimeFragment();
fm.beginTransaction().add(R.id.fragment_container, fragment).commit();
}
Log.d(MAIN_ACTIVITY_LOG_TAG, "onCreate(Bundle)");
}
...
A piece of my code in the fragment:
...
#Override
public void onAttach(Context context) {
super.onAttach(context);
Log.d(CRIME_FRAGMENT_LOG_TAG, "onAttach(Context)");
}
...
The log:
...
2022-08-03 12:36:42.512 26035-26035/com.bignerdranch.android.criminalintent D/main_activity_log_tag: onCreate(Bundle)
2022-08-03 12:36:42.514 26035-26035/com.bignerdranch.android.criminalintent D/crime_fragment_log_tag: onAttach(Context)
...
As you can see onAttach() isn't invoked when the fragment is added to the FragmentManager.
So when actually is onAttach() invoked?

Fragment transactions are asynchronous by default. They are not executed immediately but just scheduled for execution at a later point.
If you want to make your fragment transaction synchronous, change commit() to commitNow(). Most of the cases you don't really need that.

Related

Impossible to restore fragment

I can't restore my fragment !
I'm saving like :
#Override
public void onSaveInstanceState(Bundle savedInstanceState)
{
if(cameraFragment != null && cameraFragment.isAdded())
getSupportFragmentManager().putFragment(savedInstanceState, "cameraFrameLayout", cameraFragment);
super.onSaveInstanceState(savedInstanceState);
}
and restoring in onCreate(savedInstanceState) :
if(savedInstanceState == null)
cameraFragment = CameraFragment.newInstance();
else
cameraFragment = (CameraFragment) getSupportFragmentManager().findFragmentByTag("cameraFrameLayout");
When I change the device orientation I can successfully see that the onSaveInstanceState in called with my fragment but on the onCreate I have a null instance...
Txs for help !
You need to restore the saved fragment in onRestoreInstanceState
public void onRetoreInstanceState(Bundle inState){
cameraFragment = getFragmentManager().getFragment(inState,"cameraFrameLayout");
}
Also notice that getFragment is called to the FragmentManager instead if findFragmentByTag. Hope it helps!
Note: call super methods in onSaveInstanceState and onRestoreInstanceState overrides
In your fragment onCreate() method write setRetainInstance(true);
In the Activity that creates the fragment you should do something like this
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null)
{
// Display the fragment as the main content.
getFragmentManager().beginTransaction()
.replace(android.R.id.content, CameraFragment.newInstance())
.commit();
}
}
The android.R.id.content can be different in your case. This is where you want to load your fragment.

Which Fragment lifecycle methods we should commit FragmentTrasaction to avoid famous java.lang.IllegalStateException

I was wondering, what is the Fragment lifecycle methods, I should commit FragmentTransaction to avoid famous
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
According to http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html, it gives great tip, on how to avoid such exception, by commit FragmentTransaction
FragmentActivity
onCreate()
onResumeFragments()
onPostResume()
Fragment
???
However, how about Fragment? What is the suitable Fragment lifecycle we should commit our fragment? For instance, under very rare situation, I will get exception from Google Play Console crash report, while trying to commit Fragment in another Fragment's onCreate.
public class BuyPortfolioFragment extends Fragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final FragmentManager fm = this.getFragmentManager();
// Check to see if we have retained the worker fragment.
this.statusBarUpdaterFragment = (StatusBarUpdaterFragment)fm.findFragmentByTag(STATUS_BAR_UPDATER_FRAGMENT);
if (this.statusBarUpdaterFragment == null) {
this.statusBarUpdaterFragment = StatusBarUpdaterFragment.newInstance();
this.statusBarUpdaterFragment.setTargetFragment(this, 0);
// java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
fm.beginTransaction().add(statusBarUpdaterFragment, STATUS_BAR_UPDATER_FRAGMENT).commit();
} else {
statusBarUpdaterFragment.setTargetFragment(this, 0);
}
p/s I know I can avoid such exception by using commitAllowingStateLoss. I want to use it as last resource.
Fragment's lifecycle state not always matches Activity's. Fragment's method getFragmentManager() returns the FragmentManager of it's hosting Activity (unless it's a child Fragment, if so this method returns the child fragment manager of a hosting Fragment). You may never know in which state is Fragment's hosting Activity unless you make tracking code. So it's really possible that the transaction eventually may be committed after Activity onSaveInstanceState() was called.
I suggest using getChildFragmentManager() and deal with child fragments from fragments.
Or if your intention was really to control Activity Fragments, make accessors for controlling it's state, like
// Activity method
public void showSomeFragment() {
if (mFragmentTransactionsAllowed) {
// do transaction
}
}
// And track the boolean
#Override
protected void onCreate(Bundle b) {
super.onCreate(b);
// override on onCreate() in case if Activity object is reused and state was true
mFragmentTransactionsAllowed = true;
}
#Override
protected void onStart() {
super.onStart();
// override here so that if activity goes foreground but not yet destroyed
mFragmentTransactionsAllowed = true;
}
#Override
protected void onResume() {
super.onResume();
mFragmentTransactionsAllowed = true;
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mFragmentTransactionsAllowed = false;
}

Fragment: onCreate being called 4 times

I have a Working model of fragments, when i was debugging the code i saw that the Fragment onCreate is being called 4 times.
Below is my code:
MyFragmentActivity
class MyFragmentActivity extends FragmentActivity{
#Override
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction().replace(fragmentID, new MyListFragmentt())
.replace(detailFragmentID, new MyDetailFragment()).commit();
}
}
#Override
protected void onRestart() {
getSupportFragmentManager().beginTransaction().replace(detailFragmentID, new MyDetailFragment()).commitAllowingStateLoss();
}
}
MyDetailFragment.class
class MyDetailFragment extends Fragment{
// has method like oncreate(),onCreateView(),onSaveInstanceState()
}
How my oncreate of MyDetailFragment is called ? When i go to some other activity and come back and then tilt the device only then oncreate and onSaveInstanceState of MyDetailFragment is called multiple times.
How can i solve this, i have looked into few posts on SO but it says that we need use HIDE,Show methods and other things ? but What is the proper soultion to this ?
EDIT
When i am coming back from previous activity, my data in the MyDetailFragment needs to be refreshed.
Try this
MyDetailFragment fragment = new MyDetailFragment();
#Override
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction().replace(fragmentID, new MyListFragmentt())
.replace(detailFragmentID, fragment).commit();
}
}
#Override
protected void onRestart() {
if(fragment != null) {
getSupportFragmentManager().beginTransaction().replace(detailFragmentID, fragment).commitAllowingStateLoss();
}
}
i think ur recreating fragments multiple times, u do new MyListFragment everytime on onCreate function, call findFragmentByTag to get the existing fragment and set that, if null (first time) then create one
/here is some code mate, if this doesnt work and ur app has single fragment better to just create xml and have only a fragment tag in it, and set that xml in setContentView function*/
// declare following member variable
MyFragment _fragment;
// in onCreate function, call this method
private void setupFragment()
{
_fragment = (MyFragment)getFragmentManager().findFragmentByTag("MyFragment");
if(null == _fragment)
{
_fragment = new MyFragment();
}
// now do the fragment transaction
FragmentTransaction trans = getFragmentManager().beginTransaction();
trans.add(containerId, _fragment, "MyFragment"); // here tag is important
trans.commit();
}

how to wait until a fragment is removed

I have an activity with dynamic fragments in it. I need to run some code after a fragment is removed but remove(myFragment).commit() is executed asynchronously and i cant know when exactly the fragment is removed.Here is my code:
final FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.remove(myFragment).commit();
//wait until the fragment is removed and then execute rest of my code
From the documentation:
public abstract int commit ()
Schedules a commit of this transaction. The commit does not happen
immediately; it will be scheduled as work on the main thread to be
done the next time that thread is ready.
What if you use the fragment's onDetach method to call the activity and tell it its done?
class MyFrag extends Fragment {
private Activity act;
#Override
public void onAttach(Activity activity) {
act = activity;
}
#Override
public void onDetatch() {
act.fragGone();
}
}
And in the activity:
public void fragGone() {
//do something, update a boolean, refresh view, etc.
}
You could try using the onDetached() callback of the fragment. This will be called whenever it is removed from its Activity.
Use onAttach() to check when the fragment is attached to the Activity and use onDettach() to check when the fragment is dettached to the activity.
Using the onDettach() you can also check to update or not views, data, etc in this way:
#Override
public void onDetach() {
super.onDetach();
synchronized (mLock) {
mReady = false;
}
}

how i can break things with Fragments with setRetainInstance(true) and adding them to backstack?

the docs on setRetainInstance say :
This can only be used with fragments not in the back stack.
so I started playing with it.
I have one Activity with adds first frag A
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.content, new PackageFragment());
ft.commit
then from this frag I run a method from parent Activity which adds frag B to backstack
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.content, new OrderFragment());
ft.addToBackStack(null);
ft.commit();
then I create log msg from onCreate,onDestroy,onSaveInstanceState,onActivityCreated...etc
I try two versions of this process. Rotating the device on each fragment.
default
Everything is as expected. onCreate, onDestroy on fragments fire
setRetainInstance(true)
Everything is as expected?. onCreate, onDestroy on fragments dont fire
and all seems to work while fragments are in the backstack.. so why the docs say I shouldnt use it?
What are the scenarios where I might get in trouble?
thanks
Updated answer:
What are the scenarios where I might get in trouble?
When adding a Fragment to the back stack and passing a Bundle in the Fragment from onSaveInstanceState() to onCreateView() on configuration change. Calling setRetainInstance(true) will set the Bundle to null on configuration change.
(I'm not sure a developer would actually attempt this since using setRetainInstance(true) makes onSaveInstanceState() kind of redundant, but I didn't see the behaviour documented in the API docs so I wrote up this answer).
If both addToBackStack() and setRetainInstance(true) are called, setRetainInstance() partly alters the Fragment lifecycle method calls and parameter values on configuration changes, compared to calling only addToBackStack().
Specifically, in the test below, looking a differences between calling only addToBackStack() and calling setRetainInstance(true) as well, and seeing what happens on configuration change:
Calling addToBackStack() but not setRetainInstance(true);
onCreate() and onDestroy() are called.
a bundle passed from onSaveInstanceState() is received as a parameter in onCreateView().
Calling both addToBackStack() and setRetainInstance(true):
onCreate() and onDestroy() are not called. This is metioned in the API docs.
a bundle passed from onSaveInstanceState() is not received in onCreateView(). The passed-in Bundle is null.
A test with logged method calls and parameters tested for null:
In the Activity:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyFragment fragment;
if (savedInstanceState != null) {
fragment = (MyFragment) getFragmentManager().findFragmentByTag("my_fragment_tag");
} else {
fragment = new MyFragment();
FragmentTransaction t = getFragmentManager().beginTransaction();
t.addToBackStack(null);//toggle this
t.add(android.R.id.content, fragment, "my_fragment_tag").commit();
}
}
In the Fragment:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setRetainInstance(true);//toggle this
}
and
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putString("test", "value");
super.onSaveInstanceState(outState);
}
Test 1: Fragment lifecycle when addToBackStack() is called , and setRetainInstance(true) is not called
onAttach()
onCreate()
onCreateView()
onActivityCreated()
onStart()
onResume()
[Device rotated from portrait to landscape]
onPause()
onSaveInstanceState()
onStop()
onDestroyView()
onDestroy()
onDetach()
onAttach()
onCreate()
onCreateView() with bundle param != null
onStart()
onResume()
Test 2 & 3: Fragment lifecycle calls with setRetainInstance(true) called, addToBackStack() called / not called (same result):
onAttach()
onCreateView()
onActivityCreated()
onStart()
onResume()
[Device rotated from portrait to landscape]
onPause()
onSaveInstanceState()
onStop()
onDestroyView()
onDetach()
onAttach()
onCreateView() with bundle param == null
onStart()
onResume()

Categories

Resources