使用 LiveDataReactiveStreams 将 Flowable 转换为 LiveData 时出现 NullPointerException

NullPointerException on turning Flowable into LiveData using LiveDataReactiveStreams

我正在尝试将 Flowable 转换为 LiveData,但无法正常工作:

Flowable:(在存储库中)

    fun searchMyObjectByName(query: String): Flowable<Array<MyObjectResponse>> {
        return rest.searchMyObjectByName(query)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
    }

LiveData:(在 ViewModel 中)

    private val _myObject = MutableLiveData<ArrayList<MyObject>>()
    val myObject: LiveData<ArrayList<MyObject>>
        get() = _myObject
   fun searchMyObjectByNameLiveData(query: String) {
        _myObject.value = LiveDataReactiveStreams.fromPublisher(repo.searchMyObjectByName(query).map { it -> responseToObject(it) }).value
    }

观察者:(在 Fragment@OnCreateView 中)

// should be empty at first and then restore the value ...
 val resultObserver = Observer<ArrayList<MyObject>> { result -> adapter.replace(result) } //l.46
 model.myObject.observe(viewLifecycleOwner, resultObserver)

搜索由用户触发:

model.searchMyObjectByNameLiveData(query)

我得到的 NPE 错误:

 E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.xxx.xxx, PID: 13700
    java.lang.NullPointerException: result must not be null
        at com.xxx.xxx.ui.search.SearchFragment$onCreateView$resultObserver.onChanged(SearchFragment.kt:46)
        at com.xxx.xxx.ui.search.SearchFragment$onCreateView$resultObserver.onChanged(SearchFragment.kt:26)
        at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
        at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:149)
        at androidx.lifecycle.LiveData.setValue(LiveData.java:307)
        at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
        at com.xxx.xxx.ui.search.SearchViewModel.searchMedicByNameTest2(SearchViewModel.kt:56)
        at com.xxx.xxx.ui.search.SearchFragment.startSearching(SearchFragment.kt:137)
        at com.xxx.xxx.ui.search.SearchFragment$onCreateOptionsMenu.onQueryTextChange(SearchFragment.kt:81)
        at androidx.appcompat.widget.SearchView.onTextChanged(SearchView.java:1187)
        at androidx.appcompat.widget.SearchView.onTextChanged(SearchView.java:1725)
        at android.widget.TextView.sendOnTextChanged(TextView.java:10631)
        at android.widget.TextView.handleTextChanged(TextView.java:10721)
        at android.widget.TextView$ChangeWatcher.onTextChanged(TextView.java:13477)
        at android.text.SpannableStringBuilder.sendTextChanged(SpannableStringBuilder.java:1267)
        at android.text.SpannableStringBuilder.replace(SpannableStringBuilder.java:576)
        at android.text.SpannableStringBuilder.replace(SpannableStringBuilder.java:507)
        at android.text.SpannableStringBuilder.replace(SpannableStringBuilder.java:37)
        at android.view.inputmethod.BaseInputConnection.replaceText(BaseInputConnection.java:869)
        at android.view.inputmethod.BaseInputConnection.setComposingText(BaseInputConnection.java:636)

如果我直接观察这个方法,它是有效的:

 val resultObserver = Observer<ArrayList<MyObject>> {result -> adapter.replace(result)} 
 model.searchMyObjectByNameLiveData("query").observe(viewLifecycleOwner, resultObserver)

但这不是我想要的,因为此时我没有用户的输入。

感谢您的帮助。

编辑:

碎片中的听众

override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        inflater.inflate(R.menu.options_menu, menu)
        super.onCreateOptionsMenu(menu, inflater)
        Timber.i("onCreateOptionsMenu")

        searchView = SearchView((context as MainActivity).supportActionBar!!.themedContext)
        menu.findItem(R.id.search).apply {
            setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW or MenuItem.SHOW_AS_ACTION_IF_ROOM)
            actionView = searchView
        }

        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
            override fun onQueryTextSubmit(query: String): Boolean {
                return false
            }

            override fun onQueryTextChange(query: String): Boolean {
                when (newText.length) {
                    in 0..2 -> adapter.clear();
                    else -> {
                        model.searchMyObjectByNameLiveData(query)
                    }
                }
                return false
            }
        })
    }

编辑#2:

    LINENUMBER 47 L0
    ALOAD 0
    GETFIELD com/xxx/xxx/ui/search/SearchFragment$onCreateView.this[=18=] : Lcom/xxx/xxx/ui/search/SearchFragment;
    INVOKEVIRTUAL com/xxx/xxx/ui/search/SearchFragment.getAdapter ()Lcom/xxx/xxx/adapter/AdapterMedicSearch;
    ALOAD 1
    DUP
    LDC "result"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue (Ljava/lang/Object;Ljava/lang/String;)V
    INVOKEVIRTUAL com/xxx/xxx/adapter/AdapterMedicSearch.replace (Ljava/util/ArrayList;)V
   L1

我说,问题如下:

val resultObserver = Observer<ArrayList<MyObject>> { result -> adapter.replace(result) } //l.46

结果好像是null.

只需按照从按钮到顶部的堆栈跟踪

java.lang.NullPointerException: result must not be null
    at com.xxx.xxx.ui.search.SearchFragment$onCreateView$resultObserver.onChanged(SearchFragment.kt:46)
    at com.xxx.xxx.ui.search.SearchFragment$onCreateView$resultObserver.onChanged(SearchFragment.kt:26)
    at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
    at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:149)
    at androidx.lifecycle.LiveData.setValue(LiveData.java:307)
    at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
    at com.xxx.xxx.ui.search.SearchViewModel.searchMedicByNameTest2(SearchViewModel.kt:56)

好像调用了resultObserver的onChange,lambda抛出NPE,因为result数据是null.

如果添加 if != null check?.

会发生什么

总而言之,我想说这是一个竞争条件,需要适当的同步,但如果没有完整的代码,就很难说出为什么第一个值是 null。这可能是一个竞争条件的暗示,调试它时,它会工作,因为时间改变了。

更新:NPE

我无法重现您的问题,但是当与 Java 互操作时,可能会发生 NPE

class ReactiveX {
    @Test
    fun nullValue() {
        val mutableLiveData = MutableLiveData<List<MyObject>>()

        Handler(Looper.getMainLooper()).post {
            mutableLiveData.value = null
        }

        val resultObserver = Observer<List<MyObject>> { result: List<MyObject>? ->
            val returnedVal: List<MyObject> = Adapter.replace(result)
        }
        mutableLiveData.observeForever(resultObserver)
    }
}

internal data class MyObject(private val s: String)

public class Adapter {
    static List<MyObject> replace(List<MyObject> result) {
        return result;
    }
}

NPE

java.lang.NullPointerException: Adapter.replace(result) must not be null
    at com.example.playgroundapp.ReactiveX$nullValue$resultObserver.onChanged(ReactiveX.kt:19)
    at com.example.playgroundapp.ReactiveX$nullValue$resultObserver.onChanged(ReactiveX.kt:9)

解决方法: 在调用 #replace.

之前可能会检查 null

为什么我认为这是竞争条件?

嗯,我没想到,NULL 是一个有效值。我虽然在访问 LiveData 时它应该总是 return 一个值(而不是 null

@Test
fun fromCallableNotCalledWithoutObserve() {
    val subscribeActualCalled = AtomicBoolean(false)
    val sync = Flowable.fromCallable {
        subscribeActualCalled.compareAndSet(false, true)
        42
    }

    val fromPublisher = LiveDataReactiveStreams.fromPublisher(sync)
    val value = fromPublisher.value

    assertThat(value).isNull()
    assertThat(subscribeActualCalled.get()).isFalse()
}

fromCallableNotCalledWithoutObserve 显示,如果您不调用任何订阅方法,value 将始终为空。

@Test
fun valueStillNull() {
    val initValue = AtomicInteger(-1)
    val subscribeActualCalled = AtomicBoolean(false)
    val flowable = Flowable.fromCallable {
        subscribeActualCalled.compareAndSet(false, true)
        Thread.sleep(1000)
        42
    }.subscribeOn(Schedulers.io())

    val fromPublisher = LiveDataReactiveStreams.fromPublisher(flowable)
    val value = fromPublisher.value

    fromPublisher.observeForever {
        initValue.compareAndSet(-1, it ?: 666)
    }

    assertThat(value).isNull()
    assertThat(subscribeActualCalled.get()).isTrue()
    assertThat(initValue.get()).isEqualTo(-1)
}

valueStillNull 显示在没有正确的#observe* 方法的情况下访问 LiveData 的值将导致默认值,在本例中为 null.

这段代码有什么作用?

   fun searchMyObjectByNameLiveData(query: String) {
        _myObject.value = LiveDataReactiveStreams.fromPublisher(repo.searchMyObjectByName(query).map { it -> responseToObject(it) }).value
    }

它实际上设置了_myObject.value = null,因为

@Nullable
public T getValue() {
    Object data = mData;
    if (data != NOT_SET) {
        //noinspection unchecked
        return (T) data;
    }
    return null;
}

如果不对 returned LiveData 进行适当的观察*,将没有任何价值。因此,您将 LiveData _myObject 设置为 null。

 val resultObserver = Observer<ArrayList<MyObject>> { result -> adapter.replace(result) } //l.46
 model.myObject.observe(viewLifecycleOwner, resultObserver)

使用 null 调用 resultObserver 并失败。

为什么这样做?

val resultObserver = Observer<ArrayList<MyObject>> {result -> adapter.replace(result)} 
 model.searchMyObjectByNameLiveData("query").observe(viewLifecycleOwner, resultObserver)

您正在通过 LiveData#observe* 订阅 Flowable。该值被接收并推送到 resultObserver。因为该值不是 null,所以您不会获得 NPE。

听起来不错?