How to use ViewModel in Android

Android provides multiple methods to execute long running code off the UI thread while posting the results of the code to the main UI thread. CursorLoader is one of such method which is based on Android’s Loader framework. CursorLoader is a AsyncTaskLoader which is used to perform cursor query on a background thread. Cursor Loader loads data in a background thread and presents the results in the main UI thread. The second most important aspect of loader is that they monitors the data source for any update and re queries if there is any change detected. Thus the UI is always up-to-date with any new data changes.

In SDK 28.0 Android framework deprecated the use of Loaders. Thereafter the recommended option is to use Androidx ViewModels and LiveData. ViewModel is a class which prepares and manages  the data for Activity or Fragment. LiveData is a data holder class that is observed by VideoModel for any modifications in the data. Let’s see the use of ViewModel and LiveData by an example.

In one of the earlier post I showed how to get list of applications installed in Android device. I will show now how to use ViewModel and LiveData to present the data in a RecyclerView.

The xml layout of the activity is defined in main_activity.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" />

Activity code is defined in MainActivity.java

package com.codeexa.viewmodel;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import com.codeexa.viewmodel.ui.main.MainFragment;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_activity);
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.container, MainFragment.newInstance())
                    .commitNow();
        }
    }
}

Fragment class is defined in MainFragment.java

package com.codeexa.viewmodel.ui.main;

import androidx.lifecycle.ViewModelProviders;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.codeexa.viewmodel.R;

import java.util.List;

public class MainFragment extends Fragment {

    private Context mContext;
    private AppsListAdapter mAdapter;

    public static MainFragment newInstance() {
        return new MainFragment();
    }

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        mContext = context;
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.main_fragment, container, false);
        RecyclerView mRecycleView = v.findViewById(R.id.recyclerView);
        mAdapter = new AppsListAdapter(mContext.getPackageManager());
        mRecycleView.setLayoutManager(new LinearLayoutManager(mContext));
        mRecycleView.setItemAnimator(new DefaultItemAnimator());
        mRecycleView.setAdapter(mAdapter);
        return v;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        MainViewModel mViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
        mViewModel.getAppsList().observe(this, data -> {
            Log.e("tag"," Total apps: " + data.size());
            mAdapter.setData(data);
        });
    }

    public static class AppsListAdapter extends RecyclerView.Adapter<AppsListAdapter.AppsViewHolder> {

        private final PackageManager mPackageManager;
        private List<ApplicationInfo> mData;

        public AppsListAdapter(PackageManager packageManager) {
            mPackageManager = packageManager;
        }

        @NonNull
        @Override
        public AppsViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            return new AppsViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.apps_list, parent, false));
        }

        @Override
        public void onBindViewHolder(@NonNull AppsViewHolder holder, int position) {
            ApplicationInfo info = mData.get(position);
            holder.label.setText(info.loadLabel(mPackageManager));
        }

        @Override
        public int getItemCount() {
            return mData == null ? 0 : mData.size();
        }

        public void setData(List<ApplicationInfo> data) {
            mData = data;
            notifyDataSetChanged();
        }

        public static class AppsViewHolder extends RecyclerView.ViewHolder {
            TextView label;

            public AppsViewHolder( View view) {
                super(view);
                label =  view.findViewById(R.id.appName);
            }
        }
    }
}

Time to check the view model class. In this example we will be using AndroidViewModel which is a custom class extended from ViewModel. AndroidViewModel provides Application context that can be used to as a context. In this example we are using AndroidViewModel to get package manager instance. The application context is available in the constructor method provided by AndroidViewModel.

package com.codeexa.viewmodel.ui.main;

import android.app.Application;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;

import java.util.List;

public class MainViewModel extends AndroidViewModel {

    private MutableLiveData<List<ApplicationInfo>> data =
            new MutableLiveData<>();

    public MainViewModel(@NonNull Application application) {
        super(application);
        loadData(application.getPackageManager());
    }

    public void loadData(PackageManager packageManager) {

        AsyncTask.execute(() -> {
            List<ApplicationInfo> applicationInfos = packageManager.getInstalledApplications(PackageManager.GET_META_DATA);
            this.data.postValue(applicationInfos);
        });
    }

    public LiveData<List<ApplicationInfo>> getAppsList() {
        return data;
    }
}

Create MutableLiveData which exposes set and post methods to post the data to the observable. postValue is used when the data is posted from background thread. setValue is used when the data needs to be set from main thread.

Fragment and Adapter XML layouts are defined below.

main_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <androidx.recyclerview.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/recyclerView"
        android:scrollbarStyle="insideOverlay"
        android:scrollbars="vertical" />

</FrameLayout>

apps_list.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/appName"
    tools:context=".MainActivity"
    android:textAppearance="@style/TextAppearance.AppCompat.Large">

</TextView>

Download source code from the link here.

ViewModel
ViewModel
ViewModel.zip
Version: 1
440.3 KiB
15 Downloads
Details

Leave a Reply