In one of my Fragment
s, I am registering an OnFocusChangeListener
for an EditText
in onResume()
:
override fun onResume() {
super.onResume()
editText.setOnFocusChangeListener {
// do something here
}
}
I am registering the listener in onResume()
because if I would set it in an earlier lifecycle method, it would be triggered on every configuration change. Setting it in onResume()
makes sure that the focus that existed before a configuration change is already restored before the listener is registered, so the listener won't automatically fire after a configuration change / focus restore.
Now I fear that I am maybe registering this listener too late. So my question is: Can a user interaction already lead to focus for an element before or while onResume()
is executed? (That would mean, that I would loose this focus event, because I am setting up the listener during onResume()
). Or more general: is user interaction already possible while onResume()
is being executed? The Fragment documentation says about onResume()
:
Called when the fragment is visible to the user and actively running.
It's clear what "visible to the user" means, but what exactly means "actively running"? Does this already mean accepting user input? Or is user input first accepted after onResume()
has finished?
Focus restoration is done in the Activity's onRestoreInstanceState()
, which is done separately from when Fragment's restore their own View's state (that would be in the Fragment's onViewStateRestored()
).
As per the onRestoreInstanceState()
documentation, it is called between the Activity's onStart()
and onPostCreate()
(which runs prior to onResume()
and onPostResume()
- onPostResume()
is when Fragment's get their onResume()
callbacks).
This means that you are correct in that there is no callback available at the Fragment level prior to onResume()
where the focus is set correctly prior to that method being called.
That being said, yes, users can interact with a Fragment prior to it reaching the resumed state. For example, ViewPager2
(as well as ViewPager 1 when using BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
) keep the non-selected fragments (i.e., those not in the middle of the screen) in the STARTED
state. Through the power of multi-touch, users could drag a page over slightly and then use another finger to tap on a view that is partially visible. You'd see the same behavior if you use setMaxLifecycle()
with Lifecycle.State.STARTED
yourself (which is what those are doing under the hood) - the fragment is interactable, but not RESUMED.
In most general cases, however, if you're not using any of the above APIs, the Fragment lifecycle will generally match the Activity lifecycle. And an activity, as per the ActivityThread source code, does run its updates all in the same handleStartActivity()
method.
It should be noted that every time the activity is destroyed, you will get a callback to your OnFocusChangeListener
with hasFocus
of false
as the View is removed from the Activity (which always loses the View's focus). This happens after the state is saved, the View's focus state isn't actually lost, it is just something you already need to handle in your callback, usually by checking isStateSaved()
and ignoring focus loss after the state is saved and checking isRemoving()
if you are manually removing / replacing the Fragment (i.e., by doing a replace()
operation).
Given that you already will have to have logic in your listener to avoid handling the hasFocus
of false events post destruction, the 100% correct case for handling gaining focus would involve saving your own focused state (i.e., true or false for a specific view) in your saved instance state and only running your logic if the hasFocus
is changing from what you already have saved. This means that you'd restore your saved instance state earlier in the Fragment's lifecycle (say, in onViewStateRestored()
method that Fragments provide) and add your listener there. Then, your logic could safely ignore callbacks with the same focus:
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
// Restore your member variable of focus
focused = savedInstanceState?.getBoolean("HAS_FOCUS", false) ?: false
editText.setOnFocusChangeListener { _, hasFocus ->
if (focused == hasFocus) {
// ignore
return
}
focused = hasFocus
if (hasFocus) {
// We gained focus
} else if (!isStateSaved() && !isRemoving()) {
// We lost focus
}
}
}
Looking at FragmentManager
source code, performResume
call which triggers onResume
is executed immediately after the fragment is started (and onStart
is called):
https://android.googlesource.com/platform/frameworks/support/+/84448d71fda0a24ba5d60fe9368ac47b97564c88/fragment/src/main/java/androidx/fragment/app/FragmentManagerImpl.java#926
Starting the fragment is required for user interaction and no interactions can happen between onStart
and onResume
calls, since they can execute only on the same Main thread.
So, yes, no user input is possible before onResume
.
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