如何应对 Android 面试官 -> MVVM 实战一个新闻客户端 (中)
前言
本章我们基于重构的方式进行一个 MVVM 的实战,我们将一个新闻列表的普通实现,一步一步的改造成 MVVM 的架构模式,一共分为上中下三个章节,本章继续上一章,开始中篇的讲解;
控件化
我们本章向控件化进一步迈进
BaseView 重构
我们上一章,将 TitleView 和 PictureTitleView 抽取了一个 BaseView 来抽取公共的 setData 逻辑,我们还可以继续精进一步;
我们现将我们上一章定义的接口 BaseView 重命名成 IBaseView
public interface IBaseView<DATA extends BaseViewModel> {
void setData(DATA data);
}
然后我们定义一个 IBaseView 的实现类,BaseView
public abstract class BaseView extends LinearLayout implements IBaseView<BaseViewModel> {
public BaseView(Context context) {
super(context);
}
public BaseView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public BaseView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public BaseView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public void setData(BaseViewModel baseViewModel) {
}
}
然后我们定义公共的 init 方法,方法中我们进行布局的加载,但是布局 id 我们无法从自身获取,需要通过 工厂方法 模式,提供一个抽象接口来从子类中获取布局 id;同时,通过 泛型 来接收 DataBinding 在 inflater 之后返回的 ViewDataBinding 对象,整体如下:
public abstract class BaseView<VIEW_BINDING extends ViewDataBinding> extends LinearLayout implements IBaseView<BaseViewModel> {
protected VIEW_BINDING mBinding;
private void init() {
LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mBinding = DataBindingUtil.inflate(layoutInflater, getLayoutId(), this, false);
addView(mBinding.getRoot());
}
public abstract int getLayoutId();
}
通常,View 可能会需要一个点击事件,我们这里也提供一下
private void init() {
LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mBinding = DataBindingUtil.inflate(layoutInflater, getLayoutId(), this, false);
mBinding.getRoot().setOnClickListener(this::onRootClick);
addView(mBinding.getRoot());
}
public abstract int getLayoutId();
public abstract void onRootClick(View view);
然后,我们来实现 setData 部分,这里我们将泛型的实现交给 BaseView
public abstract class BaseView<VIEW_BINDING extends ViewDataBinding, DATA extends BaseViewModel> extends LinearLayout implements IBaseView<DATA> {
}
然后,我们发现,当我们在 setData 中去 setViewModel 的时候,报错了,并没有 setViewModel 的接口,那么我们就需要进行剥离出来,交给子类实现;
public abstract class BaseView<VIEW_BINDING extends ViewDataBinding, DATA extends BaseViewModel> extends LinearLayout implements IBaseView<DATA> {
@Override
public void setData(DATA baseViewModel) {
setDataToView(baseViewModel);
mBinding.executePendingBindings();
}
public abstract void setDataToView(DATA data);
}
我们接下来重构 TitleView 和 PictureTitleView,来让它们继承 BaseView
public class TitleView extends BaseView<TitleViewBinding, TitleViewModel> {
public TitleView(Context context) {
super(context);
}
@Override
public int getLayoutId() {
return R.layout.title_view;
}
@Override
public void onRootClick(View view) {
}
@Override
public void setDataToView(TitleViewModel titleViewModel) {
mBinding.setTitleViewModel(titleViewModel);
}
}
PictureTitleView
public class PictureTitleView extends BaseView<PictureTitleViewBinding, PictureTitleViewModel> {
public PictureTitleView(Context context) {
super(context);
}
@Override
public int getLayoutId() {
return R.layout.picture_title_view;
}
@Override
public void onRootClick(View view) {
}
@Override
public void setDataToView(PictureTitleViewModel pictureTitleViewModel) {
mBinding.setPictureTitleViewModel(pictureTitleViewModel);
}
@BindingAdapter("loadImageUrl")
public static void loadImageUrl(ImageView imageView, String imgUrl) {
Glide.with(imageView.getContext())
.load(imgUrl)
.transition(withCrossFade())
.into(imageView);
}
}
可以看到,我们的 TitleView 和 PictureTitleView 也清爽了很多;到这里,对齐了我们在讲 MVx 的时候的 控件化 的重要性;
Model
我们接下来,终于可以向 MVVM 的架构来迈进了,我们先来看下我们的 Fragment,一开始我们把数据的加载直接放在了 Fragment 中,这其实并不合理,我们需要将数据的获取放到 model 层,我们来进行重构;
首先我们在 base 层定义下我们的 baseModel,创建一个 IBaseModelListener,用来将 model 获取的数据回调到 View
public interface IBaseModelListener<DATA> {
void onLoadSuccess(DATA data);
void onLoadFail(int errorCode, String errorMsg);
}
然后我们定义一个我们用来获取频道列表的 model
public class NewsChannelModel {
private IBaseModelListener<List<NewsChannelsBean.ChannelList>> mListener;
public NewsChannelModel(IBaseModelListener<List<NewsChannelsBean.ChannelList>> mListener) {
this.mListener = mListener;
}
public void load() {
TecentNetworkApi.getService(NewsApiInterface.class)
.getNewsChannels()
.compose(TecentNetworkApi.getInstance().applySchedulers(new BaseObserver<NewsChannelsBean>() {
@Override
public void onSuccess(NewsChannelsBean newsChannelsBean) {
mListener.onLoadSuccess(newsChannelsBean.showapiResBody.channelList);
}
@Override
public void onFailure(Throwable e) {
e.printStackTrace();
mListener.onLoadFail(400, e.getMessage());
}
}));
}
}
然后,我们在 Fragment 中调用这个 model 的 load 方法;
public class HeadlineNewsFragment extends Fragment implements IBaseModelListener<List<NewsChannelsBean.ChannelList>> {
public HeadlineNewsFragmentAdapter adapter;
private FragmentHomeBinding viewDataBinding;
private NewsChannelModel model;
@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);
model = new NewsChannelModel(this);
model.load();
return viewDataBinding.getRoot();
}
@Override
public void onLoadSuccess(List<NewsChannelsBean.ChannelList> channelLists) {
if (adapter != null) {
adapter.setChannels(channelLists);
}
}
@Override
public void onLoadFail(int errorCode, String errorMsg) {}
}
Fragment 也清爽了很多,我们接下来重构下 NewsListFragment,重构逻辑一样,我们封装一个 NewListModel
public class NewsListModel {
private IBaseModelListener<ArrayList<BaseViewModel>> mListener;
private int mPageNum;
private String mChannelId;
private String mChannelName;
public NewsListModel(IBaseModelListener<ArrayList<BaseViewModel>> mListener, String channelId, String channelName) {
this.mListener = mListener;
this.mChannelId = channelId;
this.mChannelName = channelName;
}
public void load() {
TecentNetworkApi.getService(NewsApiInterface.class)
.getNewsList(mChannelId,
mChannelName, String.valueOf(mPageNum))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Function<NewsListBean, ObservableSource<ArrayList<BaseViewModel>>>() {
@Override
public ObservableSource<ArrayList<BaseViewModel>> apply(NewsListBean newsChannelsBean) throws Exception {
ArrayList<BaseViewModel> viewModels = new ArrayList<>();
for (NewsListBean.Contentlist contentlist : newsChannelsBean.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);
}
}
return Observable.just(viewModels);
}
})
.subscribe(new Consumer<ArrayList<BaseViewModel>>() {
@Override
public void accept(ArrayList<BaseViewModel> baseViewModels) throws Exception {
mPageNum++;
mListener.onLoadSuccess(baseViewModels);
}
});
}
public void refresh() {
mPageNum = 1;
load();
}
}
然后 NewListFragment 重构如下:
public class NewsListFragment extends Fragment implements IBaseModelListener<ArrayList<BaseViewModel>> {
private NewsListRecyclerViewAdapter mAdapter;
private FragmentNewsBinding viewDataBinding;
private NewsListModel mNewsListModel;
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);
mNewsListModel = new NewsListModel(this, getArguments().getString(BUNDLE_KEY_PARAM_CHANNEL_ID),
getArguments().getString(BUNDLE_KEY_PARAM_CHANNEL_NAME));
mNewsListModel.load();
viewDataBinding.refreshLayout.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh(@NonNull RefreshLayout refreshLayout) {
mNewsListModel.refresh();
}
});
viewDataBinding.refreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {
@Override
public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
mNewsListModel.load();
}
});
return viewDataBinding.getRoot();
}
@Override
public void onLoadSuccess(ArrayList<BaseViewModel> baseViewModels) {
contentList.addAll(baseViewModels);
mAdapter.setData(contentList);
viewDataBinding.refreshLayout.finishRefresh();
viewDataBinding.refreshLayout.finishLoadMore();
}
@Override
public void onLoadFail(int errorCode, String errorMsg) {
}
}
我们这个页面的数据加载,涉及到了分页的逻辑,我们需要将回调的结果带上分页的结果,要告知 View 是加载的哪一页,我们需要一个分页结果;
public class PageResult {
private boolean isFirstPage;
private boolean isEmpty;
private boolean hasNextPage;
public PageResult(boolean isFirstPage, boolean isEmpty, boolean hasNextPage) {
this.isFirstPage = isFirstPage;
this.isEmpty = isEmpty;
this.hasNextPage = hasNextPage;
}
}
然后,我们修改回调结果的 Listener,这个接口是公共的,也就是说,有的需要分页结果,有的不需要分页结果,那么就需要一个可变参数;
public interface IBaseModelListener<DATA> {
void onLoadSuccess(DATA data, PageResult... pageResults);
void onLoadFail(int errorCode, String errorMsg);
}
然后,NewsListModel 的回调带上这个 PageResult;
mListener.onLoadSuccess(baseViewModels,
new PageResult(mPageNum == 1, baseViewModels.isEmpty(), baseViewModels.size() >= 10));
onLoadSuccess 的回调改造如下:
@Override
public void onLoadSuccess(ArrayList<BaseViewModel> baseViewModels, PageResult... pageResults) {
if (pageResults != null && pageResults.length > 0 && pageResults[0].isFirstPage) {
contentList.clear();
}
contentList.addAll(baseViewModels);
mAdapter.setData(contentList);
viewDataBinding.refreshLayout.finishRefresh();
viewDataBinding.refreshLayout.finishLoadMore();
}
到这的时候,我们的 model 层就抽离出来了,可能看到这的时候,好多人就疑问了,你这也没使用 Jetpack 的 ViewModel 和 LiveData 呀,别着急,我们精彩的还在后面;
BaseModel
可以看到,NewChannelModel 和 NewListModel 中都有 IBaseModelListener,这个是可以抽取到 base 层的,所以我们可以创建一个 BaseMvvmModel 来抽取它;
public abstract class BaseMvvmModel {
protected WeakReference<IBaseModelListener> mReferenceIBaseModeListener;
public void register(IBaseModelListener listener) {
if (listener != null) {
mReferenceIBaseModeListener = new WeakReference<>(listener);
}
}
}
然后,NewListModel 和 NewsChannelModel 分别继承 BaseMvvmModel,同时移除子类 model 中的 IBaseModelListener 的声明;
public class NewsListModel extends BaseMvvmModel {
private int mPageNum;
private final String mChannelId;
private final String mChannelName;
public NewsListModel(String channelId, String channelName) {
this.mChannelId = channelId;
this.mChannelName = channelName;
}
public void load() {
TecentNetworkApi.getService(NewsApiInterface.class)
.getNewsList(mChannelId,
mChannelName, String.valueOf(mPageNum))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Function<NewsListBean, ObservableSource<ArrayList<BaseViewModel>>>() {
@Override
public ObservableSource<ArrayList<BaseViewModel>> apply(NewsListBean newsChannelsBean) throws Exception {
ArrayList<BaseViewModel> viewModels = new ArrayList<>();
for (NewsListBean.Contentlist contentlist : newsChannelsBean.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);
}
}
return Observable.just(viewModels);
}
})
.subscribe(new Consumer<ArrayList<BaseViewModel>>() {
@Override
public void accept(ArrayList<BaseViewModel> baseViewModels) throws Exception {
mPageNum++;
mReferenceIBaseModeListener.get().onLoadSuccess(baseViewModels,
new PageResult(mPageNum == 1, baseViewModels.isEmpty(), baseViewModels.size() >= 10));
}
});
}
public void refresh() {
mPageNum = 1;
load();
}
}
NewsChannelModel 重构如下:
public class NewsChannelModel extends BaseMvvmModel {
public NewsChannelModel() {
}
public void load() {
TecentNetworkApi.getService(NewsApiInterface.class)
.getNewsChannels()
.compose(TecentNetworkApi.getInstance().applySchedulers(new BaseObserver<NewsChannelsBean>() {
@Override
public void onSuccess(NewsChannelsBean newsChannelsBean) {
mReferenceIBaseModeListener.get().onLoadSuccess(newsChannelsBean.showapiResBody.channelList);
}
@Override
public void onFailure(Throwable e) {
e.printStackTrace();
mReferenceIBaseModeListener.get().onLoadFail(400, e.getMessage());
}
}));
}
}
然后对应的 Fragment 也需要重构下:
mNewsListModel = new NewsListModel(getArguments().getString(BUNDLE_KEY_PARAM_CHANNEL_ID),
getArguments().getString(BUNDLE_KEY_PARAM_CHANNEL_NAME));
mNewsListModel.register(this);
model = new NewsChannelModel();
model.register(this);
然后,我们的 page 其实也可以抽取到 base 层,通过可变参数的形式来决定分页的内容以及是否要分页,所以 BaseMvvmModel 继续重构;
public abstract class BaseMvvmModel {
protected WeakReference<IBaseModelListener> mReferenceIBaseModeListener;
protected boolean mIsPaging;
protected final int INIT_PAGE_NUMBER;
public BaseMvvmModel(boolean isPaging, int... initPageNumber) {
if (isPaging && initPageNumber != null && initPageNumber.length > 0) {
INIT_PAGE_NUMBER = initPageNumber[0];
} else {
INIT_PAGE_NUMBER = -1;
}
}
public void register(IBaseModelListener listener) {
if (listener != null) {
mReferenceIBaseModeListener = new WeakReference<>(listener);
}
}
}
子类实现中,也需要重构下
NewsChannelModel 的构造方法重构如下:
public NewsChannelModel() {
super(false);
}
NewsListModel 的构造方法重构如下:
public NewsListModel(String channelId, String channelName) {
super(true, 1);
this.mChannelId = channelId;
this.mChannelName = channelName;
}
到这里的时候,有人可能会问,INIT_PAGE_NUMBER 没有用到呀,定义它做什么?别急,它来了,我们其实也可以把 NewListModel 中的 refresh 和 load 提取到 base 层;
public abstract class BaseMvvmModel {
public void refresh() {
mPageNum = INIT_PAGE_NUMBER;
load();
}
public abstract void load();
}
这样,我们定义的 INIT_PAGE_NUMBER 是不是就使用到了;然后我们还可以给 refresh 加上多次加载控制,如果正在加载中则不触发二次请求,所以需要我们定义一个变量来控制是否正在加载,同时我们来区分下加载下一页和加载;
public abstract class BaseMvvmModel {
protected boolean isLoading;
public void refresh() {
if (!isLoading) {
if (mIsPaging) {
mPageNum = INIT_PAGE_NUMBER;
}
isLoading = true;
load();
}
}
public void loadNextPage() {
if (!isLoading) {
isLoading = true;
load();
}
}
}
另外,我们在子类 model 中分别操作了 BaseMvvmModel 中的 mPageNum 和 mReferenceIBaseModeListener 这两个的操作也是需要抽取到 base 层的,我们来继续重构;
public abstract class BaseMvvmModel<RESULT_DATA> {
//
protected void notifyResultToListener(RESULT_DATA data) {
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 (data != null && ((List)data).size() > 0) {
mPageNum ++;
}
}
isLoading = false;
}
protected void loadFail(int errorCode, String errorMsg) {
IBaseModelListener listener = mReferenceIBaseModeListener.get();
if (listener != null) {
listener.onLoadFail(errorCode, errorMsg);
}
isLoading = false;
}
}
然后子类 model 中直接调用对应的 notifyResultToListener 和 loadFail
NewsListModel
subscribe(new Consumer<ArrayList<BaseViewModel>>() {
@Override
public void accept(ArrayList<BaseViewModel> baseViewModels) throws Exception {
notifyResultToListener(baseViewModels);
}
});
NewsChannelModel
.compose(TecentNetworkApi.getInstance().applySchedulers(new BaseObserver<NewsChannelsBean>() {
@Override
public void onSuccess(NewsChannelsBean newsChannelsBean) {
notifyResultToListener(newsChannelsBean.showapiResBody.channelList);
}
@Override
public void onFailure(Throwable e) {
e.printStackTrace();
loadFail(404, e.getMessage());
}
}));
数据缓存
如果我们在无网的情况下打开 app,通常会展示一片空白,体验很不好,所以,就需要我们提前将数据缓存下来,当用户无网打开的时候,不至于显示一片白;
首先,我们来提供一个 CacheData;
public class BaseCachedData<DATA> {
public long updateTimeInMills;
public DATA data;
}
然后我们需要对 BaseMvvmModel 进行重构,增加缓存逻辑;
public abstract class BaseMvvmModel<NETWORK_DATA, RESULT_DATA> {
private String mCachedPreferenceKey;
private BaseCachedData<NETWORK_DATA> mData;
public BaseMvvmModel(boolean isPaging, String cachedPreferenceKey, int... initPageNumber) {
if (isPaging && initPageNumber != null && initPageNumber.length > 0) {
INIT_PAGE_NUMBER = initPageNumber[0];
} else {
INIT_PAGE_NUMBER = -1;
}
// 增加缓存key
this.mCachedPreferenceKey = cachedPreferenceKey;
}
protected void notifyResultToListener(NETWORK_DATA networkData, RESULT_DATA data) {
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) {
saveDataToPreference(networkData);
}
} else {
if (mCachedPreferenceKey != null) {
saveDataToPreference(networkData);
}
}
if (mIsPaging) {
if (data != null && ((List) data).size() > 0) {
mPageNum++;
}
}
}
private void saveDataToPreference(NETWORK_DATA networkData) {
if (networkData != null) {
BaseCachedData<NETWORK_DATA> baseCachedData = new BaseCachedData<>();
baseCachedData.data = networkData;
baseCachedData.updateTimeInMills = System.currentTimeMillis();
// 这里可以调换成自己的 spUtils
BasicDataPreferenceUtil.getInstance().setString(mCachedPreferenceKey, new Gson().toJson(baseCachedData));
}
}
}
同时 NewsChannelModel 和 NewsListModel 的构造方法以及 load 实现也需要重构下,增加需要缓存的数据的传入;
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>() {
@Override
public void onSuccess(NewsChannelsBean newsChannelsBean) {
notifyResultToListener(newsChannelsBean, newsChannelsBean.showapiResBody.channelList);
}
@Override
public void onFailure(Throwable e) {
e.printStackTrace();
mReferenceIBaseModeListener.get().onLoadFail(400, e.getMessage());
}
}));
}
}
NewsListModel 重构如下:
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())
.flatMap(new Function<NewsListBean, ObservableSource<ArrayList<BaseViewModel>>>() {
@Override
public ObservableSource<ArrayList<BaseViewModel>> apply(NewsListBean newsChannelsBean) throws Exception {
ArrayList<BaseViewModel> viewModels = new ArrayList<>();
NewsListModel.this.mNewsListBean = newsChannelsBean;
for (NewsListBean.Contentlist contentlist : newsChannelsBean.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);
}
}
return Observable.just(viewModels);
}
})
.subscribe(new Consumer<ArrayList<BaseViewModel>>() {
@Override
public void accept(ArrayList<BaseViewModel> baseViewModels) throws Exception {
notifyResultToListener(NewsListModel.this.mNewsListBean, baseViewModels);
}
});
}
}
OK,运行可以看到,数据已经存到了 SP 中;
好了,中篇文章就讲解到这里吧;
下一章预告
MVVM 实战一个新闻客户端 (下)
欢迎三连
来都来了,点个关注点个赞吧,你的支持是我前进的最大动力
转载自:https://juejin.cn/post/7383341578025992204