Nullify listener in Fragment onDetach()? - android

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.

Related

When Fragment onActivityCreated called

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.

Where is the best place to invoke removeOnTabSelectedListener

I am using TabLayout and according to the documentation, setOnTabSelectedListener is deprecated and we should use addOnTabSelectedListener(OnTabSelectedListener) and removeOnTabSelectedListener(OnTabSelectedListener) instead.
The question is : where do I have to invoke removeOnTabSelectedListener(OnTabSelectedListener), I would say onDestroy() callBack am I right?
Thats right, you can remove the listener in onDestroy() method or in onPause() method. But if you do that, dont forget to add the listener in onCreate() or in onResume().
Also, I must say is a unusual situation, regularly we dont have to remove the listener because when the activity is destroyed, it will be destroyed as well.

Is a fragment added by FragmentTransaction always accessible?

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.

Should all initialization be in onResume() instead of onCreate()?

Given that only onResume() is guaranteed to run and state is lost after onPause().
Should all initialization be in onResume() instead of onCreate()?
For example,
myDbHelper = new MyDbHelper(getApplicationContext());
It was in onCreate(). Should I move it to onResume()?
I have a listAdapter. Should it be created in onResume()?
For singletons, yes, they would be implemented differently see but what about other variables?
android docs
Now I'm working on something related to your topic. I strongly recommand you to reuse all your variables into onResume(). Becouse you can just finish() your Activity B and for that the only methode that is call in Activity A is onResume(). Also use null pattern object to avoid null pointer exception for your variables.
Not always - onResume() gets triggered when the activity temporarily lost focus (like some pop up event) when the onPause() event was triggered and then you again gain focus (thereby triggering an onResume()). In this case, if you had initialized variables in onResume() they would all be re-initialized at this point and you would lose prior values.
The best example where I can think of where you do stuff in onResume() is when you set up a BroadcastReceiver - you don't want the receiver to trigger something when an alert has come to the foreground (you get the general idea).

LoaderCallbacks.onLoadFinished not called if orientation change happens during AsyncTaskLoader run

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.

Categories

Resources