How does the ViewModel in MVVM notify the View of data changes?

There are three main methods:

  • Databinding -> ObservableArrayList and ObservableField
  • 2. LiveDate and SingleLiveEvent
  • 3. When instantiating the ViewModel in the View, pass the view instance to the ViewModel (this way uses weak references to avoid the risk of memory leaks)

For the first method, take ObservableField as an example

  • 1. First, the ViewModel is associated with databinding in layout.xml
<layout>
  <data>
    <variable
      name="viewModel"
      type="com.fly.tour.news.mvvm.viewmodel.NewsDetailViewModel" />
  </data>
  <LinearLayout 
  ...
    <TextView
      ...
      android:text="@{viewModel.mNewsDetails.content}" />
  </LinearLayout>
</layout>
Copy the code
  • 2. Bind the Layout to the ViewModel when instantiating the ViewModel
 private void initViewModel() {
       ...
        if(mBinding != null){
            mBinding.setVariable(viewModelId, mViewModel);
        }
       ...
    }
Copy the code
  • 3. Create an ObservableField instance in the ViewModel
 public ObservableField<NewsDetail> mNewsDetails = new ObservableField<>();
Copy the code
  • ObservableField. Set (data); Method notifies data of changes so that the layout is updated when the data changes
mModel.getNewsDetailById(id).subscribe(new Observer<RespDTO<NewsDetail>>() { ... @Override public void onNext(RespDTO<NewsDetail> newsDetailRespDTO) { NewsDetail newsDetail = newsDetailRespDTO.data; if (newsDetail ! = null) { mNewsDetails.set(newsDetail); } else {} } ... });Copy the code

ObservableArrayList is used to update lists.

This is done using the addOnListChangedCallback method for ObservableArrayList

BaseRefreshViewModel.java

 protected ObservableArrayList<T> mList = new ObservableArrayList<>();
Copy the code
  • Listening ObservableArrayList changes, and will list the Adapter is passed to the callback ObservableList. OnListChangedCallback

NewsTypeListActivity.java

 @Override
    public void initView() {
        mNewsTypeShowAdapter = new NewsTypeShowBindAdapter(this, mViewModel.getList());
        mViewModel.getList().addOnListChangedCallback(ObservableListUtil.getListChangedCallback(mNewsTypeShowAdapter));
        mBinding.recview.setAdapter(mNewsTypeShowAdapter);
    }
Copy the code
  • When the data changes, the callback function refreshes the interface by operating adapter.notifyxxx
public static ObservableList.OnListChangedCallback getListChangedCallback(final RecyclerView.Adapter adapter) { return new ObservableList.OnListChangedCallback() { @Override public void onChanged(ObservableList observableList) { adapter.notifyDataSetChanged(); } @Override public void onItemRangeChanged(ObservableList observableList, int i, int i1) { adapter.notifyItemRangeChanged(i, i1); } @Override public void onItemRangeInserted(ObservableList observableList, int i, int i1) { adapter.notifyItemRangeInserted(i, i1); } @Override public void onItemRangeMoved(ObservableList observableList, int i, int i1, int i2) { if (i2 == 1) { adapter.notifyItemMoved(i, i1); } else { adapter.notifyDataSetChanged(); } } @Override public void onItemRangeRemoved(ObservableList observableList, int i, int i1) { adapter.notifyItemRangeRemoved(i, i1); }}; }Copy the code
  • How does the data get updated to the interface? Again, through databing

NewsTypeShowBindAdapter.java

@Override protected void onBindItem(ItemNewsTypeShowBindingBinding binding, final NewsType item, int position) { ... binding.setNewsType(item); . }Copy the code

layout.xml

<layout> <data> <variable name="newsType" type="com.fly.tour.api.newstype.entity.NewsType"/> </data> <RelativeLayout> . <TextView ... android:text="@={newsType.typename}" /> </RelativeLayout> </layout>Copy the code

For LiveDate and SingleLiveEvent, SingleLiveEvent is used as an example

  • 1. Defined in BaseViewModel SingleLiveEvent: showNetWorkErrViewEvent

BaseViewModel.java

public final class UIChangeLiveData extends SingleLiveEvent { private SingleLiveEvent<Boolean> showNetWorkErrViewEvent; . public SingleLiveEvent<Boolean> getShowInitLoadViewEvent() { return showInitLoadViewEvent = createLiveData(showInitLoadViewEvent); }Copy the code
protected SingleLiveEvent createLiveData(SingleLiveEvent liveData) {
        if (liveData == null) {
            liveData = new SingleLiveEvent();
        }
        return liveData;
    }
Copy the code
  • 2. Register an observer in BaseMvvmActivity to respond when the event is notified in the ViewModel

BaseMvvmActivity.java

protected void initBaseViewObservable() { mViewModel.getUC().getShowNetWorkErrViewEvent().observe(this, new Observer<Boolean>() { @Override public void onChanged(@Nullable Boolean show) { showNetWorkErrView(show); }}); }Copy the code
  • 3. Before obtaining remote data in NewsDetailViewModel, send this event when the network is disconnected

NewsDetailViewModel.java

 public void getNewsDetailById(final int id) {
        if (!NetUtil.checkNetToast()) {
            postShowNetWorkErrViewEvent(true);
            return;
        }
 }
Copy the code
public void postShowNetWorkErrViewEvent(boolean show) {
        if (mUIChangeLiveData != null) {
            mUIChangeLiveData.showNetWorkErrViewEvent.postValue(show);
        }
    }
Copy the code

SingleLiveEvent.java

public class SingleLiveEvent<T> extends MutableLiveData<T> { private static final String TAG = "SingleLiveEvent"; private final AtomicBoolean mPending = new AtomicBoolean(false); @MainThread public void observe(LifecycleOwner owner, final Observer<T> observer) { if (hasActiveObservers()) { Log.w(TAG, "Multiple observers registered but only one will be notified of changes."); } // Observe the internal MutableLiveData super.observe(owner, new Observer<T>() { @Override public void onChanged(@Nullable T t) { if (mPending.compareAndSet(true, false)) { observer.onChanged(t); }}}); } @MainThread public void setValue(@Nullable T t) { mPending.set(true); super.setValue(t); } /** * Used for cases where T is Void, to make calls cleaner. */ @MainThread public void call() { setValue(null); }}Copy the code

Why SingleLiveEvent instead of MutableLiveData directly

Features of SingleLiveEvent:

  • It only sends updates that occur after a subscription.
  • It supports only one observer.

Consider a MutableLiveData scenario: The viewModel detects a data update, notifies the subscriber activity with setValue, the activity observes the update, the Toast prompts the content, the phone screen is flipped, and the activity registers the view Observer, immediately received the contents of the previous viewModel notification, pop up the same Toast again. The reason why Toast pops up repeatedly is because MutableLiveData sends updates to subscribers before subscribing, and SingleLiveEvent solves this problem very well