Android架構元件:用ViewModelCommandLiveData處理ViewModel中的事件釋出

1484289741000發表於2018-06-06

如果你已經開始使用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作為一個解決思路,希望能起到拋磚引玉的作用。

相關文章