androidmemory-leaksrx-javarx-kotlinrx-binding

Rxjava, combineLatest with RxTextView memory leak


I'm trying to use combineLatest with several RxTextViews and I thought that I was disposing my Disposables properly but it looks like I'm still getting a memory leak.

val one = RxTextView.afterTextChangeEvents(one)
            val two = RxTextView.afterTextChangeEvents(two)
            val three = RxTextView.afterTextChangeEvents(three)
            val four = RxTextView.afterTextChangeEvents(four)
            val five = RxTextView.afterTextChangeEvents(five)

            val disposable = Observables.combineLatest(one, two, three, four, five) { oneEvent, twoEvent, threeEvent, fourEvent, fiveEvent ->
                 //combining happening with these strings: oneEvent.view().text.toString  }
                    .skip(1) 
                    .debounce(1000, TimeUnit.MILLISECONDS)
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe {
                        //network call
                    }
            compositeDisposable.add(disposable)
        }

And then in my onStop I dispose of it.

override fun onStop() {
    super.onStop()
    if (!compositeDisposable.isDisposed) {
        compositeDisposable.dispose()
    }
}

I read that if some Views are referenced in the onNext() method, then there’s a potential NullPointerException which is what I believe is happening. Here's my NPE I'm getting:

fatal Exception: java.lang.NullPointerException: view == null
       at com.jakewharton.rxbinding2.internal.Preconditions.checkNotNull(Preconditions.java:27)
       at com.jakewharton.rxbinding2.widget.RxTextView.afterTextChangeEvents(RxTextView.java:159)
       at MyFragment$setUpTextListeners$1.execute(MyFragment.kt:170)

Here's the fragment I'm using:

private lateinit var viewModel: MyViewModel
private lateinit var binding: FragmentMyThingsBinding

private val compositeDisposable = CompositeDisposable()

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

    binding = FragmentMyThingsBinding.inflate(inflater, container, false)
    viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
    binding.viewModel = viewModel

    //calls to kick off business logic

    return binding.root
}


override fun onStart() {
    super.onStart()

    setUpTextListeners()
}

override fun onStop() {
    super.onStop()
    if (!compositeDisposable.isDisposed) {
        compositeDisposable.dispose()
    }
}

private fun setUpTextListeners() {

    val one = RxTextView.afterTextChangeEvents(one)
    val two = RxTextView.afterTextChangeEvents(two)
    val three = RxTextView.afterTextChangeEvents(three)
    val four = RxTextView.afterTextChangeEvents(four)
    val five = RxTextView.afterTextChangeEvents(five)

    compositeDisposable.add(Observables.combineLatest(one, two, three, four, five) { oneEvent, twoEvent, threeEvent, fourEvent, fiveEvent ->
            //combine here using oneEvent.view().text.toString  }
            .skip(1)
            .debounce(1000, TimeUnit.MILLISECONDS)
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe {
                //network call
            })

    }
}

Solution

  • One of your views, that you pass to RxTextView.afterTextChangeEvents, is null. There is assert in that function, that checks incoming argument on null.

    And that problem is not connected with memory leaks.

    EDIT: Changing the synthetic view property to use binding.editText fixed the issue. (Discussion in comments)