Re-inflating view in handler.post when getView() is null

272 Views Asked by At

I recently replaced all the deprecated AsyncTask code in my apps with handlers and newSingleThreadExecutors. After retrieving response data from a remote server, I update the UI in the handler.post section of the code.

I've never personally been able to reproduce any problems with this, but on some devices (mostly oppo's, redmi's, vivo's, etc) under some real-world conditions, getView() returns null and my stop-gap attempt to re-inflate the view fails. The number of crashes has increased by a lot:

Exception java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object android.content.Context.getSystemService(java.lang.String)' on a null object reference

Rough outline of my code:

public class ResultFragment extends Fragment {
    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.result, container, false);
    }

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        Bundle bundle = this.getArguments();
        assert bundle != null;
        String query_url = bundle.getString("query_url");
        send_request(query_url);
    }

    void send_request(String... urls) {
        Handler handler = new Handler(Looper.getMainLooper());
        Executors.newSingleThreadExecutor().execute(() -> {
        .....
            handler.post(() -> {
                context = getContext();

                final TextView mTextView;
                final WebView mWebView;

                if (getView() != null) {
                    mTextView = getView().findViewById(R.id.count);
                    mWebView = getView().findViewById(R.id.result);
                } else {
                    View view = LayoutInflater.from(context).inflate(R.layout.result, null); <-- crash
                    mTextView = view.findViewById(R.id.count);
                    mWebView = view.findViewById(R.id.result);
                }

My understanding from the lifecycle documentation is that I should be able to get the view with this code. And I do understand that trying to re-inflate the code like this is a dangerous proposition (crashes might occur!). But how do I do so when getView() returns null?

As I say, I've never been able to replicate these crashes. So I'm open to trying anything that might work.

For general information, I'm targeting sdk version 33.

2

There are 2 best solutions below

2
Gabe Sechan On

You wouldn't do this at all. If you don't have a view, reinflating it isn't going to do what you expect. It would, at best, create a new set of views that are in memory only and not displayed on the screen. In other words it would be pointless.

Also, that's not what your problem is. You problem is that the context is null. Your fragment isn't attached to any. In this case, what you probably want to do is update any persisted state (if any) and skip updating the UI.

Also, if you're inflating your UI normally on an excutor that then posts to a handler- stop. THat's not how it works. THe inflation should happen in the onCreateView function. You can fill in the views like that, but you would NEVER inflate them like that.

1
elvisrusu On

Your code will crash if the fragment view (or the entire Fragment instance) is destroyed as a result of user leaving your app or screen. A quick fix for your issue is to cancel the handler runnable execution when the fragment view is destroyed.

@Override
public void onDestroyView() {
    super.onDestroyView();
    handler.removeCallbacksAndMessages(null);
}

For future development of your app you should look over more advance API to deal with this sort of issue. Ex: RxJava, Kotlin coroutines to name a few.

Small tip that will probably help you to reproduce the crash on any device/emulator - activate the developer option: "Don't keep activity", press the button that makes the network request and then immediately close the application. The thread will post a runnable that will execute after the fragment/view-fragment is destroyed -> NPE crash.