0

Android Studio 4

RxJava2, MVVM.

Approach#1

In my activity:

 val dispose = filmsRxJavaViewModel.filmsListSingle
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .doOnSubscribe( {// call before .doOnTerminate()
                Debug.d(TAG, "initLogic: doOnSubscribe:")
                binding.progressBarLayout.containerProgressBarLayout.setVisibility(View.VISIBLE)
            })
            .doOnTerminate({// call before .subscribe()
                Debug.d(TAG, "initLogic: doOnTerminate:")
                binding.filmsSwipeRefreshLayout.isRefreshing = false
                binding.progressBarLayout.containerProgressBarLayout.setVisibility(View.GONE)
            })
            // Only after call subscribe() then execute network request
            .subscribe({ filmsList -> // success
                Debug.d(TAG, "initLogic: subscribe: success")
                // Hide swipe to refresh icon animation
                if (filmsList.size > 0) { // list<Film>
filmItemListDataBindingAdapter!!.updateData(filmsList)
                } else {
                    binding.filmsRecyclerView.visibility = View.GONE
                    binding.emptyLayoutContainer.root.visibility = View.VISIBLE
                }
            }, {// error
                    throwable ->
                Debug.e(TAG, "initLogic: subscribe: error = $throwable")
                Toast.makeText(this, throwable.message, Toast.LENGTH_LONG).show()
            })

in my ViewModel:

class FilmsRxJavaViewModel(application: Application) : AndroidViewModel(application) {
    companion object {
        private val TAG = FilmsRxJavaViewModel::class.java.name
    }

    lateinit var filmsListSingle: Single<List<Film>>

    init {
        Debug.d(TAG, "init:")
        loadData()
    }

    fun loadData() {
        Debug.d(TAG, "loadData:")
        filmsListSingle = TransportServiceRxJava.getFilms()
    }

}

And my Transport and Retrofit interface

fun getFilms() : Single<List<Film>> {
            return testRxJavaRestClient.getFilms()
        }

import io.reactivex.Single
import retrofit2.http.GET

interface TestRxJavaRestClient {

    @GET("films")
    fun getFilms(): Single<List<Film>>

}

And as result it's work. Nice... but the problem with this approach that Activity know about switch between threads.

So I use another approach.

Approach#2

val dispose = filmsRxJavaViewModel.filmsListSingle
            .doOnSubscribe( {// call before .doOnTerminate()
                Debug.d(TAG, "initLogic: doOnSubscribe:")
                binding.progressBarLayout.containerProgressBarLayout.setVisibility(View.VISIBLE)
            })
            .doOnTerminate({// call before .subscribe()
                Debug.d(TAG, "initLogic: doOnTerminate:")
                binding.filmsSwipeRefreshLayout.isRefreshing = false
                binding.progressBarLayout.containerProgressBarLayout.setVisibility(View.GONE)
            })
            // Only after call subscribe() then execute network request
            .subscribe({ filmsList -> // success
                Debug.d(TAG, "initLogic: subscribe: success")
                // Hide swipe to refresh icon animation
                if (filmsList.size > 0) { // list<Film>
filmItemListDataBindingAdapter!!.updateData(filmsList)
                } else {
                    binding.filmsRecyclerView.visibility = View.GONE
                    binding.emptyLayoutContainer.root.visibility = View.VISIBLE
                }
            }, {// error
                    throwable ->
                Debug.e(TAG, "initLogic: subscribe: error = $throwable")
                Toast.makeText(this, throwable.message, Toast.LENGTH_LONG).show()
            })

In my ViewModel:

fun loadData() {
        Debug.d(TAG, "loadData:")
        filmsListSingle = TransportServiceRxJava.getFilms()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
    }

As you can see switch between threads I move to viewModel.

I think this is a correct way. Only viewModel must need about switch between threads. As result my Activity work only with view widgets (e.g. show toast).

Is this a correct way?

Alexei
  • 14,350
  • 37
  • 121
  • 240

1 Answers1

1

Putting the heavy Rx logic inside the ViewModel is the way to go.

This is for testability: the Fragments and Activities are heavyweight and difficult to test. By contrast, the ViewModel is amenable to testing.

So we make the Fragment and Activities "stupid" following the humble object pattern.

The Fragment and Activities should merely observe changes to a ViewModel's model that is a bag of primitives. This is done by the ViewModel exposing LiveData. For transient changes to the View, like a Toast, the Fragment or Activity can observe LiveData of a stream of events that expire when handled.

Please see the Android Architecture Blueprints and SingleLiveEvent here

Also, they are not "threads" in RxJava - they're schedulers.

David Rawson
  • 20,912
  • 7
  • 88
  • 124