Categories: Android

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.zip
Version: 1
440.3 KiB
63 Downloads
Details
Date:November 14, 2019
Share

Recent Posts

  • Android
  • Guides

How to use DialogFragments instead of AlertDialogs in Android

In this post we will be discussing the use of DialogFragments in Android. We will see how to migrate away…

5 months ago
  • Firebase

Save and Access Environment Variables in Firebase

Environment variables in the cloud functions allows you to set/get key values at the time of deployment which can later…

7 months ago
  • Android

How to migrate to AndroidX support library

AndroidX is the latest Android support library package and is an improved version of older support libraries. Android Studio seamlessly…

8 months ago
  • Ads Integration

Unity: How to automatically add Admob App ID

Admob is one of the most used Ad network used by developers all over the world. In recent sdk update…

10 months ago
  • Android
  • Guides

How to use Accessibility Service in Android

Accessibility Service is used to assist users with disabilities. Accessibility services are background services which is invoked by the system…

11 months ago
  • Android
  • Guides

How to track real time location in Android with Google Maps

Realtime location tracking is the backbone of all delivery and tracking apps in the market. For example tracking your food…

11 months ago