Ofir Bar

    Ofir Bar

    2 years ago
    Hey guys, Can anyone please post a short code example for the part marked in red? I didn’t understand what the author meant by “Presenter” and “the same scope as the view model”. Of course I can search for that on Google but I would like to get a code example exactly for the context marked in Red Source: https://medium.com/androiddevelopers/viewmodels-and-livedata-patterns-antipatterns-21efaef74a54
    satyan

    satyan

    2 years ago
    In pseudo code:
    FeatureViewModel @Inject(
      private val subFeatureAPresenter: subFeatureAPresenter,
      private val subfeatureBPresenter: subfeatureBPresenter
    ) {
    
        fun onAction(action: Action) {
          if (action is related to subFeatureAPresenter) {
            val change = subFeatureAPresenter.action(action)
            // handle change
          }
          if (action is related to subFeatureBPresenter) {
            val change = subFeatureBPresenter.action(action)
            // handle change
          }
        }
    }
    Anastasia Finogenova

    Anastasia Finogenova

    2 years ago
    Consider moving your business logic to UseCase/Interactor instead. So your ViewModel will keep the presentation logic and the Interactor will have no knowledge of Android classes and be easily unit tested. Inject those Interactors into the ViewModel for better testability
    Samuel Michael

    Samuel Michael

    2 years ago
    class AddCreditCardPresenter @Inject constructor(
        private val interactor: AddCreditCardContract.Interactor
    ) : AddCreditCardContract.Presenter {
        private var view: AddCreditCardContract.View? = null
    
        init {
            interactor.setPresenter(this)
        }
    
        override fun onViewAttached(view: AddCreditCardContract.View, router: AddCreditCardContract.Router) {
            this.view = view
            interactor.onRouterAttached(router)
            interactor.onLifeCycleStarted { view as Fragment }
    
            if (view.isEmpty()) {
                interactor.loadData()
            }
        }
    
        override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
            interactor.onActivityResult(requestCode, resultCode, data)
        }
    
        override fun onViewDetached() {
            view = null
            interactor.onRouterDetached()
        }
    
        override fun onBackPressed() {
            interactor.onBackPressed()
        }
    
        override fun onDestroyed() {
            interactor.onDestroyed()
        }
    
        override fun onDataLoaded(dataModel: AddCreditCardDataModel) {
            view?.let { view ->
                view.addCreditCardNumber(dataModel.creditCardNumber)
                view.addExpiration(dataModel.expiryDate)
                view.addCvv(dataModel.securityCode)
                view.addNameOnCard(dataModel.nameOnCard)
    
                view.addSelectedAddress(SelectedAddressViewModel(dataModel.selectedAddress, this))
    
                dataModel.saveButton.buttonClickListener = Form(
                        mutableListOf(dataModel.creditCardNumber, dataModel.expiryDate, dataModel.securityCode, dataModel.nameOnCard)
                ) {
                    interactor.saveCard()
                }
    
                view.addSaveButton(dataModel.saveButton)
                view.showKeyboard()
                view.topmostViewLoaded()
            }
        }
    
        override fun hideSoftKeyboard() {
            view?.hideSoftKeyboard()
        }
    
        override fun showLoadingIndicator() {
            view?.showLoadingIndicator()
        }
    
        override fun hideLoadingIndicator() {
            view?.hideLoadingIndicator()
        }
    
        override fun onBillingAddressClick() {
            view?.hideKeyboard()
    
            interactor.goToSelectBillingAddress()
        }
    }
    satyan

    satyan

    2 years ago
    The interactor/use case shouldn’t contains the presenter instance and be aware of the view’s lifecycle. This is presentation logic. I think usecases / interactors should only have start/dispose event to handle their lifecycle and dispatch responses using either the concurrency method of your liking (coroutines, RXJava, callbacks on executors or whatever).
    Jeremy

    Jeremy

    2 years ago
    Yes or ideally just expose cold observables and avoid the start/dispose
    Ofir Bar

    Ofir Bar

    2 years ago
    In my project I am doing MVVM, as a junior dev all the terminiology I am aware of includes: LifeCycles(Activity/Fragment), ViewModel, Repositories and Models (data classes for POJO/POKO or REST data classes). I have never heard terms like: interactor, usecases, presenter, contract, cold observables with start/dispose. So I assume the terms mentioned here are a part of bigger architecture standard or something? can anyone point me as to where all these terms comes from? Is that relevant to MVVM or the terms assumed I used MVP? @satyan @Jeremy @Anastasia Finogenova @Samuel Michael
    Anastasia Finogenova

    Anastasia Finogenova

    2 years ago
    So when people say Presenter I think it is a term related to MVP, I don't think it is a standard term for MVVM. UseCase aka Interactor is coming from clean architecture. So it is a layered architecture pattern to achieve testability and decoupling. Both MVP and MVVM are takes on the clean architecture. Just Google what is usecase in clean architecture. For observables it is reactive pattern terms, people meant using RxJava but you don't have to. Interactor should not be aware of implementation details imo like rx or coroutines.
    Ofir Bar

    Ofir Bar

    2 years ago
    Thank you @Anastasia Finogenova
    Jeremy

    Jeremy

    2 years ago
    Yep. Observable is just a callback. In Kotlin you can just pass in a lambda if you want to avoid depending on a specific lib