Android DataBinding & MVVM

先說說兩者的關係,DataBinding是一個實現數據和UI綁定的框架,而MVVM是一種架構模式,實現MVVM模式需要藉助DataBinding來完成。

本文將以《Android 談談我所理解的MVP》中的例子爲基礎,使用MVVM模式重新實現一下。

嗯,本篇博客也是有副標題的:Retrofit + RxJava + RxLifecycle + MVVM

本文涉及到的一些基礎知識:

《Android DataBinding使用詳解(一)》

《Android DataBinding使用詳解(二)》

《Android Retrofit + RxJava使用詳解》

《Android 使用RxLifecycle解決RxJava內存泄漏》

Demo效果圖:

Demo效果圖

Demo下載地址

1.MVVM

先來張圖感受一下:

MVVM

  • View

    View層只負責UI相關的工作,不進行邏輯處理,並且不需要在Activity/Fragment中做更新UI的操作,更新UI通過Binding實現,在ViewModel中更新數據源即可。如果UI和業務邏輯沒有關係,比如點擊按鈕顯示或隱藏控件,是可以在Activity/Fragment中進行UI更新的。

  • ViewModel

    ViewModel層只做和邏輯處理相關的工作,在ViewModel中不會持有View層的引用,雙方通過Binding方式通信,只需要在ViewModel層對數據進行操作,View層就會自動更新UI。

  • Model

    Model層的職責和MVC、MVP類似,基本上就是實體類(Bean)和Retrofit的Service。

    在MVVM中Bean是繼承BaseObservable的,有些文章裏把Bean歸到了ViewModel層中,因爲Bean中實現的觀察者模式纔是View和Model溝通的橋樑,嗯,有道理,不過我還是想把它歸到Model層,關於這個問題不在過多討論,仁者見仁,智者見智吧。

2.MVVM實踐

首先看下項目結構:

項目結構

做一些準備工作

定義一個請求參數接口:

public interface RetrofitService {

    /**
     * 獲取快遞信息
     * Rx方式
     *
     * @param type   快遞類型
     * @param postid 快遞單號
     * @return Observable<ExpressInfo>
     */
    @GET(Constant.UrlOrigin.get_express_info)
    Observable<ExpressInfo> getExpressInfoRx(@Query("type") String type, @Query("postid") String postid);
}

定義Retrofit幫助類,用於Retrofit與RetrofitService的初始化:

public class RetrofitHelper {

    private static RetrofitHelper retrofitHelper;
    private RetrofitService retrofitService;

    public static RetrofitHelper getInstance() {
        return retrofitHelper == null ? retrofitHelper = new RetrofitHelper() : retrofitHelper;
    }

    private RetrofitHelper() {
        // 初始化Retrofit
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(Constant.SERVER_URL)
                .addConverterFactory(GsonConverterFactory.create()) // json解析
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 支持RxJava
                .build();
        retrofitService = retrofit.create(RetrofitService.class);
    }

    public RetrofitService getRetrofitService() {
        return retrofitService;
    }
}

Model層

Retrofit相關:

public class DataManager {

    private static DataManager dataManager;
    private RetrofitService retrofitService;

    public static DataManager getInstance() {
        return dataManager == null ? dataManager = new DataManager() : dataManager;
    }

    private DataManager() {
        retrofitService = RetrofitHelper.getInstance().getRetrofitService();
    }

    /**
     * 獲取快遞信息
     *
     * @param type   快遞類型
     * @param postid 快遞單號
     * @return Observable<ExpressInfo>
     */
    public Observable<ExpressInfo> getExpressInfo(String type, String postid) {
        return retrofitService.getExpressInfoRx(type, postid);
    }
}

使用了單例模式,在構造方法中獲取RetrofitService實例,定義getExpressInfo方法,返回泛型爲ExpressInfo的被觀察者對象,稍後在ViewModel中會用到。

實體類:

public class ExpressInfo extends BaseBean {

    private String message;
    private String nu;
    private String ischeck;
    private String condition;
    private String com;
    private String status;
    private String state;
    private List<DataBean> data;

    public void setExpressInfo(ExpressInfo expressInfo) {
        setMessage(expressInfo.getMessage());
        setNu(expressInfo.getNu());
        setIscheck(expressInfo.getIscheck());
        setCondition(expressInfo.getCondition());
        setCom(expressInfo.getCom());
        setStatus(expressInfo.getStatus());
        setState(expressInfo.getState());
        setData(expressInfo.getData());
    }

    @Bindable
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
        notifyPropertyChanged(BR.message);
    }

    @Bindable
    public String getNu() {
        return nu;
    }

    public void setNu(String nu) {
        this.nu = nu;
        notifyPropertyChanged(BR.nu);
    }

    ...
}

BaseBean:

public class BaseBean extends BaseObservable {
}

主要看下setExpressInfo方法,setExpressInfo方法是爲了在ViewModel層中獲取到數據之後,方便更新數據源的,其中的操作也可以放在ViewModel中進行處理。

ViewModel層

首先看下BaseViewModel:

public class BaseViewModel {

    private LifecycleProvider<ActivityEvent> provider;

    public BaseViewModel(LifecycleProvider<ActivityEvent> provider) {
        this.provider = provider;
    }

    public LifecycleProvider<ActivityEvent> getProvider() {
        return provider;
    }
}

由於使用了RxLifecycle框架來管理RxJava,而RxLifecycle與RxJava的綁定是在ViewModel中進行的,所以就需要在構造ViewModel時傳入LifecycleProvider接口的實例。MainActivity最終繼承了RxAppCompatActivity,在RxAppCompatActivity內部又實現了LifecycleProvider接口,所以在構造ViewModel時直接傳入this就可以了。

public class ExpressViewModel extends BaseViewModel {

    public ExpressInfo expressInfo;
    private DataManager dataManager;

    // 是否顯示Loading
    public final ObservableBoolean isShowLoading = new ObservableBoolean();
    // 錯誤信息
    public final ObservableField<String> errorMessage = new ObservableField<>();

    public ExpressViewModel(LifecycleProvider<ActivityEvent> provider, ActivityMainBinding binding) {
        super(provider);
        expressInfo = new ExpressInfo();
        binding.setExpressViewModel(this);
        dataManager = DataManager.getInstance();
    }

    /**
     * 獲取快遞信息
     *
     * @param type   快遞類型
     * @param postid 快遞單號
     */
    public void getExpressInfo(String type, String postid) {
        isShowLoading.set(true);

        dataManager.getExpressInfo(type, postid)
                .subscribeOn(Schedulers.io()) // 在子線程中進行Http訪問
                .observeOn(AndroidSchedulers.mainThread()) // UI線程處理返回接口
                .compose(getProvider().<ExpressInfo>bindUntilEvent(ActivityEvent.DESTROY)) // onDestroy取消訂閱
                .subscribe(new DefaultObserver<ExpressInfo>() {  // 訂閱
                    @Override
                    public void onNext(@NonNull ExpressInfo expressInfo) {
                        ExpressViewModel.this.expressInfo.setExpressInfo(expressInfo);
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        errorMessage.set(e.getMessage());
                        isShowLoading.set(false);
                    }

                    @Override
                    public void onComplete() {
                        isShowLoading.set(false);
                    }
                });
    }
}

首先看下:

// 是否顯示Loading
public final ObservableBoolean isShowLoading = new ObservableBoolean();
// 錯誤信息
public final ObservableField<String> errorMessage = new ObservableField<>();

由於Dialog和Toast不是定義在佈局文件中的,所以不能通過Binding的方式顯示Dialog和Toast,而Activity中不能處理邏輯,所以無法在Activity中判斷是否顯示Dialog和Toast,在ViewModel中又不能顯示UI,那該怎麼辦呢?彆着急,可以在ViewModel中定義一個觀察者對象isShowLoading,在Activity中對isShowLoading進行監聽,true時顯示Dialog,false時關閉Dialog就可以了,errorMessage同理。

然後看下構造方法:

public ExpressViewModel(LifecycleProvider<ActivityEvent> provider, ActivityMainBinding binding) {
    super(provider);
    expressInfo = new ExpressInfo();
    binding.setExpressViewModel(this);
    dataManager = DataManager.getInstance();
}

在構造方法中傳入LifecycleProvide和Binding的實例,調用Bindin的setExpressViewModel方法將ViewModel層與View層進行綁定。

接下來定義一個getExpressInfo方法,在其中調用DataManager類的getExpressInfo方法(根據實際需求命名),返回被觀察者對象,然後進行訂閱,在onNext方法中設置數據,在onError和onComplete方法中設置Dialog和Toast的狀態。

View層

佈局文件:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="expressViewModel"
            type="com.yl.mvvmdemo.viewmodel.ExpressViewModel" />

        <variable
            name="clickListener"
            type="android.view.View.OnClickListener" />
    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:layout_marginTop="10dp"
            android:text="@{expressViewModel.expressInfo.data[0].context}" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_marginBottom="10dp"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:onClick="@{clickListener}"
            android:text="獲取快遞信息" />

    </RelativeLayout>

</layout>

定義變量expressViewModel,會在Binding中生成對應的set方法,就是我們在ViewModel的構造方法中調用的setExpressViewModel方法,然後在TextView中顯示快遞信息。

Activity:

public class MainActivity extends BaseActivity {

    private ProgressDialog progressDialog;
    private ExpressViewModel expressViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        expressViewModel = new ExpressViewModel(this, binding);
        binding.setClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                expressViewModel.getExpressInfo("yuantong", "11111111111");
            }
        });

        // 顯示Loading
        progressDialog = new ProgressDialog(this);
        progressDialog.setMessage("正在獲取快遞信息...");
        expressViewModel.isShowLoading.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
            @Override
            public void onPropertyChanged(Observable observable, int i) {
                if (expressViewModel.isShowLoading.get()) {
                    progressDialog.show();
                } else {
                    progressDialog.dismiss();
                }
            }
        });

        // 顯示錯誤信息
        expressViewModel.errorMessage.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
            @Override
            public void onPropertyChanged(Observable observable, int i) {
                Toast.makeText(MainActivity.this, expressViewModel.errorMessage.get(), Toast.LENGTH_SHORT).show();
            }
        });
    }
}

BaseActivity:

public class BaseActivity extends RxAppCompatActivity {
}

在Activity中創建ExpressViewModel對象,在點擊方法中調用getExpressInfo方法獲取快遞信息,重點看下下面的方法:

expressViewModel.isShowLoading.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
    @Override
    public void onPropertyChanged(Observable observable, int i) {
        if (expressViewModel.isShowLoading.get()) {
            progressDialog.show();
        } else {
            progressDialog.dismiss();
        }
    }
});

對isShowLoading添加一個屬性改變的監聽,如果爲true就顯示loading框,爲false就關閉。

3.寫在最後

到這裏,MVVM模式就學習完了,DataBinding確實很強大,但有個致命的問題,就是出現問題的時候不好debug,因爲UI更新都在佈局文件中完成了,而且不論出現什麼錯誤,都會提示找不到Binding文件,沒有具體的提示,這個相當頭疼。

源碼已託管到GitHub上,歡迎Fork,覺得還不錯就Start一下吧!

GitHub傳送門

歡迎同學們吐槽評論,如果你覺得本篇博客對你有用,那麼就留個言或者頂一下吧(^-^)

發佈了64 篇原創文章 · 獲贊 275 · 訪問量 27萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章