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.
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.
@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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With