android-How to resolve NullPointerException when use rxjava in android?

1. The purpose of this post

Sometimes, when you use rxjava to do something in android, you would get this error:

Caused by java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getString(int)' on a null object reference
       at com.bswen.w3.ui.main.forwarder.ForwarderFragment.a(SourceFile)
       at com.bswen.w3.ui.main.forwarder.e.a(SourceFile)
       at com.bswen.w3.ui.main.forwarder.b.accept(lambda)
       at io.reactivex.internal.observers.LambdaObserver.onNext(SourceFile)
       at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(SourceFile)
       at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(SourceFile)
       at e.a.l.b.b$b.run(SourceFile)
       at android.os.Handler.handleCallback(Handler.java:751)
       at android.os.Handler.dispatchMessage(Handler.java:95)
       at android.os.Looper.loop(Looper.java:154)
       at android.app.ActivityThread.main(ActivityThread.java:6816)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1565)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1453)

Fatal Exception: io.reactivex.exceptions.OnErrorNotImplementedException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getString(int)' on a null object reference
       at e.a.n.a.a$k.a(SourceFile)
       at e.a.n.a.a$k.accept(SourceFile)
       at io.reactivex.internal.observers.LambdaObserver.onError(SourceFile)
       at io.reactivex.internal.observers.LambdaObserver.onNext(SourceFile)
       at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(SourceFile)
       at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(SourceFile)
       at e.a.l.b.b$b.run(SourceFile)
       at android.os.Handler.handleCallback(Handler.java:751)
       at android.os.Handler.dispatchMessage(Handler.java:95)
       at android.os.Looper.loop(Looper.java:154)
       at android.app.ActivityThread.main(ActivityThread.java:6816)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1565)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1453)

2. The gradle dependency and the code

First, you should have these dependencies in your build.gradle:

dependencies {
    implementation "io.reactivex.rxjava2:rxandroid:2.0.2"
    implementation "io.reactivex.rxjava2:rxjava:2.2.0"
}

And use may use the rxjava in android like this:

 final Context context = this.getContext();//get the android text object
 Observable.create(new ObservableOnSubscribe<Boolean>() {
     @Override
     public void subscribe(ObservableEmitter<Boolean> emitter) throws Exception {
         emitter.onNext(doTheChange(b,context));//do the async job
     }
 }).subscribeOn(Schedulers.io()) // do the background job in the IO thread pool.
   .observeOn(AndroidSchedulers.mainThread()) //got the result, update the UI
   .subscribe(new Consumer<Boolean>() {
      @Override
      public void accept(Boolean aBoolean) throws Exception {
          doRealUpdateUI(b); //do update the UI
      }

In the doRealUpdateUI:

textView.setText(Html.fromHtml(
                String.format(getActivity().getString(R.string.test_string), //the NullPointerException line
                        String.valueOf(s.getNumber()))));

So there is a NullPointerException in the line that call getActivity().getString, when you call getString, the getActivity returns null.

3. The solution 1

Add the try-catch to your code when you do the subscription with rxjava:

 final Context context = this.getContext();//get the android text object
 Observable.create(new ObservableOnSubscribe<Boolean>() {
     @Override
     public void subscribe(ObservableEmitter<Boolean> emitter) throws Exception {
         emitter.onNext(doTheChange(b,context));//do the async job
     }
 }).subscribeOn(Schedulers.io()) // do the background job in the IO thread pool.
   .observeOn(AndroidSchedulers.mainThread()) //got the result, update the UI
   .subscribe(new Consumer<Boolean>() {
      @Override
      public void accept(Boolean aBoolean) throws Exception {
        try {
          doRealUpdateUI(b); //do update the UI
        }catch(Exception ex) {
            LogUtils.error("",ex); //log the error
        }
      }
  }

4. The solution 2

Add the onError implementation to your rxjava:

 final Context context = this.getContext();//get the android text object
 Observable.create(new ObservableOnSubscribe<Boolean>() {
     @Override
     public void subscribe(ObservableEmitter<Boolean> emitter) throws Exception {
         emitter.onNext(doTheChange(b,context));//do the async job
     }
 }).subscribeOn(Schedulers.io()) // do the background job in the IO thread pool.
   .doOnError(throwable -> LogUtils.error("",throwable)) //log the error.
   .observeOn(AndroidSchedulers.mainThread()) //got the result, update the UI
   .subscribe(new Consumer<Boolean>() {
      @Override
      public void accept(Boolean aBoolean) throws Exception {
          doRealUpdateUI(b); //do update the UI
      }

when subscribeOn() is used but the onError() is not, if an error occurs, it will be thrown on the subscribed Scheduler thread but the error stacktrace will have no reference to the place where you subscribed. This will make debugging extremely hard. To avoid the issue, use onError().