I have this code:
ViewThreeObserver observer = my_view.getViewTreeObserver();
observer.addOnScrollChangedListener(new OnScrollChangedListener() {
#Override
public void onScrollChanged() {
if(condition) {
//do something
}
}
});
Now I would remove listener on observer if condition is verified.
I've try with:
observer.addOnScrollChangedListener(null);
But I get an error that claim "ViewThreeObserver is not alive". What does it mean, and how I could remove listener correctly?
observer is a long-lived reference which has no guarantee to be valid for the lifetime of the view. Instead you can just call getViewTreeObserver on your view again and remove the listener (use removeOnScrollChangedListener as Ahmad mentioned).
my_view.getViewTreeObserver().removeOnScrollChangedListener(this);
Although this is a short-lived call, there is a potential of it being not alive so you could check isAlive on it beforehand (never experienced this myself).
You can also use isAlive on observer if you wanted to (most likely will not be alive) and use that to remove the listener. If observer is not alive you will need to call getViewTreeObserver anyway.
Quote for getViewTreeObserver
Returns the ViewTreeObserver for this view's hierarchy. The view tree
observer can be used to get notifications when global events, like
layout, happen. The returned ViewTreeObserver observer is not
guaranteed to remain valid for the lifetime of this View. If the
caller of this method keeps a long-lived reference to
ViewTreeObserver, it should always check for the return value of
isAlive().
I've seen many different variations of this here are a few:
Without checking isAlive
Checking isAlive on short-lived call
Use ViewTreeObserver#removeOnScrollChangedListener(...) instead of setting the listener to null.
I get an error that claim "ViewThreeObserver is not alive"
It's recommended that you check with ViewTreeObserver#isAlive() if the ViewTreeObserver is alive or not before removing the listener.
Related
I am using kotlin view binding in my fragment. In some cases app crashes with IllegalStateException & view as null, I am accessing it in a runnable which is called using a handler with a 1.5sec delay.
numberRunnable = Runnable {
if (mobileView.text.toString().isNotEmpty() && Utility.isMobileNumberValid(mobileView.text.toString())) {
presenter.getCustomerDetails(Utility.getServerAcceptableContactNumber(mobileView.text.toString()))
}
}
mobileView is null
Handler code:
handler.postDelayed(numberRunnable, 1500)
I am aware of one possibility to check if isAdded in my fragment, but since I cannot replicate the bug I am not sure if its the problem.
The action is likely running after the user leaves the Fragment and onDestroy() is called. In that state, there will be no View instances in the Fragment.
A simple workaround would be to create a global var to check your Fragment's created state. Set it to true in onViewCreated() and false in onDestroyView() (before the super call). Then check that value inside the Runnable before executing your logic.
A better solution (although this is subject to race conditions and needs every Runnable being assigned to a global variable) might be to use the Handler.removeCallbacks() method and pass all your Runnables.
override fun onDestroyView() {
handler.removeCallbacks(numberRunnable)
}
Yet another possibility is to simply say the View is nullable:
mobileView?.let {
//Your stuff goes in here
//You can reference mobileView using "it"
}
You cannot assume that after 1.5s the views are still attached to the view hierarchy.
Add handler.removeCallbacks(numberRunnable) to your onStop() life-cycle callback to remove the numberRunnable when the fragment is not active anymore.
Also ask yourself the question of why you need to have the delay.
I am working on an app which listens for View events like scrolling, layout drawn using ViewTreeObserver. ViewTreeObserver has a method to check if it's alive before doing anything eg. adding listeners.
I have to reproduce the issue of dead / not alive ViewTreeObserver to see If my code works well in production. I don't see anything in android documentation to reproduce it.
I appreciate any help / pointers.
Thanks
In fact, if you check the source code of class ViewTreeObserver, there is a "kill" function to set mAlive to false, also only here, but it is never invoked.
/**
* Marks this ViewTreeObserver as not alive. After invoking this method, invoking
* any other method but {#link #isAlive()} and {#link #kill()} will throw an Exception.
*
* #hide
*/
private void kill() {
mAlive = false;
}
In my opinion, this observer will become unavailable (but is not un-alive, you couldn't use isAlive() to determine current observer's state) after:
You removed listener(s), such as
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
The current activity containing the view is destroyed
If getViewTreeObserver is called before the view is attached, a variable mFloatingTreeObserver is returned. When the view is attached the first observer's listeners will be merged with the parent's listeners and the first getViewTreeObserver that you obtained will no longer be alive, as kill() is called in that merge method
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.
I have a LocalService that exposes a Binder with some APIs. I create a Service Listener, just like this:
if (dataServiceListener == null) {
dataServiceListener = new DataServiceListener();
mainActivity.getApplicationContext().bindService
(new Intent(mainActivity, LocalService.class),
dataServiceListener.svcConn, mainActivity.BIND_AUTO_CREATE);
}
After I call the method that the Binder in dataServiceListener exposes, I get the response in the dataServiceListener onResult() method. Up to this point, no kind of issues, everything is working.
Some sort of problem occurs when I close the Activity that is waiting for the Service Listener callback and immediately reopen it. Even though I re-instantiate the dataServiceListener in onCreate(), I get two callbacks instead of one, the old one from the destroyed Activity and the latter (right) one; this way the results mix up on the UI.
Is there a way to tell the Service or the Service Listener that when the activity finishes, the callbacks must be avoided. Or maybe even destroy the ServiceListener objects.
I think this is the issue that Mark L. Murphy (Commonsware) described in "The Busy Coder's Guide to Android Development":
The biggest catch is to make sure that the activity retracts the listeners when it is done.
How can I do this? Is there a way to get rid of the useless listeners when the activity finishes?
Thank you!
I had the same issue. I was working in a remote sevice using AIDL. I got this problem when i am trying do unregister my listeners using the remove method from ArrayList Collection inside a foreach loop, because I was not using asBinder in the comparision. Searching fora solution, I find out the RemoteCallbackList class in Android API. This class does exactly what i needed, and what i think you should do, on a easy way, taken all reponsabilites for the hard work that involves this task.
From the Android API:
To use this class, simply create a single instance along with your service, and call its register(E) and unregister(E) methods as client register and unregister with your service. To call back on to the registered clients, use beginBroadcast(), getBroadcastItem(int), and finishBroadcast().
Broadcast sample:
int i = callbacks.beginBroadcast();
while (i > 0) {
i--;
try {
callbacks.getBroadcastItem(i).somethingHappened();
} catch (RemoteException e) {
// The RemoteCallbackList will take care of removing
// the dead object for us.
}
}
callbacks.finishBroadcast();
The code you show is for binding to a service. You do not show where you are registering a listener with that service. You apparently are, based upon your question and your reference to an onResult() method. Given the nature of your problem, I am going to guess that what you're doing is:
Binding to the service in onCreate()
In onServiceConnected(), you are calling some sort of setListener() method on the Binder
In that case, if we ignore configuration changes, the proper way to unwind matters would be to, in onDestroy(), call some removeListener() method on the Binder, then call unbindService().
Configuration changes, particularly in a pre-fragment world, make this complicated. It's the reason why this sample project (and the accompanying material in the book) is so icky. Binding is twitchy -- if you unbind from the old activity, and nothing else is keeping the service around, the service will shut down before the new activity gets a chance to bind. Binding is also state -- you cannot simply fail to unbind, lest you leak stuff.
So, the recipe becomes:
Bind to the service in onCreate() using the Application Context
In onServiceConnected(), call sort of setListener() method on the Binder
In onRetainNonConfigurationInstance(), make note of the fact that you're undergoing a configuration change, and return some Object that has your Binder, your Listener, and all the rest of your state
In onCreate(), use getLastNonConfigurationInstance() -- if it is null, proceed as normal, but if it is not null, hold onto that Binder and Listener and don't re-bind and re-register the listener
In onDestroy(), if the flag from Step #3 above is false (i.e., we are not undergoing a configuration change), call some removeListener() method on the Binder, then call unbindService().
Using fragments with setRetainInstance(true) can probably simplify this some, though I have not worked through a sample for that yet.
I had this issue too. You need to release all the resources,listeners,threads from the service when it finishes.
Your activity has to register/unregister itself as the listener. You need to use the proper lifecycle callback methods, not onBackPressed(). Register onStart(), unregister onStop(). One way to do it is to make the listener a static member of your service, and provide static register/unregister methods. Then call those from your activity as appropriate.
I finally solved the issue (and no, I haven't been working on it for so long :D).
The callback to the listener was made before the Fragment's onDestroy was called. So the boolean "dontupdate" value was never set to false. Overriding onBackPressed in the main activity solved the problem, as I invoked a destroy() method for each fragment that takes care of setting the boolean value to false.
I want to do some cleanup in a view when the activity is being destroyed. Is there any way to get a callback in the View when the activity is being destroyed? I tried using onDetachedFromWindow, but I'm not sure whether it is correct thing to do.
If you want to get a callback without having to overload the View.onDetachedFromWindow() method, you may use the View.addOnAttachStateChangeListener() method, which takes a callback listener as a single parameter.
With the understanding that onDestroy is not guaranteed to be called, you can just callback into your view in the activities onDestroy method.
Edit in response to comment:
You can get any view by giving it an id in the layout and calling findViewById. Here's an example:
Layout.xml (only showing the bare minimum)
<LinearLayout>
<com.example.superwidget.DropDownTouchEnabledListView
android:id="#+id/special_list_view" />
</LinearLayout>
MyActivity.java (again, bare minimal and assuming proper imports)
#Override
void onDestroy() {
DownTouchEnabledListView v = (DownTouchEnabledListView)findViewById(R.id.special_list_view);
v.doCallback(with, parameters);
}
Since View contains Context, I use LocalBroadcastManager for this. So far, I haven't encountered any issues.
The View is listening to destroy event filter. The Activity then just need to broadcast destroy event.
// On View `setup` function
LocalBroadcastManager.getInstance(context).registerReceiver(receiver, filter)
// On Activity
override fun onDestroy() {
super.onDestroy()
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
}
Don't forget to unregister the event filter once the destroy event is triggered.
presenter.destroy() // Your clean-up code here
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
You can find more detail about LocalBroadcastManager here:
Blog post
Android Documentation