天天看點

Android架構模式三:MVVM

原文位址:https://upday.github.io/blog/model-view-viewmodel/

Android架構模式:MVVM

在開發upday應用的前六個月中,經過四次不同設計,我們學到了一個重要的教訓:我們需要一個能及時相應設計變化的架構!最終我們選擇的解決方案是MVVM。和我一起來探索下什麼是MVVM;我們是如何在upday中應用它的以及是什麼使得它對我們而言是如此完美地适合。

MVP模式

MVVM的主要參與者是:

  • View-向ViewModel報告使用者行為
  • ViewModel-公開與View相關的資料流
  • DataModel-抽象的資料源。ViewModel與DataModel協作以擷取和儲存資料。

乍一看,MVVM似乎與MVP模式非常接近,因為兩者在抽象View的狀态和行為方面都做得非常好。MVP抽象了一個獨立于特定平台使用者界面的View,而MVVM則是為了簡化編寫事件驅動的使用者界面而建立的。

如果MVP是由Presenter直接告知View顯示什麼,那在MVVM中,ViewModel暴露View可以綁定到的事件流。這樣,ViewModel就不需要像Presenter一樣再持有View的引用。這也意味着MVP模式所需的所有接口現在都被丢棄了。

View同樣也會通知ViewModel不同的使用者行為。MVVM模式支援View和ViewModel之間的雙向資料綁定,它們之間存在一個多對一的關系。View有一個ViewModel的引用,但ViewModel沒有關系View的資訊。消費者需要知道生産者,但是生産者,即ViewModel不需要知道,也不關心誰是消費者。

Android架構模式三:MVVM

upday中的MVVM

快速浏覽下upday部落格上Androidi的文章将立即揭示我們最喜歡的庫是:RxJava。毫無疑問,RxJava是upday代碼的基石。MVVM所需的事件驅動模型就是由RxJava的

Observable

實作的。以下是我們如何在RxJava的幫助下使用MVVM建構upday的Android應用:

DataModel

DataModel公開了便于RxJava的

Observable

事件流消費的資料。它組合來自多個資料源的資料,如網絡層,資料庫或shared preferences,并向任何需要它的地方公開易于消費的資料。DataModel維護着全部業務邏輯。

我們強烈強調單一職責原則,這指導我們為應用中的每一個功能建立一個DataModel。例如,我們有一個ArticleDataModel合并來自API服務和資料庫層資料作為輸出,這個DataModel處理業務邏輯是通過一個時間過慮器來確定從資料庫中擷取最新的消息。

ViewModel

ViewModel是應用程式的一個抽象視圖模型。ViewModel從DataModel中查詢必要的資料,應用UI邏輯,然後公開View使用的相關資料。與DataModel相似,ViewModel通過

Observables

公開資料。

我們從ViewModel中學到兩件事:

  • ViewModel應該向View公開它的狀态,而不僅僅是事件。例如,如果我們需要顯示使用者的姓名和郵箱位址,我們公為一個封裝了兩者的

    DisplayableUser

    類建立一個資料流,而不是為此建立兩個資料流。每次姓名或郵箱改變時,該資料流都會響應。這樣,我們就確定View始終顯示最新的使用者資訊。
  • 我們應該確定使用者的每個操作都通過ViewModel,View的任何可能的邏輯都遷移到ViewModel中。

我們在common mistakes in MVVM + RxJava這篇部落格中詳細讨論了這兩個話題。

View

View是應用中實際的使用者界面。它可以是

Activity

Fragment

或任何自定義控件。對于

Activity

Fragment

,我們在方法

onResume()

中綁定事件源并在

onPause()

中解除綁定。

private final CompositeSubscription mSubscription = new CompositeSubscription();

@Override
public void onResume() {
    super.onResume();
    mSubscription.add(mViewModel.getSomeData()
                     .observeOn(AndroidSchedulers.mainThread())
                     .subscribe(this::updateView,
                                this::handleError));
}

@Override
public void onPause() {
    mSubscription.clear();
    super.onPause();
}
           

如果MVVM中的View是一個Android控件,那麼就在它的構造方法中綁定。為了確定不發生記憶體洩漏,解除綁定在

onDetachedFromWindow

中進行。

private final CompositeSubscription mSubscription = new CompositeSubscription();

public MyView(Context context, MyViewModel viewModel) {
    ...
    mSubscription.add(mViewModel.getSomeData()
                     .observeOn(AndroidSchedulers.mainThread())
                     .subscribe(this::updateView,
                                this::handleError));
}

@Override
public void onDetachedFromWindow() {
    mSubscription.clear();
    super.onDetachedFromWindow();
}
           

MVVM中類的可測試性

我們喜歡MVVM模式的主要原因之一是測試非常簡單

DataModel

我們的代碼中大量應用控制反轉,且不包含任何Android類,這有助于實作單元測試。

ViewModel

我們将View與單元測試視為ViewModel資料的不同消費者。ViewModel完全與UI或Android類分離,是以可以直接進行單元測試。

考慮下面的例子,其中ViewModel僅僅公開DataModel中的一些資料:

public class ViewModel {
    private final IDataModel mDataModel;

    public ViewModel(IDataModel dataModel) {
        mDataModel = dataModel;
    }

    public Observable<Data> getSomeData() {
        return mDataModel.getSomeData();
    }
}
           

這個ViewModel的測試很好實作。借助Mockito庫,我們模拟DataModel并控制傳回的資料。然後確定當我們訂閱由

getSomeData()

傳回的

Observable

時,預期的資料被發射(emit)了。

public class ViewModelTest {
    @Mock
    private IDataModel mDataModel;
    private ViewModel mViewModel;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        mViewModel = new ViewModel(mDataModel);
    }

    @Test
    public void testGetSomeData_emitsCorrectData() {
        SomeData data = new SomeData();
        Mockito.when(mDataModel.getSomeData()).thenReturn(Observable.just(data));
        TestSubscriber<SomeData> testSubscriber = new TestSubscriber<>();

        mViewModel.getSomeData().subscribe(testSubscriber);

        testSubscriber.assertValue(data);
    }
}
           

如果ViewModel需要通路Android類,我們建立一個名為

Provider

的包裝器。例如,對于Android資源,我們建立一個

IResourceProvider

,它會分不開一些如

String getString(@StringRes final int id)

這樣的方法。

IResourceProvider

的實作會包含對

Context

引用,但是ViewModel僅會引用注入的

IResourceProvider

就像我們上面,和這篇部落格中提到的,我們建立模型對象來儲存資料狀态。這也允許控制ViewModel發射了的資料并作更高層次的測試。

View

鑒于使用者界面中的邏輯很少,視圖很容易用Espresso進行測試。我們還使用DaggerMock和MockWebServer等庫來提高UI測試的穩定性。

MVVM是正确的解決方案嗎?

我們已經使用MVVM和RxJava快一年了。我們發現,由于View僅僅作為ViewModel的消費者,這使得替換不同UI元素時,僅需改變一小部分的類,有些甚至為零。

我們同時學習關注分離是多麼的重要,我們應該更多地分解代碼,建立隻有特定職責的小巧的View和ViewModel。View中的ViewModel是注入的。這意味着大多時候,我們僅僅在XML中建立使用者界面,且不需要作任何修改。因而,當U需求再次改變時,我們可以輕松地替換掉View。

結語

MVVM結合了MVP提供的關注點分離的優點,同時利用了資料綁定的優點。成為了一種最少化視圖邏輯、模型盡可能多地由操作驅動的模式。

經過在應用“嬰兒期”的設計變更後,我們在應用的“青春期”轉向了MVVM,這期間我們學到了很多東西。現在,我們可以自信地響應下一次設計變更。終于我們可以稱upday是一個成熟的應用了。

這裡可以找到一個簡單的MVVM示例。

這裡可以找到一個比較MVP與MVVP的“Hello, World!”式的示例。

Written with StackEdit.