如何应对 Android 面试官 -> MVVM 实战一个新闻客户端 (下)

前言


本章我们基于重构的方式进行一个 MVVM 的实战,我们将一个新闻列表的普通实现,一步一步的改造成 MVVM 的架构模式,一共分为上中下三个章节,本章继续上一章,开始下篇的讲解;

缓存数据处理


上一章我们讲到了网络数据的缓存逻辑,但是缓存之后我们还没有使用,接下来我们来看下如何使用缓存的数据;

image.png

数据的整体来源是两个方向,一个是 Server 端数据,一个是 SP 中的数据;

我们需要在底层数据分发的时候就来判断是否来自缓存数据,新增一个接口,同时在底层数据分发的时候进行判断并替换;

public interface MvvmDataObserver<F> {
    void onSuccess(F t, boolean isFromCache);
    void onFailure(String errorMsg);
}

因为我们使用了 RxJava 来做网络接口层的转换,所以我们的 BaseObserver 需要重构一下:

public class BaseObserver<T> implements Observer<T> {

    private MvvmDataObserver mvvmDataObserver;

    public BaseObserver(MvvmDataObserver mvvmDataObserver) {
        this.mvvmDataObserver = mvvmDataObserver;
    }

    @Override
    public void onSubscribe(Disposable d) {}

    @Override
    public void onNext(T t) {
        mvvmDataObserver.onSuccess(t, false);
    }

    @Override
    public void onError(Throwable e) {
        mvvmDataObserver.onFailure(e);
    }

    @Override
    public void onComplete() {}
}

也就是说将数据的分发,统一交给了 MvvmDataObserver 来分发;然后,需要 BaseMvvmModel 来实现这个 MvvmDataObserver 接口,然后子 Model 来做接口的具体实现;

public abstract class BaseMvvmModel<NETWORK_DATA, RESULT_DATA> implements MvvmDataObserver<NETWORK_DATA> {

}

NewListModel 重构如下:

public class NewsListModel extends BaseMvvmModel<NewsListBean, ArrayList<BaseViewModel>> {

    private final String mChannelId;
    private final String mChannelName;
    private NewsListBean mNewsListBean;

    public NewsListModel(String channelId, String channelName) {
        super(true, channelId + channelName + "pref_key", 1);
        this.mChannelId = channelId;
        this.mChannelName = channelName;
    }

    @Override
    public void load() {
        TecentNetworkApi.getService(NewsApiInterface.class)
                .getNewsList(mChannelId,
                        mChannelName, String.valueOf(mPageNum))
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new BaseObserver<>(this));
    }

    @Override
    public void onSuccess(NewsListBean newsListBean, boolean isFromCache) {
        ArrayList<BaseViewModel> viewModels = new ArrayList<>();
        NewsListModel.this.mNewsListBean = newsListBean;
        for (NewsListBean.Contentlist contentlist : newsListBean.showapiResBody.pagebean.contentlist) {
            if (contentlist.imageurls != null && contentlist.imageurls.size() > 0) {
                PictureTitleViewModel pictureTitleViewModel = new PictureTitleViewModel();
                pictureTitleViewModel.imgUrl = contentlist.imageurls.get(0).url;
                pictureTitleViewModel.title = contentlist.title;
                pictureTitleViewModel.jumpUrl = contentlist.link;
                viewModels.add(pictureTitleViewModel);
            } else {
                TitleViewModel titleViewModel = new TitleViewModel();
                titleViewModel.title = contentlist.title;
                titleViewModel.jumpUrl = contentlist.link;
                viewModels.add(titleViewModel);
            }
        }
        notifyResultToListener(NewsListModel.this.mNewsListBean, viewModels, isFromCache);
    }

    @Override
    public void onFailure(Throwable throwable) {

    }
}

NewsChannelModel 重构如下:

public class NewsChannelModel extends BaseMvvmModel<NewsChannelsBean, List<NewsChannelsBean.ChannelList>> {

    public NewsChannelModel() {
        super(false, "NEWS_CHANNEL_PREF_KEY");
    }

    @Override
    public void load() {
        TecentNetworkApi.getService(NewsApiInterface.class)
                .getNewsChannels()
                .compose(TecentNetworkApi.getInstance().applySchedulers(new BaseObserver<NewsChannelsBean>(this)));
    }

    @Override
    public void onSuccess(NewsChannelsBean newsChannelsBean, boolean isFromCache) {
        notifyResultToListener(newsChannelsBean, newsChannelsBean.showapiResBody.channelList, isFromCache);
    }

    @Override
    public void onFailure(Throwable throwable) {
        mReferenceIBaseModeListener.get().onLoadFail(400, throwable.getMessage());
    }
}

然后我们的 notifyResultToListener 重构如下,增加了 isFromCache 的判断逻辑;

protected void notifyResultToListener(NETWORK_DATA networkData, RESULT_DATA data, boolean isFromCache) {
    IBaseModelListener listener = mReferenceIBaseModeListener.get();
    if (listener != null) {
        if (mIsPaging) {
            listener.onLoadSuccess(this, data, new PageResult(mPageNum == INIT_PAGE_NUMBER, data == null ? true : ((List) data).isEmpty(), ((List) data).size() > 10));
        } else {
            listener.onLoadSuccess(this, data);
        }
    }

    if (mIsPaging) {
        if (mCachedPreferenceKey != null && mPageNum == INIT_PAGE_NUMBER && !isFromCache) {
            saveDataToPreference(networkData);
        }
    } else {
        if (mCachedPreferenceKey != null && !isFromCache) {
            saveDataToPreference(networkData);
        }
    }

    if (mIsPaging) {
        if (data != null && ((List) data).size() > 0 && !isFromCache) {
            mPageNum++;
        }
    }
    if (!isFromCache) {
        isLoading = false;
    }
}

只有非缓存的时候,我们才将数据保存到本地,根据 isFromCache 标识;

我们接下来处理 缓存的读取和网络请求的发送逻辑;同样的,我们需要在 BaseMvvmModel 中进行逻辑的判断和处理,我们增加一个 getCachedDataAndLoad 接口,先从缓存获取同时进行网络数据的获取;

public void getCachedDataAndLoad() {
    if (!isLoading) {
        isLoading = true;
    }
    String cacheStr = BasicDataPreferenceUtil.getInstance().getString(mCachedPreferenceKey);
    if (!TextUtils.isEmpty(cacheStr)) {
        try {
            // 反序列化
            NETWORK_DATA data = new Gson().fromJson(new JSONObject(cacheStr).getString("data"),
                    (Class<NETWORK_DATA>) GenericUtils.getGenericType(this));
            if (data != null) {
                onSuccess(data, true);
            }
            if (isNeedUpdate()) {
                load();
                return;
            }
        } catch (JSONException e) {
            // do nothing
        }
    }
    load();
}

然后,Fragment 中调用这个 getCachedDataAndLoad 方法进行数据的拉取;

model = new NewsChannelModel();
model.register(this);
model.getCachedDataAndLoad();
mNewsListModel = new NewsListModel(getArguments().getString(BUNDLE_KEY_PARAM_CHANNEL_ID),
        getArguments().getString(BUNDLE_KEY_PARAM_CHANNEL_NAME));
mNewsListModel.register(this);
mNewsListModel.getCachedDataAndLoad();

运行,可以看到,我们的页面在无网状态下依然可以展示数据,但是如果我们的 App 是在无网的时候安装并运行的,那么依然是会展示一片空白,这个时候,其实是需要我们进行一些数据的预置的,我们可以增加一下预置数据的获取逻辑;

public abstract class BaseMvvmModel<NETWORK_DATA, RESULT_DATA> implements MvvmDataObserver<NETWORK_DATA> {
    
    private String mPreDefinedData;
    ```
    public BaseMvvmModel(boolean isPaging, String cachedPreferenceKey, String preDefinedData, int... initPageNumber) {
        if (isPaging && initPageNumber != null && initPageNumber.length > 0) {
            INIT_PAGE_NUMBER = initPageNumber[0];
        } else {
            INIT_PAGE_NUMBER = -1;
        }
        this.mCachedPreferenceKey = cachedPreferenceKey;
        // 赋值预置数据
        this.mPreDefinedData = preDefinedData;
    }
    
    // 获取预置数据
    ```
    public void getCachedDataAndLoad() {
        if (!isLoading) {
            isLoading = true;
        }
        if (mCachedPreferenceKey != null) {
            String cacheStr = BasicDataPreferenceUtil.getInstance().getString(mCachedPreferenceKey);
            if (!TextUtils.isEmpty(cacheStr)) {
                try {
                    // 反序列化
                    NETWORK_DATA data = new Gson().fromJson(new JSONObject(cacheStr).getString("data"),
                            (Class<NETWORK_DATA>) GenericUtils.getGenericType(this));
                    if (data != null) {
                        onSuccess(data, true);
                    }
                    if (isNeedUpdate()) {
                        load();
                        return;
                    }
                } catch (JSONException e) {
                    // do nothing
                }
            }
            // 从预置数据中取数据
            if (mPreDefinedData != null) {
                NETWORK_DATA data = new Gson().fromJson(mPreDefinedData,
                        (Class<NETWORK_DATA>) GenericUtils.getGenericType(this));
                if (data != null) {
                    onSuccess(data, true);
                }
            }
        }
        load();
    }
}

子 Model 中传入预置数据

public class NewsChannelModel extends BaseMvvmModel<NewsChannelsBean, List<NewsChannelsBean.ChannelList>> {

    public static final String PREDEFINED_CHANNELS = "{"showapi_res_body":{"channelList":[{"channelId":"5572a108b3cdc86cf39001cd","name":"国内焦点"},{"channelId":"5572a108b3cdc86cf39001ce","name":"国际焦点"},{"channelId":"5572a108b3cdc86cf39001cf","name":"军事焦点"},{"channelId":"5572a108b3cdc86cf39001d0","name":"财经焦点"},{"channelId":"5572a108b3cdc86cf39001d1","name":"互联网焦点"},{"channelId":"5572a108b3cdc86cf39001d2","name":"房产焦点"},{"channelId":"5572a108b3cdc86cf39001d3","name":"汽车焦点"},{"channelId":"5572a108b3cdc86cf39001d4","name":"体育焦点"},{"channelId":"5572a108b3cdc86cf39001d5","name":"娱乐焦点"},{"channelId":"5572a108b3cdc86cf39001d6","name":"游戏焦点"},{"channelId":"5572a108b3cdc86cf39001d7","name":"教育焦点"},{"channelId":"5572a108b3cdc86cf39001d8","name":"女人焦点"},{"channelId":"5572a108b3cdc86cf39001d9","name":"科技焦点"},{"channelId":"5572a109b3cdc86cf39001da","name":"社会焦点"},{"channelId":"5572a109b3cdc86cf39001db","name":"国内最新"},{"channelId":"5572a109b3cdc86cf39001dc","name":"台湾最新"},{"channelId":"5572a109b3cdc86cf39001dd","name":"港澳最新"},{"channelId":"5572a109b3cdc86cf39001de","name":"国际最新"},{"channelId":"5572a109b3cdc86cf39001df","name":"军事最新"},{"channelId":"5572a109b3cdc86cf39001e0","name":"财经最新"},{"channelId":"5572a109b3cdc86cf39001e1","name":"理财最新"},{"channelId":"5572a109b3cdc86cf39001e2","name":"宏观经济最新"},{"channelId":"5572a109b3cdc86cf39001e3","name":"互联网最新"},{"channelId":"5572a109b3cdc86cf39001e4","name":"房产最新"},{"channelId":"5572a109b3cdc86cf39001e5","name":"汽车最新"},{"channelId":"5572a109b3cdc86cf39001e6","name":"体育最新"},{"channelId":"5572a10ab3cdc86cf39001e7","name":"国际足球最新"},{"channelId":"5572a10ab3cdc86cf39001e8","name":"国内足球最新"},{"channelId":"5572a10ab3cdc86cf39001e9","name":"CBA最新"},{"channelId":"5572a10ab3cdc86cf39001ea","name":"综合体育最新"},{"channelId":"5572a10ab3cdc86cf39001eb","name":"娱乐最新"},{"channelId":"5572a10ab3cdc86cf39001ec","name":"电影最新"},{"channelId":"5572a10ab3cdc86cf39001ed","name":"电视最新"},{"channelId":"5572a10ab3cdc86cf39001ee","name":"游戏最新"},{"channelId":"5572a10ab3cdc86cf39001ef","name":"教育最新"},{"channelId":"5572a10ab3cdc86cf39001f0","name":"女人最新"},{"channelId":"5572a10ab3cdc86cf39001f1","name":"美容护肤最新"},{"channelId":"5572a10ab3cdc86cf39001f2","name":"情感两性最新"},{"channelId":"5572a10ab3cdc86cf39001f3","name":"健康养生最新"},{"channelId":"5572a10ab3cdc86cf39001f4","name":"科技最新"},{"channelId":"5572a10bb3cdc86cf39001f5","name":"数码最新"},{"channelId":"5572a10bb3cdc86cf39001f6","name":"电脑最新"},{"channelId":"5572a10bb3cdc86cf39001f7","name":"科普最新"},{"channelId":"5572a10bb3cdc86cf39001f8","name":"社会最新"},{"channelId":"57463656a44a13cf","name":"旅游最新"},{"channelId":"9a15d3d4b5cab17f34e6d7976b1b1f30","name":"电商最新"},{"channelId":"5b4fe11ec1239b0805568da6e90fd875","name":"物流最新"},{"channelId":"3f4794412eaa17ed","name":"创业最新"}],"ret_code":0,"totalNum":48},"showapi_res_code":0,"showapi_res_error":""}";

    public NewsChannelModel() {
        super(false, "NEWS_CHANNEL_PREF_KEY", PREDEFINED_CHANNELS);
    }
}

运行效果如下,可以看到界面上不那么空白了,通过预置数据;

image.png

Jetpack 的 ViewModel + LiveData

千呼万唤,ViewModel 来了,接下来我们要引入 ViewModel 用来监听网络数据的返回,以及结合 LiveData 实现数据更新的处理;

我们分别定义两个 ViewModel,以及对应的 ViewModel 中声明 LiveData 来观测数据的变化并及时更新到UI

HeadlineNewsViewModel 持有 LiveData 和 Model,并实现 IBaseModelListener 来接受网络请求回调结果,待结果返回之后,由 LiveData 将数据 post 到 View 层;

public class HeadlineNewsViewModel extends ViewModel implements IBaseModelListener<List<NewsChannelsBean.ChannelList>> {

    private NewsChannelModel model;
    public MutableLiveData<List<NewsChannelsBean.ChannelList>> dataList = new MutableLiveData<>();

    public HeadlineNewsViewModel() {
        model = new NewsChannelModel();
        model.register(this);
        model.getCachedDataAndLoad();
    }

    @Override
    public void onLoadSuccess(BaseMvvmModel model, List<NewsChannelsBean.ChannelList> channelLists, PageResult... pageResults) {
        if (model instanceof NewsChannelModel) {
            dataList.postValue(channelLists);
        }
    }

    @Override
    public void onLoadFail(int errorCode, String errorMsg) {

    }
}

HeadlineNewsFragment 中持有 ViewModel 并获取 LiveData 监听数据变化;

public class HeadlineNewsFragment extends Fragment {
    public HeadlineNewsFragmentAdapter adapter;
    FragmentHomeBinding viewDataBinding;
    private HeadlineNewsViewModel mHeadlineNewsViewModel = new HeadlineNewsViewModel();

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        viewDataBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_home, container, false);
        adapter = new HeadlineNewsFragmentAdapter(getChildFragmentManager());
        viewDataBinding.tablayout.setTabMode(TabLayout.MODE_SCROLLABLE);
        viewDataBinding.viewpager.setAdapter(adapter);
        viewDataBinding.tablayout.setupWithViewPager(viewDataBinding.viewpager);
        viewDataBinding.viewpager.setOffscreenPageLimit(1);
        mHeadlineNewsViewModel.dataList.observe(this, channelLists -> {
            if (adapter != null) {
                adapter.setChannels(channelLists);
            }
        });
        return viewDataBinding.getRoot();
    }
}

同样的 NewListViewModel 中也这样处理下,但是不同的地方在于这个页面需要分页和加载更多,ViewModel 提供对应的 refresh 和 loadNextPage,同时声明 ViewStatus 类型的 LiveData 用来更新界面状态;

public class NewsListViewModel extends ViewModel implements IBaseModelListener<ArrayList<BaseViewModel>> {

    private NewsListModel model;
    public MutableLiveData<ArrayList<BaseViewModel>> dataList = new MutableLiveData<>();
    public MutableLiveData<ViewStatus> viewStatus = new MutableLiveData<>();

    public NewsListViewModel(String channelId, String channelName) {
        dataList.setValue(new ArrayList());
        model = new NewsListModel(channelId, channelName);
        model.register(this);
        model.getCachedDataAndLoad();
    }

    public void refresh() {
        model.refresh();
    }

    public void loadNextPage() {
        model.loadNextPage();
    }

    @Override
    public void onLoadSuccess(BaseMvvmModel model, ArrayList<BaseViewModel> baseViewModels, PageResult... pageResults) {
        if (model instanceof NewsListModel) {
            if (pageResults[0].isFirstPage) {
                dataList.getValue().clear();
            }
            if (pageResults[0].isEmpty) {
                if (pageResults[0].isFirstPage) {
                    viewStatus.postValue(ViewStatus.EMPTY);
                } else {
                    viewStatus.postValue(ViewStatus.NO_MORE_DATA);
                }
            } else {
                dataList.getValue().addAll(baseViewModels);
                dataList.postValue(dataList.getValue());
                viewStatus.postValue(ViewStatus.SHOW_CONTENT);
            }
        }
    }

    @Override
    public void onLoadFail(int errorCode, String errorMsg) {

    }
}

NewListFragment 中获取 dataList 并 observer

public class NewsListFragment extends Fragment {
    private NewsListRecyclerViewAdapter mAdapter;
    private FragmentNewsBinding viewDataBinding;
    private NewsListViewModel mNewsListViewModel;
    ArrayList<BaseViewModel> contentList = new ArrayList<>();
    protected final static String BUNDLE_KEY_PARAM_CHANNEL_ID = "bundle_key_param_channel_id";
    protected final static String BUNDLE_KEY_PARAM_CHANNEL_NAME = "bundle_key_param_channel_name";

    public static NewsListFragment newInstance(String channelId, String channelName) {
        NewsListFragment fragment = new NewsListFragment();
        Bundle bundle = new Bundle();
        bundle.putString(BUNDLE_KEY_PARAM_CHANNEL_ID, channelId);
        bundle.putString(BUNDLE_KEY_PARAM_CHANNEL_NAME, channelName);
        fragment.setArguments(bundle);
        return fragment;
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        viewDataBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_news, container, false);
        mAdapter = new NewsListRecyclerViewAdapter(getContext());
        viewDataBinding.listview.setHasFixedSize(true);
        viewDataBinding.listview.setLayoutManager(new LinearLayoutManager(getContext()));
        viewDataBinding.listview.setAdapter(mAdapter);
        mNewsListViewModel = new NewsListViewModel(getArguments().getString(BUNDLE_KEY_PARAM_CHANNEL_ID),
                getArguments().getString(BUNDLE_KEY_PARAM_CHANNEL_NAME));
        mNewsListViewModel.dataList.observe(this, baseViewModels -> {
            mAdapter.setData(contentList);
            viewDataBinding.refreshLayout.finishRefresh();
            viewDataBinding.refreshLayout.finishLoadMore();
        });
        viewDataBinding.refreshLayout.setOnRefreshListener(refreshLayout -> mNewsListViewModel.refresh());
        viewDataBinding.refreshLayout.setOnLoadMoreListener(refreshLayout -> mNewsListViewModel.loadNextPage());
        return viewDataBinding.getRoot();
    }
}

BaseMvvmViewModel


我们前面是用来 ViewModel,但是没有抽取一个 base 层,它们其实是有公共的地方可以抽取出来的,我们来创建 BaseMvvmViewModel 并重构下;

public abstract class BaseMvvmViewModel<MODEL extends BaseMvvmModel, DATA> extends ViewModel implements IBaseModelListener<List<DATA>> {

    public MutableLiveData<List<DATA>> dataList = new MutableLiveData<>();
    public MutableLiveData<ViewStatus> viewStatus = new MutableLiveData<>();
    protected MODEL model;

    public BaseMvvmViewModel() {
        // viewStatus.postValue(ViewStatus.LOADING);
    }

    public abstract MODEL createModel();

    public void getCacheDataAndLoad() {
        createAndRegisterModel();
        if (model != null) {
            model.getCachedDataAndLoad();
        }
    }

    public void refresh() {
        createAndRegisterModel();
        if (model != null) {
            model.refresh();
        }
    }

    public void loadNextPage() {
        createAndRegisterModel();
        if (model != null) {
            model.loadNextPage();
        }
    }

    /**
     * activity/fragment 销毁的时候会调用 viewmodel 的这个方法,进行业务处理
     */
    @Override
    protected void onCleared() {
        super.onCleared();
    }

    private void createAndRegisterModel() {
        if (model == null) {
            model = createModel();
            model.register(this);
        }
    }

    @Override
    public void onLoadSuccess(BaseMvvmModel model, List<DATA> data, PageResult... pageResults) {
        if (model.isPaging()) {
            if (pageResults[0].isEmpty) {
                if (pageResults[0].isFirstPage) {
                    viewStatus.postValue(ViewStatus.EMPTY);
                } else {
                    viewStatus.postValue(ViewStatus.NO_MORE_DATA);
                }
            } else {
                if (pageResults[0].isFirstPage) {
                    dataList.postValue(data);
                } else {
                    dataList.getValue().addAll(data);
                    dataList.postValue(dataList.getValue());
                }
                viewStatus.postValue(ViewStatus.SHOW_CONTENT);
            }
        } else {
            dataList.postValue(data);
            viewStatus.postValue(ViewStatus.SHOW_CONTENT);
        }
    }

    @Override
    public void onLoadFail(int errorCode, String errorMsg) {
        viewStatus.postValue(ViewStatus.REFRESH_ERROR);
    }
}

子 ViewModel 继承这个 BaseMvvmViewModel,并修改相关逻辑;

public class NewsListViewModel extends BaseMvvmViewModel<NewsListModel, BaseViewModel> {

    private final String mChannelId;
    private final String mChannelName;

    public NewsListViewModel(String channelId, String channelName) {
        this.mChannelId = channelId;
        this.mChannelName = channelName;
    }

    @Override
    public NewsListModel createModel() {
        return new NewsListModel(mChannelId, mChannelName);
    }

    public void refresh() {
        model.refresh();
    }

    public void loadNextPage() {
        model.loadNextPage();
    }
}

HeadlineNewsViewModel 重构如下:

public class HeadlineNewsViewModel extends BaseMvvmViewModel<NewsChannelModel, NewsChannelsBean.ChannelList> {

    @Override
    public NewsChannelModel createModel() {
        return new NewsChannelModel();
    }
}

因为删除了构造方法中的调用 model 层的逻辑,所以需要我们在 Fragment 中手动调用一下数据的拉取;

mNewsListViewModel = new NewsListViewModel(getArguments().getString(BUNDLE_KEY_PARAM_CHANNEL_ID),
        getArguments().getString(BUNDLE_KEY_PARAM_CHANNEL_NAME));
mNewsListViewModel.getCacheDataAndLoad();
mHeadlineNewsViewModel = new HeadlineNewsViewModel();
mHeadlineNewsViewModel.getCacheDataAndLoad();

运行,可以正常的拉取数据并展示,但是,我们在使用 ViewPager + Fragment 的时候,通常都是懒加载的方式,也就是 Fragment 可见的时候,我们才会 load 并展示数据,我们来实现一下懒加载;

懒加载

这里我们借助 LifeCycle 来监听页面 onResume 的时候,来进行数据的加载;因此我们的 BaseMvvmViewModel 需要实现 LifeCycleObserver 接口;

public abstract class BaseMvvmViewModel<MODEL extends BaseMvvmModel, DATA> extends ViewModel implements LifecycleObserver, IBaseModelListener<List<DATA>> {

    // ...
    // 省略部分代码
    
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    private void onResume() {
        if (dataList == null || dataList.getValue() == null || dataList.getValue().isEmpty()) {
            createAndRegisterModel();
            model.getCachedDataAndLoad();
        } else {
            dataList.postValue(dataList.getValue());
            viewStatus.postValue(viewStatus.getValue());
        }
    }
}

然后,对应的 Fragment 中添加下 observer,同时也不用主动调用了 getCacheDataAndLoad 方法了;

getLifecycle().addObserver(mNewsListViewModel);

HeadlineNewsFragment

getLifecycle().addObserver(mHeadlineNewsViewModel);

BaseView

到这里的时候,可以看到,我们的 base 下的 mvvm 中 BaseMvvmModel 和 BaseMvvmViewModel 都有了,就差 BaseMvvmView 了,我们来抽取下;

image.png

public abstract class BaseMvvmFragment<VIEW extends ViewDataBinding, VIEWMODEL extends BaseMvvmViewModel, DATA> extends Fragment implements Observer {

    protected VIEWMODEL viewmodel;
    protected VIEW viewDataBinding;

    public abstract int getLayoutId();
    public abstract VIEWMODEL getViewmodel();
    public abstract String getFragmentTag();
    public abstract void onNetworkResponded(List<DATA> dataList, boolean isDataUpdated);
    public abstract void onViewCreated();

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(getFragmentTag(), "onCreate");
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        viewDataBinding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false);
        return viewDataBinding.getRoot();
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        viewmodel = getViewmodel();
        getLifecycle().addObserver(viewmodel);
        viewmodel.dataList.observe(this, this);
        viewmodel.viewStatus.observe(this, this);
        onViewCreated();
    }

    @Override
    public void onChanged(Object o) {
        if (o instanceof ArrayList) {
            onNetworkResponded((List<DATA>) o, true);
        }
    }
}

对应的子 Fragment 继承这个 Base 然后重写对应的实现即可;

public class NewsListFragment extends BaseMvvmFragment<FragmentNewsBinding, NewsListViewModel, BaseViewModel> {
    private NewsListRecyclerViewAdapter mAdapter;
    ArrayList<BaseViewModel> contentList = new ArrayList<>();
    protected final static String BUNDLE_KEY_PARAM_CHANNEL_ID = "bundle_key_param_channel_id";
    protected final static String BUNDLE_KEY_PARAM_CHANNEL_NAME = "bundle_key_param_channel_name";

    public static NewsListFragment newInstance(String channelId, String channelName) {
        NewsListFragment fragment = new NewsListFragment();
        Bundle bundle = new Bundle();
        bundle.putString(BUNDLE_KEY_PARAM_CHANNEL_ID, channelId);
        bundle.putString(BUNDLE_KEY_PARAM_CHANNEL_NAME, channelName);
        fragment.setArguments(bundle);
        return fragment;
    }

    @Override
    public int getLayoutId() {
        return  R.layout.fragment_news;
    }

    @Override
    public NewsListViewModel getViewmodel() {
        return viewmodel = new NewsListViewModel(getArguments().getString(BUNDLE_KEY_PARAM_CHANNEL_ID),
                getArguments().getString(BUNDLE_KEY_PARAM_CHANNEL_NAME));
    }

    @Override
    public String getFragmentTag() {
        return getArguments().getString(BUNDLE_KEY_PARAM_CHANNEL_ID);
    }

    @Override
    public void onNetworkResponded(List<BaseViewModel> baseViewModels, boolean isDataUpdated) {
        if (isDataUpdated) {
            mAdapter.setData(contentList);
            viewDataBinding.refreshLayout.finishRefresh();
            viewDataBinding.refreshLayout.finishLoadMore();
        }
    }

    @Override
    public void onViewCreated() {
        mAdapter = new NewsListRecyclerViewAdapter(getContext());
        viewDataBinding.listview.setHasFixedSize(true);
        viewDataBinding.listview.setLayoutManager(new LinearLayoutManager(getContext()));
        viewDataBinding.listview.setAdapter(mAdapter);

        viewDataBinding.refreshLayout.setOnRefreshListener(refreshLayout -> viewmodel.refresh());
        viewDataBinding.refreshLayout.setOnLoadMoreListener(refreshLayout -> viewmodel.loadNextPage());
    }
}

ViewModelProvider

这里我们不能每次都去创建 ViewModel,否则就会导致每次都重新请求数据,这里借助 ViewModelProvider 来控制 ViewModel 的创建;这里直接上代码了

public static class NewsListViewModelFactory implements ViewModelProvider.Factory {
    private String mChannelId;
    private String mChannelName;

    public NewsListViewModelFactory(String channelId, String channelName) {
        this.mChannelId = channelId;
        this.mChannelName = channelName;
    }

    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        return (T) new NewsListViewModel(mChannelId, mChannelName);
    }
}

Fragment 中 createViewModel 重构如下:

@Override
public NewsListViewModel getViewmodel() {
    return new ViewModelProvider(getActivity(), new NewsListViewModel.NewsListViewModelFactory(getArguments().getString(BUNDLE_KEY_PARAM_CHANNEL_ID),
            getArguments().getString(BUNDLE_KEY_PARAM_CHANNEL_NAME))).get(getArguments().getString(BUNDLE_KEY_PARAM_CHANNEL_ID), NewsListViewModel.class);
}

好了,MVVM 的上中下就全部讲完了;

上篇

中篇

欢迎三连


来都来了,点个关注,点个赞吧,你的支持是我最大的动力~~

  • 29
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值