Before Google architecture component and LiveData I have not paid attention to onActivityCreated() callback.
I read about this here in SOF as well in documentation, and i still cannot understand the behavior.
From one of SOF answers:
As the name states, this is called after the Activity's onCreate() has
completed.
In which conditions onActivityCreated() called and when onActivityCreated() not called?
is it possible that onCreateView() called but onActivityCreated() not called?
It common practice to attach LiveData observers in onActivityCreated(), so i guess there are significant difference between onActivityCreated() and onCreateView()?
Although looking on the diagram from official Android docs seems like it onActivityCreated() is called always after onCreateView()(in terms of execution, not order) and there no diffrence?
Something confusing here.
UPDATE:
onActivityCreated() deprecated.
EDIT: According to Ian Lake on Twitter (see https://twitter.com/ianhlake/status/1193964829519667202), the fact that FragmentActivity attempts to dispatch onActivityCreated in onStart is irrelevant, because no matter what happens, the FragmentManager dispatches it anyway when going from onCreate to onStart.
case Fragment.CREATED:
// We want to unconditionally run this anytime we do a moveToState that
// moves the Fragment above INITIALIZING, including cases such as when
// we move from CREATED => CREATED as part of the case fall through above.
if (newState > Fragment.INITIALIZING) {
fragmentStateManager.ensureInflatedView();
}
if (newState > Fragment.CREATED) {
fragmentStateManager.createView(mContainer);
fragmentStateManager.activityCreated(); // <--
fragmentStateManager.restoreViewState();
So what I said below is actually wrong.
Apparently using onActivityCreated inside a Fragment is equivalent to using onViewCreated.
But this also means you shouldn't rely on onActivityCreated to know if your Activity was actually created, because it is called more times than when the Activity is actually created.
Fragments are confusing.
ORIGINAL ANSWER:
Is it possible that onCreateView() called but onActivityCreated() not called?
UPDATE: No, it's not possible.
ORIGINAL: Yes, in a FragmentPagerAdapter they use FragmentTransaction.detach / FragmentTransaction.attach, which causes the View to be destroyed, but the Fragment stays alive (stopped, but not destroyed).
In this case, .attach() runs onCreateView, but not onActivityCreated.
It's common practice to attach LiveData observers in onActivityCreated(), so i guess there are significant difference between onActivityCreated() and onCreateView()?
UPDATE: it doesn't matter, although onViewCreated is still clearer.
ORIGINAL: It's actually a bad practice, and should be done in onViewCreated, providing getViewLifecycleOwner() as the lifecycle owner.
Although looking on the diagram from official Android docs seems like it onActivityCreated() is called always after onCreateView() and there no diffrence?
UPDATE: Despite that the FragmentActivity only tries to dispatch it once, all Fragments always go through onActivityCreated, because that's just how the FragmentManager works.
ORIGINAL: It is not always called after onCreateView, in fact, it's more-so called "before onStart, but only once".
/**
* Dispatch onStart() to all fragments.
*/
#Override
protected void onStart() {
super.onStart();
mStopped = false;
if (!mCreated) {
mCreated = true;
mFragments.dispatchActivityCreated(); // <---
}
mFragments.noteStateNotSaved();
mFragments.execPendingActions();
// NOTE: HC onStart goes here.
mFragments.dispatchStart();
}
UPDATE: but apparently this doesn't really matter to the FragmentManager, because it goes CREATED -> ACTIVITY_CREATED -> STARTED either way.
Related
I have a quick question.
If I add a Fragment by:
getFragmentManager().beginTransaction().add(...)
will it be always accessible by:
getFragmentManager().findFragmentByTag(...)
assuming I will never call beginTransaction().remove ?
(I will only use hide() and show() transactions to manipulate its visibility)
I don't see why not , according to the documentation , it will be accessible , however make sure you don't call .replace() either.
Because .replace() is a like a sequence of .remove().add()
Also transactions do not get added to the back stack by default. You can:
transition.addToBackStack("TAG");
After that you can use its identifier without problem, if you want to refer to it later.
Sure, Fragment is always accessible as long as it is attached to the activity. Fragment gets detached as soon as onDetach is called.
onDetach Called when the fragment is no longer attached to its activity. This is called after onDestroy(), except in the cases where the fragment instance is retained across Activity re-creation (see setRetainInstance(boolean)), in which case it is called after onStop().
For visibility, you are using hide() and show(),
hide() and show() is only relevant for fragments which are already attached to activity.
In short, onDetach never gets called during hide() and show().A attached fragment never get detached from the activity during hide() and show().So ,it will be accessible.
Usually in my fragments I attach a listener in onAttach() and nullify the listener in onDetach().
Is setting the listener to null in onDetach() necessary?
Although I do it because it makes the code look more symmetric, it doesn't seem necessary since the fragment is already destroyed because onDestroyView() and onDestroy() were already called before according to the fragment's lifecycle.
Thanks in advance.
It makes sense if you want to notify the listener of an finished AsyncTask but you are not interested in the result if the Fragment is not attached anymore. In the onPostExecute you then check if the listener is still present and if so use it.
So yes, there is at least one use cases where it makes sense to set the listener to null.
A lot of threads ask the question how to run code or get the visibility of the fragment.
But I would like to know "why" the Fragments: onResume() and onStart() are 'not' visible to the user, although the documents state it should be visible to the user.
From: http://developer.android.com/reference/android/app/Fragment.html#Lifecycle
onStart() makes the fragment visible to the user (based on its containing activity being started).
onResume() makes the fragment interacting with the user (based on its containing activity being resumed).
http://developer.android.com/reference/android/app/Fragment.html#onResume()
and onStart() for that matter, clearly describe:
Called when the fragment is visible to the user and actively running. This is generally tied to Activity.onResume of the containing Activity's lifecycle.
While I clearly see no fragment, till after onResume has completed. Soo the question remains: 'why' is the fragment only 'visible' after onResume. and not from onStart as per doc?
Oh and the expand on this: I use no ViewPager.. Just a simple Activity-Fragment model.
Hope anyone has some intel on this...
[UPDATE*] I have added a sample project with one activity and one fragment showing the issue. For anyone to try ;-)
https://www.dropbox.com/s/08noqvmq7sjwppb/fragmentUserVisibilityTest.zip
Perhaps, you have a long running operation in your onStart() which happens in the main thread. In this case you won't see anything until this operation ends. Which happens by the time onResume is called. Consider using AsyncTask for long operations.
onStart() gets called when a fragment is about to be shown, onResume() is called when a fragment is about to be interactible. That's the same as for an Activity.
onStart() and onResume() would have been of no use, if they had been called after a fragment becomes visible/interactible.
i've been constantly frustrated by this and i can't find a good answer, so hoping someone here can offer guidance.
i have a fragment that uses AsyncTask quite extensively. i'm constantly plagued by bugs where the fragment calls getActivity(), which returns null. i assume these are happening because some method in the fragment are invoked before the activity is attached, or after it is detached.
what's the correct way to handle this in my code? i don't want to have this idiom littered all over the place,
Activity activity = getActivity();
if (activity != null) { // do something }
looking at the docs for Fragment, i can come up with many possible hooks to solve this: isDetached(), onActivityCreated(), onAttach(), isResumed(), and so on. what is the right combination?
EDIT:
A few people have suggested canceling tasks when paused, but this implies that the standard idiom,
new AsyncTask<...>.execute();
cannot be used. It implies that every exec'd AsyncTask needs to be tracked to completion, or canceled. I have simply never seen that in example code from Google or elsewhere. Something like,
private final Set<AsyncTask<?>> tasks = new HashSet<>;
...
AsyncTask<?> t = new AsyncTask<...>() {
...
public void onPostExecute(...) {
tasks.remove(this);
...
}
}
tasks.add(t);
t.execute();
...
#Override
public void onPause() {
for (AsyncTask<?> t: tasks) {
t.cancel();
}
tasks.clear();
}
Try to cancel your AsyncTasks in the onPause or onStop methods. That will prevent the onPostExecute from being called when the Fragment is not active anymore (getActivity() returns null).
Or you could check if the Fragment is attached by calling this.isAdded() in your Fragment.
I could not find a good solution. In summary, either use a Loader, or check that getActivity() does not return null before using it. I looked into using Loaders, but the pattern makes a lot of assumptions about the structure of the app and the nature of data retrieval that didn't work for me.
In terms of coordinating lifecycles, I keep onActivityCreated as a mental benchmark - it marks the point at which the underlying activity has finished its own onCreate. Prior to that I do not believe there is an activity to getActivity() from.
That get activity is returning null sounds like either you're calling getActivity() too early (i.e. before it is created) or too late (i.e. when it stopped interacting with the fragment). Stopping your tasks in onPause() would prevent getActivity from returning null since it would cut off the task once the fragment stopped interacting with the underlying activity becuase the activity itself was paused. I think waiting for onStop() may be too late since, if the task were to still be running when the underlying activity paused it may still reutrn null.
Using android-support-v4.jar and FragmentActivity (no fragments at this point)
I have an AsyncTaskLoader which I start loading and then change the orientation while the background thread is still running. In my logs I see the responses come through to the background requests. The responses complete and I expect onLoadFinished() to be called, but it never is.
As a means of troubleshooting, in the Manifest, if I set android:configChanges="orientation" onLoadFinished() gets called as expected.
My Activity implements the loader callbacks. In the source for LoaderManager.initLoader() I see that if the loader already exists, the new callback is set to the LoaderInfo inner object class but I don't see where Loader.registerListener() is called again. registerListener only seems to be called when LoaderManagerImpl.createAndInstallLoader() is called.
I suspect that since the activity is destroyed and recreated on orientation change and since it is the listener for callbacks, the new activity is not registered to be notified.
Can anyone confirm my understanding and what the solution so that onLoadFinished is called after orientation change?
Nikolay identified the issue - Thank you.
I was calling initLoader fron onResume(). The Android documentation states:
"You typically initialize a Loader within the activity's onCreate()
method, or within the fragment's onActivityCreated() method."
Read "typically" as a bit more emphatic than I did when it comes to dealing with configuration change life cycle.
I moved my initLoader call to onCreate() and that solved my problem.
I think the reason is that in FragmentActivity.onCreate() a collection of LoaderManagers is pulled from LastNonConfigurationInstance and in FragmentActivity.onStart() there is some start up work regarding Loaders and LoaderManagers. Things are already in process by the time onResume() is called. When the Loader needs instantiated for the first time, calling initLoader from outside onCreate() still works.
It's actually not the call to initLoader() in onCreate() that's fixing it. It's the call to getLoaderManager(). In summary, what happens is that when an activity is restarted, it already knows about the loaders. It tries to restart them when your activity hits onStart(), but then it hits this code in FragmentHostCallback.doLoaderStart()*:
void doLoaderStart() {
if (mLoadersStarted) {
return;
}
mLoadersStarted = true;
if (mLoaderManager != null) {
mLoaderManager.doStart();
} else if (!mCheckedForLoaderManager) {
mLoaderManager = getLoaderManager("(root)", mLoadersStarted, false);
// WTF: Why aren't we calling doStart() here?!
}
mCheckedForLoaderManager = true;
}
Since getLoaderManager() wasn't called yet, mLoaderManager is null. It therefore skips the first condition and the call to mLoaderManager.doStart().
You can test this by simply putting a call to getLoaderManager() in onCreate(). You don't need to call init / restart loaders there.
This really seems like a bug to me.
* This is the code path even if you aren't using fragments, so don't get confused by that.