RxJava2 how to update existing subscription when r

2019-06-03 02:14发布

问题:

I have an activity on which I make a network request everytime the user input changes.

The api definition is as follows:

interface Api {
  @GET("/accounts/check")
  fun checkUsername(@Query("username") username: String): Observable<UsernameResponse>
}

Then the services that manages it all:

class ApiService {

  var api: Api

  init {
    api = retrofit.create(Api::class.java)
  }

  companion object {
    val baseUrl: String = "https://someapihost"
    var rxAdapter: RxJava2CallAdapterFactory = RxJava2CallAdapterFactory.create()
    val retrofit: Retrofit = Retrofit.Builder()
            .baseUrl(baseUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(rxAdapter)
            .build()


}

  fun checkUsername(username: String): Observable<UsernameResponse> {
    return api.checkUsername(username)
  }
}

Then inside my activity, whenever the EditText content changes, I make this call:

  private fun checkUsername(username: String) {
      cancelSubscription()
      checkUsernameDisposable = ApiService()
            .checkUsername(username)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe {
              updateUi(it)
      }
  }

So this is creating a new disposable every time the input changes. This is obviously incorrect. What I want to do is to update the existing subscription with the results of the new network call.

回答1:

First of all you're thinking right, creating an Observable for each change event is far from efficient.

There are 2 approaches to this:

One

You can use RxBinding to get a text change Observable, now you can flatMap the text changes to your apiService call, down to one disposable.

disposable = RxTextView.textChanges(editText)
    .switchMap { ApiService().checkUsername(it) }
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { updateUi(it) }

Two

You can use a Subject to use as a channel for the changes of the EditText like this:

val editTextChangesSubject: PublishSubject<String> = PublishSubject.create()

// when the editText changes call
editTextChangesSubject.onNext(newText)

disposable = editTextChangesSubject
        .switchMap { ApiService().checkUsername(it) }
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe { updateUi(it) }

Now that's also down to one disposable!

Note: People sometimes tend to use the Subject technique if they're using a specific architecture pattern that separates the View logic from the middle man logic, if you're not bound by that, RxBinding is the way to go.

Also if worth mentioning, the two approaches will give you powers that didn't exist when subscribing for each text change event, like using flow control operators like debounce or onBackpressureLatest.

Edit:

Used switchMap instead of flatMap, see the difference in Here