5

I am using MVVM architecture in my application and i want to make API requests from the activity's viewmodel class. The problem here is I am not getting the best way to do so. As viewmodel is already lifecycle aware for that activity so there is no need to make a separate viewmodel class for the API's? If this is so then should i fire the normal retrofit requests from the viewmodel class or what would be the best approach in this scenario?

What i was doing earlier without MVVM is this:

class UserViewModel : ViewModel() {

    private val cd = CompositeDisposable()
    val status: MutableLiveData<Boolean>? = MutableLiveData<Boolean>()


    val responseImages = MutableLiveData<ResponseImages>()
    fun getImages(text: String) {
        cd.add(
            RetrofitHelper.apiInstance.getImages(Site.METHOD, Site.KEY, text)
                .myApiSubscriber(status)
                .subscribe({
                    responseImages.postValue(it)
                }, {
                    it.printStackTrace()
                })
        )
    }


    private fun <T> Single<T>.myApiSubscriber(status: MutableLiveData<Boolean>?): Single<T> {
        return this.doOnSubscribe {
            status?.postValue(true)
//            Utils.debugger("PROGRESS ", " doOnSubscribe")
        }.doFinally {
            status?.postValue(false)
//            Utils.debugger("PROGRESS ", " doFinally")
        }.subscribeOn(Schedulers.io())
    }

    override fun onCleared() {
        cd.dispose()
        super.onCleared()
    }

    fun callCleared() {
        onCleared()
    }
}

so is the above way is still useful in case of MVVM or not and what's the best approach to follow with MVVM? Please suggest.

Cosmic Dev
  • 522
  • 6
  • 20
  • You can do it in different ways. I find the following article very clear and useful for 2021 MVVM - Retrofit supports "suspend functions", now so the calls are very easy (without the need for .myApiSubscriber(status).subscribe etc). Use LiveData in ViewModel and suspend fun (or Flow) in repositories: https://proandroiddev.com/no-more-livedata-in-your-repository-there-are-better-options-25a7557b0730 – Pavel Biryukov Oct 22 '21 at 10:58

1 Answers1

11

First, I strongly recommend reading Uncle Bob's SOLID Principles to understand why code is separated if you haven't already.

A common practice followed is to use a Repository Pattern as suggested in the Android Documentation. Here's the reference for the architecture: Android Architecture Components Reference

I've broken down what the role of each block is below:

Activity/Fragment : Here's where you do all your view related stuff, like initialising RecyclerView's, showing dialog's, fragment transactions, showing toast's etc. This is where, you will also register Observers for a MutableLiveData (that is present in your ViewModel)

ViewModel: The ViewModel contains all the business logic that is attributed to the View. It is not the responsibility of the ViewModel to initialise API calls. The reason is because, perhaps a scenario might occur where, you would require to further process the response and perhaps store it into a db or even fetch data from the DB in case of an error in the API.

What you can do is however, call the API using an instance of the repository which contains all the details of performing the API's. Once the response is fetched, assign the value to the livedata in your viewmodel which is observed by the Observer registered in the Fragment/Activity.

Repository: This is usually where all network operations and database operations are carried out. This allows for the responsibility of fetching all the data to be segregated away from the ViewModel.

Here's a quick brief example

class UserViewModel(private val imageRepository) : ViewModel() {

    //Not required since you're using a Single which uses a SingleObserver that  doesn't require to be disposed manually.
    private val cd = CompositeDisposable() 

    val responseImages = MutableLiveData<ResponseImages>()
    val showError = MutableLiveData<Boolean>()

    fun getImages(text: String) =
        imageRepository.getImages(text)
                .observerOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .subscribe({
                    responseImages.value = (it)
                }, {
                    showError.value = true
                })


    override fun onCleared() {
        cd.dispose()
        super.onCleared()
    }

    fun callCleared() {
        onCleared()
    }
}

//Note: you should ideally just pass the API instance. unless required. 
class ImageRepository(val retrofitHelper: RetrofitHelper){

    fun getImages(text:String): Single<ResponseImages> {
        return retrofitHelper.apiInstance.getImages(Site.Method,Site.key,text)
    }

}

//In your Activities onCreate()

class HomeActivity: AppCompatActivity(){

    override fun onCreate(bundle: SavedInstanceState?){
        viewModel.responseImages.observer(this,Observer {
            //do something with your ResponseImages
        }
    }
}

There are further things that could be done, for e.g., use a Strategy Pattern for your Repository where ImageRepository is an interface and ImageRepositoryImpl has all the details etc. But that is for another time!

The SunflowerApp by Google is a great reference :)

Rajiv Rajan
  • 151
  • 4
  • but i think the response should be received or observed in ViewModel class rather than in Activity as that's what the purpose of using MVVM is. There will be a repository class to do so. – Cosmic Dev Mar 15 '20 at 05:51
  • @CosmicDev the example I have shown above does that very thing. If you observe, the response is fetched within the viewmodel & in the onNext() of the SingleObserver, I assign the value (assuming no further transformations are necessary) to the responseImages LiveData. Hope that clears it out – Rajiv Rajan Mar 16 '20 at 12:28
  • @CosmicDev if you feel the answer helped you, or solved your question. It would be great if you could accept the answer! – Rajiv Rajan Mar 19 '20 at 18:45