如果你已經開始使用Google官方的Mvvm框架,理想情況下,Activity或者Fragment當中的邏輯應該越少越好,然而事實上我們的頁面在載入layout,獲取Viewmodel之外,往往還負責著以下一些職責:
- 各種對話方塊(等待效果,確認,Toast等等)
- 頁面跳轉
- 其他頁面需要監控viewmodel變化的行為
儘管DataBinding解決了資料->檢視繫結的問題,仍然有一些操作是我們必須在Activity/Fragment當中完成的,這些不得不由檢視完成的行為可以視為一系列CallBack,現在的問題是,如何將這些CallBack從ViewModel中分發出去。
Mvp中如何解決Callback問題
MVP模式下,會約定一個約束類,來要求View跟Presenter分別負責自己的行為:
public interface BaseMVPContract {
interface View extends BaseView {
/**
* 開始等待載入
*/
void onLoadingStart();
/**
* 結束等待載入
*/
void onLoadingEnd();
}
interface Presenter extends Presenter<View> {
/**
* 載入ProjectList
*
* @param parentId 父分類id
* @param name 分類名稱
*/
void loadProjects(Long Id);
}
}
複製程式碼
在Mvvm中,ViewModel不關心View的實現,也不持有View的引用,因此,對View的約束沒有存在的必要,參考LiveData的使用,我們可以考慮將Viewmodel要求檢視執行的指令封裝成一個被觀察者(LiveData),將動作釋出出去,檢視方訂閱這個LiveData來實現自己的指令執行邏輯.
封裝指令LiveData的一種實現方式
public class ViewModelCommandLiveData<T> {
private MutableLiveData<List<UiAction<T>>> delegate = new MutableLiveData<>();
private List<UiAction<T>> list = new ArrayList<>();
@MainThread
public void execute(@NonNull UiAction<T> action) {
list.add(action);
delegate.setValue(list);
//一個指令的狀態是瞬時的,因此在執行完成之後需要清除狀態
delegate.setValue(null);
}
public void observe(LifecycleOwner owner, @NonNull UiActionExecutor<T> executor) {
delegate.observe(owner, uiActions -> {
if (uiActions != null) {
for (UiAction<T> action : uiActions) {
executor.exec(action);
}
}
if (uiActions != null && !uiActions.isEmpty()) {
list = new ArrayList<>();
}
});
}
public interface UiAction<E> {
void exec(E t);
}
public interface UiActionExecutor<R> {
void exec(UiAction<R> action);
}
}
複製程式碼
ViewModel中如何傳送指令
public class ProjectListViewModel extends AndroidViewModel {
//指令LiveData
public ViewModelCommandLiveData<ProjectListVmHandler> commands = new ViewModelCommandLiveData<>();
public LiveData<List<Project>> getProjectList(String userId) {
//傳送指令開始等待效果
commands.execute(t->t.onLoadingStart());
final MutableLiveData<List<Project>> data = new MutableLiveData<>();
gitHubService.getProjectList(userId).enqueue(new Callback<List<Project>>() {
@Override
public void onResponse(Call<List<Project>> call, Response<List<Project>> response) {
//傳送指令關閉等待效果
commands.execute(t->t.onLoadingEnd());
data.setValue(response.body());
}
@Override
public void onFailure(Call<List<Project>> call, Throwable t) {
//傳送指令關閉等待效果
commands.execute(t->t.onLoadingEnd());
data.setValue(null);
}
});
return data;
}
public interface ProjectListVmHandler{
void onLoadingStart();
void onLoadingEnd();
}
}
複製程式碼
檢視中訂閱指令並實現指令處理:
public class ProjectListFragment extends Fragment implements Injectable {
public static final String TAG = "ProjectListFragment";
@Inject
ViewModelProvider.Factory viewModelFactory;
private ProjectListViewModel.ProjectListVmHandler mHandler = new ProjectListViewModel.ProjectListVmHandler() {
@Override
public void onLoadingStart() {
showLoading();
}
@Override
public void onLoadingEnd() {
hideLoading();
}
};
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
final ProjectListViewModel viewModel = ViewModelProviders.of(this,
viewModelFactory).get(ProjectListViewModel.class);
observeViewModel(viewModel);
}
private void observeViewModel(ProjectListViewModel viewModel) {
//訂閱viewModel指令LiveData
viewModel.commands.observe(this, action -> action.exec(mHandler));
}
}
複製程式碼
總結
關於ViewModel中是否需要CallBack的話題,不同的業務場景有不同的回答,作為一個程式設計師,我們總是希望把UI相關的重複、繁瑣又容易出bug的套路程式碼交給程式碼生成去處理,但是實際業務場景往往需要我們這樣去做,本文的VMCommandLiveData作為一個解決思路,希望能起到拋磚引玉的作用。