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.

  1. ViewModel lifecycle
  2. ViewModel source code analysis
  3. What is a ViewModelStore
  4. What is a ViewModelStoreOwner
  5. How to simplify communication between fragments

ViewModel lifecycle

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> 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> 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> 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:

jzman-blog

The advantages of using the ViewModel are as follows:

  1. Activities are no longer involved in communication between sub-fragments and have a single responsibility.
  2. Fragments are different from each other except that they use the same instance of the ViewModel, and each Fragment can work independently.
  3. Each Fragment has its own life cycle and can be replaced and removed at will without affecting the normal operation of the other Fragment.