Android体系结构:从MVP到MVVM的旅程

Overchoice is a cognitive process in which people have a difficult time making a decision when faced with many options. Making a decision becomes overwhelming due to the many potential outcomes and risks that may result from making the wrong choice.

过度选择是一个认知过程,当人们面对许多选择时,他们很难做出决定。 由于做出错误的选择可能会带来许多潜在的结果和风险,因此决策变得不知所措。

The same principle is applicable to Android development as well. Today we have so many android architectures like MVC, MVP, MVVM, MVI, Clean Architecture and so on. It becomes very difficult for a developer to choose the best architecture as almost every architecture is mainly based on separation of concerns, i.e UI based classes like Activities/Fragments should only contain logic that handles UI and so on. So how to choose one from many ?

同样的原理也适用于Android开发。 今天,我们有许多Android架构,例如MVC,MVP,MVVM,MVI,Clean Architecture等。 开发人员很难选择最佳的架构,因为几乎每种架构都主要基于关注点的分离 ,即基于UI的类(例如Activity / Fragments)应该只包含处理UI的逻辑。 那么如何从众多中选择一种呢?

Image for post

If we do some research, we’ll find that MVP & MVVM are the most common architecture which is being used right now in the industry. Let’s discuss these two briefly with the help of below image.

如果我们做一些研究,我们会发现MVP和MVVM是目前行业中最常用的体系结构。 让我们借助下图简要讨论这两个。

Image for post
MVP Vs MVVM
MVP与MVVM

If we observe carefully, we will see that View & Model is there in both architectures. The only difference is Presenter is there in MVP and ViewModel is there in MVVM architecture. Now let’s see what is the difference between these two.

如果我们仔细观察,就会发现两种架构中都存在View&Model 。 唯一的区别是MVP中Presenter ,而MVVM体系结构中有ViewModel 现在,让我们看看这两者之间有什么区别。

  1. Presenter keeps reference to Views which it uses to callback view’s methods like showProgress(), hideProgress(), showErrorMessage() and so on. ViewModel, on the other hand, doesn’t keep any reference to views but exposes LiveData for the view to observe. We’ll understand it better when we walk through some code shortly.

    Presenter保留对View的引用,该View用来回调View的方法,例如showProgress(),hideProgress(),showErrorMessage()等。 另一方面,ViewModel不会保留对视图的任何引用,但会公开LiveData以供视图观察。 当我们很快浏览一些代码时,我们会更好地理解它。
  2. Presenter is NOT lifecycle aware component by default. Views can be either destroyed or recreated (screen orientation change) while presenter is fetching data from the network. This might result in leakage of data or worse, it can crash the app. ViewModel, on the other hand, is a lifecycle aware component. It survives configuration change. ViewModelProviders knows how to give ViewModel. When we make below call the very first time,

    Presenter默认情况下不是生命周期感知组件。 在演示者从网络中获取数据时,视图可以被破坏或重新创建(屏幕方向改变)。 这可能导致数据泄漏或更糟,它可能会使应用程序崩溃。 另一方面,ViewModel是生命周期感知组件。 它可以承受配置更改。 ViewModelProviders知道如何提供ViewModel。 当我们第一次进行以下通话时,

Image for post

it gives a new instance. When the rotated activity comes back, it reconnects to the same ViewModel. We can understand it better with the help of below image which clearly shows that ViewModel survives configuration change.

它给出了一个新实例。 当旋转后的活动返回时,它将重新连接到相同的ViewModel。 下图帮助我们更好地理解它,该图清楚地表明ViewModel在配置更改后仍然存在。

Image for post

闲聊,让我们看一些代码! (Enough chit-chat, let’s see some code!)

We’ll be seeing the same app written using MVP and MVVM architectures. This is a very simple app which takes user birth year as input and prints his age after 2 seconds (intentionally added to mimic API request). Let’s dive in!

我们将看到使用MVP和MVVM架构编写的同一应用程序。 这是一个非常简单的应用程序,它将用户出生年份作为输入,并在2秒后打印出他的年龄(有意添加到模拟API请求中)。 让我们潜入吧!

Using MVP Architecture:

使用MVP架构:

Image for post
MVP Folder Structure
MVP文件夹结构

As we can see, we have view and presenter packages containing Activity (view) and Presenter classes. Also, we have created two interfaces namely IViewContract and IPresenterContract so that these two layers can communicate with each other. If you remember from the above image (MVP vs MVVM image), Presenter and View both keeps a reference to each other

如我们所见,我们具有包含Activity(视图)和Presenter类的view和presenter包。 另外,我们创建了两个接口,即IViewContract和IPresenterContract,以便这两层可以相互通信。 如果您还记得上面的图像(MVP与MVVM图像),Presenter和View两者相互保持引用

Let’s see what’s written inside Activity and Presenter.

让我们看看Activity和Presenter中写的是什么。

class FindMyAgeActivity : AppCompatActivity(), IViewContract {


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_find_my_age)


        val agePresenter = AgePresenter(this)


        btn_find_age.setOnClickListener {
            agePresenter.findAge(et_birth_year.text.toString())
        }
    }


    ... Overridden methods defined below
}
class AgePresenter(private val viewContract: IViewContract) : IPresenterContract {


    override fun findAge(birthYear: String) {
        viewContract.hideAgeView()


        if (birthYear.isBlank()) {
            viewContract.showValidationError()
            return
        }


        viewContract.showProgressBar()


        CoroutineScope(Dispatchers.IO).launch {
            delay(2000)
            val age = Calendar.getInstance().get(Calendar.YEAR) - Integer.parseInt(birthYear)


            withContext(Dispatchers.Main) {
                viewContract.hideProgressBar()


                if(age > 0) {
                    viewContract.showAgeOnScreen(age)
                } else {
                    viewContract.showValidationError()
                }
            }
        }
    }
}

So when user enters birth year and clicks on ‘Find Age’ button, presenter method ‘findAge()’ is called. This method takes care of updating the UI like showing progress loader, showing error message on UI if the user enters invalid input etc. using IViewContract interface methods.

因此,当用户输入出生年份并单击“查找年龄”按钮时,将调用演示者方法“ findAge()”。 此方法负责更新UI,例如显示进度加载器,如果用户使用IViewContract接口方法输入了无效输入等,则在UI上显示错误消息。

Everything seems to be working fine except for one small problem. Try to rotate the device while presenter is fetching the data (Here I have added explicit delay of 2 seconds to demonstrate the issue). What do you see ?

除了一个小问题,一切似乎都工作正常。 在演示者获取数据时尝试旋转设备(此处我添加了2秒的显式延迟来演示该问题)。 你看到了什么 ?

当我们旋转设备时发生了什么? (What happened when we rotated the device?)

When we rotated the device, android destroyed the existing activity and recreated it. So old presenter also died with activity and a new instance of presenter is created which is unaware of user request. So we see that ongoing loader disappears from the screen (as activity is recreated and default visibility of loader is View.GONE) and age is NOT displayed on UI. We can solve this issue by writing some boiler plate code , but we can do better and that too without writing any extra code.

当我们旋转设备时,android销毁了现有的活动并重新创建了它。 因此,旧的演示者也因活动而死亡,并且创建了一个新的演示者实例,该实例不知道用户请求。 因此,我们看到正在进行的加载程序从屏幕上消失了(因为重新创建了活动,并且加载程序的默认可见性是View.GONE),并且年龄没有显示在UI上。 我们可以通过编写一些样板代码来解决此问题,但是我们可以做得更好,而无需编写任何额外的代码。

MVVM抢救 (MVVM to rescue)

Using MVVM Architecture:

使用MVVM体系结构:

Image for post
MVVM Folder Structure
MVVM文件夹结构

As we can see, we don’t have any interfaces defined here in MVVM. So lots of boiler-plate code have been removed. We have two packages namely view and ViewModel. The view has a reference to ViewModel but ViewModel has no reference to View. ViewModel survives rotation and other configuration changes.

如我们所见,MVVM在这里没有定义任何接口。 因此,许多样板代码已被删除。 我们有两个包,分别是view和ViewModel。 该视图引用了ViewModel,但ViewModel没有引用View。 ViewModel可以承受旋转和其他配置更改。

Let’s see code written inside Activity and ViewModel.

让我们看看在Activity和ViewModel内部编写的代码。

class FindMyAgeActivity : AppCompatActivity(){


    private lateinit var viewModel: AgeViewModel


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_find_my_age)


        viewModel = ViewModelProviders.of(this).get(AgeViewModel::class.java)
        observeViewModel()


        btn_find_age.setOnClickListener {
            viewModel.findAge(et_birth_year.text.toString())
        }
    }


    private fun observeViewModel() {
        observeLoaderChange()
        observeAgeTextChange()
    }


    private fun observeAgeTextChange() {
        viewModel.age.observe(this, Observer { personAge ->
            tv_age.visibility = View.VISIBLE
            tv_age.text = getString(R.string.age_text, personAge)
        })
    }


    private fun observeLoaderChange() {
        viewModel.showLoader.observe(this, Observer { isShowLoader ->
            if(isShowLoader) {
                progress_bar.visibility = View.VISIBLE
            } else {
                progress_bar.visibility = View.GONE
            }
        })
    }
}
class AgeViewModel: ViewModel() {


    val age = MutableLiveData<Int>()
    val showLoader = MutableLiveData<Boolean>()
   
    fun findAge(birthYear: String) {
        showLoader.value = true


        CoroutineScope(Dispatchers.IO).launch {
            delay(2000)
            val personAge = Calendar.getInstance().get(Calendar.YEAR) - Integer.parseInt(birthYear)
            withContext(Dispatchers.Main) {
                showLoader.value = false
                if(personAge > 0) {
                    age.value = personAge
                } 
            }
        }
    }
}

So when activity is created for the first time and we ask for ViewModel instance, ViewModelProviders creates a new instance and gives it to us. But once the activity is recreated (on screen orientation change) it reconnects back to same ViewModel. Since ViewModel doesn’t have any reference to view, it shares data with activity with the use of LiveData.

因此,当首次创建活动并要求ViewModel实例时,ViewModelProviders将创建一个新实例并将其提供给我们。 但是,一旦重新创建活动(在屏幕方向上更改),它就会重新连接回相同的ViewModel。 由于ViewModel没有要查看的引用,因此它通过LiveData与活动共享数据。

LiveData is an observable data holder class that is lifecycle-aware, one of the Android Architecture Components. You can use LiveData to enable your UI to update automatically when the data updates.

LiveData是可观察到的数据持有者类,具有生命周期意识,是Android体系结构组件之一。 您可以使用LiveData使您的UI在数据更新时自动更新。

ViewModel objects are scoped to the Lifecycle passed to the ViewModelProvider when getting the ViewModel. The ViewModel remains in memory until the Lifecycle it’s scoped to goes away permanently: in the case of an activity, when it finishes, while in the case of a fragment, when it’s detached.

ViewModel对象的范围是获取ViewModel时传递给ViewModelProvider的生命周期。 ViewModel保留在内存中,直到其生命周期范围永久消失:对于活动而言,它完成时,对于片段而言,当它脱离时。

Now when you run this app and rotate the screen while ViewModel is fetching data, everything works fine and UI is updated as soon as ViewModel updates the age LiveData.

现在,当您运行此应用程序并在ViewModel获取数据时旋转屏幕时,一切正常,并且只要ViewModel更新了年龄 LiveData,UI就会更新。

Image for post
  • MVP architecture enforces View and Presenter to be tightly coupled with each other whereas, in MVVM View and ViewModel are loosely coupled.

    MVP体系结构强制将View和Presenter紧密地耦合在一起,而在MVVM中,View和ViewModel则是松散耦合的。
  • In MVP, lots of code is needed for setting up callbacks between View and Presenter making code size excessive. In MVVM, we don’t need much boilerplate code for setting up callbacks. Less code means fewer bugs.

    在MVP中,在View和Presenter之间设置回调需要大量代码,从而使代码太大。 在MVVM中,我们不需要太多样板代码来设置回调。 更少的代码意味着更少的错误。
  • In MVP, we need to write some boilerplate code inside view and presenter to check whether the view is alive or not before posting any data back to view whereas, in MVVM ViewModel and LiveData solves the lifecycle issue without writing much code.

    在MVP中,我们需要在视图和演示者内部编写一些样板代码,以在将任何数据发布回视图之前检查视图是否处于活动状态,而在MVVM中,ViewModel和LiveData无需编写太多代码即可解决生命周期问题。

If you have made it so far, give yourself pat on the back. I hope I was able to clearly explain the differences present in MVP and MVVM. If you learned something by reading this article, do share with your friends. Your feedback/suggestions are welcome.

如果您到目前为止已经做到了,那就轻拍一下自己的背部。 我希望我能够清楚地解释MVP和MVVM中存在的差异。 如果您通过阅读本文中学到了东西,请与朋友分享。 欢迎您的反馈/建议。

Thank you!

谢谢!

翻译自: https://medium.com/swlh/android-architecture-journey-from-mvp-to-mvvm-29bf469a8b59

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值