PS: the original text was first published on wechat official account: Jingxing zhi (Jzman-blog)
We learned earlier about the use of LiveData and Lifecycle architecture components:
- Lifecycle for Android Jetpack components
- LiveData for Android Jetpack component
The ViewModel is lifecycle aware and automatically stores and manages UI-related data, even if the device configuration changes, so we don’t need to save data in onSaveInstanceState and restore data in onCreate, Using the ViewModel takes the work out of our hands, nicely separating the view from the logic.
- ViewModel lifecycle
- ViewModel source code analysis
- What is a ViewModelStore
- What is a ViewModelStoreOwner
- How to simplify communication between fragments
ViewModel lifecycle
Once the ViewModel is retrieved from OnCreate, it persists until the View bound to the ViewModel is completely onDestory.
ViewModel source code analysis
This creation project is to upgrade Android Studio to 3.2.1, so the dependent package in the project is directly replaced with the corresponding package under AndroidX. The main configuration is as follows:
/ / gradle plug-in
dependencies {
classpath 'com. Android. Tools. Build: gradle: 3.2.1'
}
// ViewModel and LiveData versions
def lifecycle_version = "2.0.0"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
/ / gradle - wrapper. The properties files
distributionUrl=https\:/ / services.gradle.org/distributions/gradle-4.6-all.zip
Copy the code
Create ViewModel as follows:
/ * *
*
* You can inherit AndroidViewModel if you want to use Context
* Powered by jzman.
* Created on 2018/12/13 0013.
* /
public class MViewModel extends ViewModel {
private MutableLiveData<List<Article>> data;
public LiveData<List<Article>> getData(){
if (data == null) {
data = new MutableLiveData<>();
data.postValue(DataUtil.getData());
}
return data;
}
}
Copy the code
AndroidViewModel: AndroidViewModel: AndroidViewModel: AndroidViewModel: AndroidViewModel: AndroidViewModel: AndroidViewModel: AndroidViewModel: AndroidViewModel
MViewModel mViewModel = ViewModelProviders.of(this).get(MViewModel.class);
mViewModel.getData().observe(this.new Observer<List<Article>>() {
@Override
public void onChanged(List<Article> articles) {
for (Article article : articles) {
Log.i(TAG,article.getDesc());
}
}
});
Copy the code
ViewModelProviders provide four static methods to obtain the corresponding ViewModelProvider. The four static methods are as follows:
public static ViewModelProvider of(@NonNull Fragment fragment)
public static ViewModelProvider of(@NonNull FragmentActivity activity)
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory)
public static ViewModelProvider of(@NonNull FragmentActivity activity, @Nullable Factory factory)
Copy the code
Take the second method as an example, which is implemented as follows:
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
@Nullable Factory factory) {
Application application = checkApplication(activity);
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return new ViewModelProvider(activity.getViewModelStore(), factory);
}
Copy the code
You can use the default AndroidViewModelFactory or create a custom Factory by calling one of the above methods:
Let’s take a look at ViewModelProvider. There are two key properties in ViewModelProvider:
private final Factory mFactory;
private final ViewModelStore mViewModelStore;
Copy the code
When the ViewModelProvider is created, the mFactory and mViewModelStore are initialized, and then the get() method is initialized.
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
// Get the class name, different from getName when getting the inner class name
//getCanonicalName-->xx.TestClass.InnerClass
//getName-->xx.TestClass$InnerClass
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
}
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
Copy the code
Then call the get method with key as follows:
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
//noinspection unchecked
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if(viewModel ! =null) {
// TODO: log a warning.
}
}
/ / create the ViewModel
viewModel = mFactory.create(modelClass);
// Retrieve the corresponding ViewModel from mViewModelStore according to the key
mViewModelStore.put(key, viewModel);
//noinspection unchecked
return (T) viewModel;
}
Copy the code
AndroidViewModelFactory = AndroidViewModelFactory = AndroidViewModelFactory = AndroidViewModelFactory = AndroidViewModelFactory = AndroidViewModelFactory
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
AndroidViewModel is a parent of modelClass or interface
if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
/ /...
Reflection creates the ViewModel and returns it
return modelClass.getConstructor(Application.class).newInstance(mApplication);
}
return super.create(modelClass);
}
Copy the code
After the creation of the specific ViewModel object, you can call the method in the specific ViewModel at will, in front of the source code will encounter a variety of encapsulation classes, Such as ViewModelStore, ViewModelStoreOwner, AndroidViewModelFactory, etc., will be introduced in the following.
What is a ViewModelStore
ViewModelStore is mainly used to store the state of ViewModel when the device configuration changes, such as the current interface is recreated or destroyed, etc. The corresponding new ViewModelStore should hold all information about the corresponding ViewModel as the old ViewModelStore did. The corresponding clear() method will only notify the ViewModel that it is no longer in use. The corresponding ViewModelStore will no longer store the relevant information.
This class actually uses HashMap to store the corresponding ViewModel, which is very simple:
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if(oldViewModel ! =null) {
oldViewModel.onCleared();
}
}
final ViewModel get(String key) {
return mMap.get(key);
}
public final void clear(a) {
for (ViewModel vm : mMap.values()) {
vm.onCleared();
}
mMap.clear();
}
}
Copy the code
What is a ViewModelStoreOwner
This is an interface that defines a method getViewModelStore() to get the ViewModelStore of the corresponding ViewModel, and also calls the ViewModelStoreOwner clear() method, ViewModelStore = ViewModelStore = ViewModelStore = ViewModelStore
public interface ViewModelStoreOwner {
/ * *
* Returns owned {@link ViewModelStore}
*
* @return a {@code ViewModelStore}
* /
@NonNull
ViewModelStore getViewModelStore(a);
}
Copy the code
LifecycleOwner implements the FragmentActivity/Fragment interface directly or indirectly. For example:
- An indirect implementation of the Activity:
public ViewModelStore getViewModelStore(a) {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if(nc ! =null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
Copy the code
- Fragment directly implements:
@Override
public ViewModelStore getViewModelStore(a) {
if (mFragmentManager == null) {
throw new IllegalStateException("Can't access ViewModels from detached fragment");
}
return mFragmentManager.getViewModelStore(this);
}
Copy the code
The process of saving ViewModelStore is done in the upper implementation of Activty or Fragment. It is OK to recognize the ViewModelStoreOwner interface here.
How to simplify communication between fragments
Communication between fragments used to be implemented through the interface of the host Activity forwarding. Now you can use the same ViewModel to communicate between two fragments. Create a ViewModel that communicates with two fragments using its host Activity. Create a ViewModel that communicates with two fragments using its host Activity.
/ * *
* Powered by jzman.
* Created on 2018/12/14 0014.
* /
public class FViewModel extends ViewModel {
private MutableLiveData<String> mSelect = new MutableLiveData<>();
public void selectItem(String item) {
mSelect.postValue(item);
}
public LiveData<String> getSelect(a) {
return mSelect;
}
}
Copy the code
Then, create the LeftFragment as follows:
public class LeftFragment extends Fragment {
private FViewModel mViewModel;
private FragmentTitleBinding titleBinding;
public LeftFragment(a) {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_title, container, false);
titleBinding = DataBindingUtil.bind(view);
mViewModel = ViewModelProviders.of(getActivity()).get(FViewModel.class);
RvAdapter adapter = new RvAdapter(getActivity(), new RvAdapter.OnRecycleItemClickListener() {
@Override
public void onRecycleItemClick(String info) {
mViewModel.selectItem(info);
}
});
titleBinding.rvData.setLayoutManager(new LinearLayoutManager(getActivity()));
titleBinding.rvData.setAdapter(adapter);
return view;
}
}
Copy the code
The LeftFragment layout file is a RecycleView, and its Item layout file is as follows:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="itemData"
type="String"/>
<variable
name="onItemClick"
type="com.manu.archsamples.fragment.RvAdapter.OnRecycleItemClickListener"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:onClick="@{() -> onItemClick.onRecycleItemClick(itemData)}">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{itemData}"
android:padding="10dp"/>
</LinearLayout>
</layout>
Copy the code
RecyclerView Adapter is as follows:
public class RvAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context mContext;
private List<String> mData;
private OnRecycleItemClickListener mOnRecycleItemClickListener;
public RvAdapter(Context mContext,OnRecycleItemClickListener itemClickListener) {
this.mContext = mContext;
mData = DataUtil.getDataList();
mOnRecycleItemClickListener = itemClickListener;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.recycle_item,null);
view.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
return new MViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
MViewHolder mHolder = (MViewHolder) holder;
mHolder.bind(mData.get(position),mOnRecycleItemClickListener);
}
@Override
public int getItemCount(a) {
return mData.size();
}
private static class MViewHolder extends RecyclerView.ViewHolder{
RecycleItemBinding itemBinding;
MViewHolder(@NonNull View itemView) {
super(itemView);
itemBinding = DataBindingUtil.bind(itemView);
}
void bind(String info, OnRecycleItemClickListener itemClickListener){
itemBinding.setItemData(info);
itemBinding.setOnItemClick(itemClickListener);
}
}
public interface OnRecycleItemClickListener {
void onRecycleItemClick(String info);
}
}
Copy the code
Then, create the RightFragment as follows:
public class RightFragment extends Fragment {
private static final String TAG = RightFragment.class.getName();
private FragmentContentBinding contentBinding;
private FViewModel mViewModel;
public RightFragment(a) {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_content, container, false);
contentBinding = DataBindingUtil.bind(view);
mViewModel = ViewModelProviders.of(getActivity()).get(FViewModel.class);
mViewModel.getSelect().observe(this.new Observer<String>() {
@Override
public void onChanged(String s) {
// Receives the value of the LeftFragment Item click event
contentBinding.setData(s);
}
});
return view;
}
}
Copy the code
The RightFragment layout file is as follows:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="data"
type="String"/>
</data>
<FrameLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragment.LeftFragment">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:layout_marginStart="12dp"
android:layout_marginTop="10dp"
android:text="@{data,default=def}"/>
</FrameLayout>
</layout>
Copy the code
After using the ViewModel, the host Activity is very clean, only responsible for the Fragment switch can be done, the test effect is as follows:
The advantages of using the ViewModel are as follows:
- Activities are no longer involved in communication between sub-fragments and have a single responsibility.
- Fragments are different from each other except that they use the same instance of the ViewModel, and each Fragment can work independently.
- Each Fragment has its own life cycle and can be replaced and removed at will without affecting the normal operation of the other Fragment.