Kotlin Android Extensions : java.lang.IllegalStateException: view must not be null - android

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.

Related

Android - kotlin: What is the best way to add a delay in a custom view?

I have a custom view let's call it CustomTV, and I need to call notifydatasetchanged after 5 seconds inside method loadDetails(). I know there are 3 ways to do this.
Using Handlers with postdelay. I cannot use this because this is not lifecycle aware safe. the main thread will still execute my Runnable even if there is no View, i.e. user goes the background causing a crash.
Using Observable.timer, I am not 100% sure how this can be done from a View. Where would I dispose the disposable safely?
disposable = Observable.timer(
1000L,
TimeUnit.MILLISECONDS,
AndroidSchedulers.mainThread()
).subscribe {
// myadapter.notifydatasetchanged()
}
Coroutines. Issue with this is that I am inside a view. Not sure if I need to have the scope of the fragment of the View or the View.
Is loadDetails() a method in the CustomTV class or in your fragment?
I would suggest using a coroutine in your fragment to update the child view.
Ideally you would push this type of business logic down into a view model and test it - that's probably out of scope for your question though
Regarding 1: I don't think it will cause a crash. The Runnable itself will keep the View reference alive. But this would keep your View alive longer than necessary. Handlers are dangerous when they try to use a Fragment's attached Activity/Context assuming they are not null, because it can be null at the time the Runnable is called.
I don't use Rx, so can't comment on that.
Regarding 3: I would make loadDetails a suspend function with internal delay() call. Then the Fragment or Activity that calls loadDetails() can use its own coroutine launched from its own lifecycleScope to call it. The Fragment can use launchWhenStarted if applicable.

UI Change in Back Ground Thread

i have a function and this function send http request in back Ground Thread
when request is excute and complete i have some line of code to make UI Change but when fragment pause the app crash Because the function cant make UI Change when fragment pause i dont now what i must do for solve this
val url = "BASE_DIR/example"
val client = OkHttpClient()
val request = Request.Builder().url(url).build()
val code = client.newCall(request).execute().code()
if (code == 200) {
fragment.requireActivity().runOnUiThread {
fragment.refresh.isRefreshing = false
}
Another way to send a message to the UI thread is with a Handler
Handler(Looper.getMainLooper()).post {
// do whatever
}
or you have the usual postDelayed methods etc. So you can update fragment without it needing to be attached to an Activity, which is probably better than just doing a null check on getActivity (the fragment might just be temporarily detached, so you still want to update its state so you don't "lose" the result).
If you don't mind losing the result, because you're treating the detached fragment as dead so it doesn't need to be updated, then the null check like in #rahat's answer is the simplest way to do it. Don't make it more complicated than it needs to be!
Try with
fragment.getActivity()?.runOnUiThread {
fragment.refresh?.isRefreshing = false
}
The issue is when the fragment is detached the requireActivity()
will throw IllegalStateException so since it is not associated with any activity, but getActivity() returns null if it is not associated with any activity, so with null safe call you can do it safely.

View.getContext return null, why?

I'm using WebView with javascript interface and sometimes when I call loadUrl on the webView, mWebView.getContext() return null.
How can a view have no Context ? Why ?
Is a view whitout context still used or reusable ?
What should I do when my view did not have a context ?
Most important, if the view has no context, will mWebView.post(new Runnable() { ... be executed ?
Is this code relevant ?
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
mWebView.loadUrl("javascript:...");
} else {
mWebView.post(new Runnable() {
public void run() {
mWebView.loadUrl("javascript:...");
}
});
}
2 common reasons of a null context to a view:
You're trying to get the context in the callback of an asynchronous (handler, network call) call but the activity/fragment is gone because of another action of the user (ex: pressed back). Therefore the view is detached and has no context anymore.
You have a memory leak somewhere and two or more instances of your activity and view hierarchy. Then something happen in the instance you're not refering to anymore but the view has lost the link to its context.
Regarding the handler.
I am not sure if the view has its own handler or if it uses the handler of the Activity it is attached to, you'd probably have to read the source to find out. However the question is not really relevant: if the view has no context, you have a bigger problem: it's not on the screen.
Regarding whether the code in 5. is relevant, you would need to answer that questions: Why don't you know on which thread your code is running?
When you know on which thread you are, and if it makes sense to you not to be on the main thread, then using a handler is a valid way to execute your code on the main. As well as Activity.runOnUiThread()
Just remember that a Handler's lifecycle is not tied to the activity. So you should clear the queue of messages and runnables when your activity/fragment pauses

Remove OnScrollChangedListener from ViewThreeObserver

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.

fragments, when am i "active"?

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.

Categories

Resources