先說說兩者的關係,DataBinding是一個實現數據和UI綁定的框架,而MVVM是一種架構模式,實現MVVM模式需要藉助DataBinding來完成。
本文將以《Android 談談我所理解的MVP》中的例子爲基礎,使用MVVM模式重新實現一下。
嗯,本篇博客也是有副標題的:Retrofit + RxJava + RxLifecycle + MVVM
本文涉及到的一些基礎知識:
《Android Retrofit + RxJava使用詳解》
《Android 使用RxLifecycle解決RxJava內存泄漏》
Demo效果圖:
1.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一下吧!
歡迎同學們吐槽評論,如果你覺得本篇博客對你有用,那麼就留個言或者頂一下吧(^-^)