Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NullPointerException when implementing Java interface from Kotlin

I'm trying to implement BiFunction interface from RxJava in Kotlin and I'm getting a NullPointerException.

This is the Java interface that I'm implementing in Kotlin. It's from RxJava 2.

package io.reactivex.functions;

import io.reactivex.annotations.NonNull;

/**
 * A functional interface (callback) that computes a value based on multiple input values.
 * @param <T1> the first value type
 * @param <T2> the second value type
 * @param <R> the result type
 */
public interface BiFunction<T1, T2, R> {

    /**
     * Calculate a value based on the input values.
     * @param t1 the first value
     * @param t2 the second value
     * @return the result value
     * @throws Exception on error
     */
    @NonNull
    R apply(@NonNull T1 t1, @NonNull T2 t2) throws Exception;
}

This is my implementation

class MonitoringStateReducer: BiFunction<MonitoringViewState, MonitoringResult, 
    MonitoringViewState> {
    override fun apply(
        previousState: MonitoringViewState,
        result: MonitoringResult
    ): MonitoringViewState {
        when (result) {
           //Returns a non-null new state
        }
    }
}

And then, in the ViewModel, I try to use it, but it throws a NullPointerException.

2019-08-22 09:57:41.049 6925-6925/com.name.app E/AndroidRuntime: FATAL EXCEPTION: main Process: com.name.app, PID: 6925 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.name.app/com.name.app.features.monitoring.presentation.MonitoringActivity}: java.lang.NullPointerException: accumulator is null at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2907) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2986) at android.app.ActivityThread.-wrap11(Unknown Source:0) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1641) at android.os.Handler.dispatchMessage(Handler.java:105) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6694) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:769) Caused by: java.lang.NullPointerException: accumulator is null at io.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39) at io.reactivex.Observable.scanWith(Observable.java:11537) at io.reactivex.Observable.scan(Observable.java:11502) at com.name.app.features.monitoring.presentation.MonitoringViewModel.compose(MonitoringViewModel.kt:47) at com.name.app.features.monitoring.presentation.MonitoringViewModel.(MonitoringViewModel.kt:18) at com.name.app.features.monitoring.presentation.MonitoringViewModel_Factory.get(MonitoringViewModel_Factory.java:25) at com.name.app.features.monitoring.presentation.MonitoringViewModel_Factory.get(MonitoringViewModel_Factory.java:8) at dagger.internal.DoubleCheck.get(DoubleCheck.java:47) at com.name.app.di.viewmodel.ViewModelFactory.create(ViewModelFactory.kt:12) at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:164) at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:130) at com.name.app.features.monitoring.presentation.MonitoringActivity$viewModel$2.invoke(MonitoringActivity.kt:46) at com.name.app.features.monitoring.presentation.MonitoringActivity$viewModel$2.invoke(MonitoringActivity.kt:26) at kotlin.UnsafeLazyImpl.getValue(Lazy.kt:81) at com.name.app.features.monitoring.presentation.MonitoringActivity.getViewModel(Unknown Source:7) at com.name.app.features.monitoring.presentation.MonitoringActivity.bind(MonitoringActivity.kt:85) at com.name.app.features.monitoring.presentation.MonitoringActivity.onCreate(MonitoringActivity.kt:119) at android.app.Activity.performCreate(Activity.java:6984) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1235) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2860) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2986)  at android.app.ActivityThread.-wrap11(Unknown Source:0)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1641)  at android.os.Handler.dispatchMessage(Handler.java:105)  at android.os.Looper.loop(Looper.java:164)  at android.app.ActivityThread.main(ActivityThread.java:6694)  at java.lang.reflect.Method.invoke(Native Method)  at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:769)

class MonitoringViewModel @Inject constructor(
    private val processor: MonitoringProcessor
) : BaseViewModel<MonitoringIntention, MonitoringViewState>() {
    //Properties that are not relevant for the question

    private val reducer: MonitoringStateReducer = MonitoringStateReducer()

    private fun compose(): Observable<MonitoringViewState> {
        return intentsSubject.compose(intentFilter)
            .map(actionFromIntent)
            .compose(processor)
            .scan(MonitoringViewState.init(), reducer) //Exception is here
            .distinctUntilChanged()
            .replay(1)
            .autoConnect(0)
    }

    override fun state(): Observable<MonitoringViewState> = compose()

    //Functions that are not relevant for the question
}

This code doesn´t work either.

private val reducer by lazy(LazyThreadSafetyMode.NONE) {
    MonitoringStateReducer()
}

However, if I replace the reducer with this code, it works.

private val reducer: BiFunction<MonitoringViewState, MonitoringResult, MonitoringViewState>
    get() = MonitoringStateReducer()

Tested on Kotlin 1.3.40 and 1.3.50.

like image 282
GianMS Avatar asked Aug 21 '19 16:08

GianMS


2 Answers

The problem comes from the Kotlin class initialization order. The crash is due to the fact that BaseViewModel constructor is calling the state() method that is overridden in the child MonitoringViewModel class. As a result, when the reducer is accessed, it hasn't initialized yet. During construction of a new instance of a derived class, the base class initialization is done as the first step and it happens before the initialization logic of the derived class is run. Take a look at this article, which describes a pretty similar problem. Derived class initialization order Kotlin's documentation section should be useful too.

like image 64
Valeriy Katkov Avatar answered Sep 21 '22 23:09

Valeriy Katkov


@Valeriy Katkov's answer is correct, please accept his answer.

Here is a code simulating the same scenario:

fun main() {
    MonitoringViewModel()
}

open abstract class BaseViewModel {

    init {
        state()
    }

    abstract fun state()
}

class MonitoringViewModel: BaseViewModel() {

    private val reducer1: MonitoringStateReducer = MonitoringStateReducer()
    private val reducer2: BiFunction<MonitoringViewState, MonitoringResult, MonitoringViewState>
        get() = MonitoringStateReducer()

    override fun state() {
        Observable.just(MonitoringResult(3))
                .scan(MonitoringViewState(0), reducer1)
                .subscribe { state -> println(state.value) }
    }

}

data class MonitoringViewState(val value: Int)

data class MonitoringResult(val value: Int)

class MonitoringStateReducer : BiFunction<MonitoringViewState, MonitoringResult,
        MonitoringViewState> {
    override fun apply(
            previousState: MonitoringViewState,
            result: MonitoringResult
    ): MonitoringViewState {
        return MonitoringViewState(previousState.value + result.value)
    }
}

If you run this code with the scan operator using reducer1 you will see the exception:

Exception in thread "main" java.lang.NullPointerException: accumulator is null

If you run the code with scan operator using reducer2, it succeeds.

Why does it crash when using reduce1?

From @Valeriy Katkov's answer:

The crash is due to the fact that BaseViewModel constructor is calling the state() method that is overridden in the child MonitoringViewModel class. As a result, when the reducer is accessed, it hasn't initialized yet. During construction of a new instance of a derived class, the base class initialization is done as the first step and it happens before the initialization logic of the derived class is run.

like image 41
Gustavo Avatar answered Sep 21 '22 23:09

Gustavo