We often required to load some data which take a long time to load. For this, we use the Async Task Loader to load the data in the background. But now we have an alternative for this work and we can use the LiveData and ViewModel as Async Task Loader. In this article, I am going to explain how to use the LiveData and ViewModel as Async Task Loader.
Here to explain the use of LiveData and ViewModel to load the data like Async Task Loader we will create an app to list all installed package on a device. Below is the sample of the app which we are going to develop in this article of How to use LiveData and ViewModel as Async Task Loader.
In this Article, I am going to create a demo app to list all app installed on your device. We will create four class and will go with each of the class one by one.
AppData.java is the POJO class which contains four field appName, packageName, Icon, and mFile. Apart from this, we have getter and setter of the variable. Below is the full code of AppData class.
There is nothing special in above POJO class it is a normal POJO that we use in our day to day task as a developer. The real magic is in our next class that is AppDataModel, so let's create the AppDataModel class.
Let me explain more about our ViewModel and LiveData class. We have to create our AppDataModel class by extending the ViewModel.In the normal scenario, we can't store context or activity in ViewModel, but as we are going to extract the information about the installed app so we must require the context if we want to retrieve the information from our AppDataModel class. If you want to learn about the LiveData and ViewModel you can check out the article on LiveData and ViewModel which we published recently.
As we described above we can't use the context inside the ViewModel. So for the solution of this the AndroidViewModel so we will extend the AndroidViewModel instead of ViewModel as given below.
The major difference between both classes is context. In normal ViewModel, we can't use the context and activity due to the chance of memory leakage issue, but in AndroidViewModel we can use the context. For using the context its mandatory to create a default constructor with Application as the argument.
LiveData is the class which can be observed and notifies the bound activity and fragment in case data has been changed. Even LiveData observer can update the UI of the app on data change. If you want are not familiar with it, then check my recent article on LiveData.
Below is the full code of our LiveData class.
Using below code we are declaring some variable that we required to retrieve and store the information of the installed app.
In below code we are creating the constructor of our LiveData class and calling the LoadAppInfo() method to retrieve the app information.
It is important to remind that from where context is LiveData constructor. So the answer to this is that it is coming from the ViewModel constructor call using the below code.
You can check the code above for our LoadAppInfo() method it self-explanatory along with the comment. But still, if have any question then put in the comment section below.
So far we have created the POJO and ViewModel for our demo app. Now we have to put the observer on LiveData so that whenever any change happens into stored data. Then observer notifies the Main Activity to update the UI.
So now we need to modify our main class. In Main class, first of all, we have to define our RecyclerView as given below. Here we are declaring the RecyclerView and assigning a simple LinearLayout.
Declare and assign adapter to recycler view. It is important to that we have not created adapter class yet will create later.
Declare the AppDataModel class using the below code.
Assign the value to declared appDataModel variable.
Set observer on LiveData using code below.
In above code, we are retrieving the data using getAppLiveData() method declared in our ViewModel class and set the observer on same.
The complete source code of the Adapter class is as given below.
The above Adapter class is sample adapter which provides the following functionality, that can be further extended.
We have done with this demo and now you are ready to test your app of "LiveData and ViewModel as Async Task Loader".
First of all, we are putting the XML file here.
We have already given the complete code of the POJO and Adapter class above. Here we are giving the source code of the AppViewModel and MainActivity Class.
We have completed this article and you can see that it is very simple to use the LiveData and ViewModel as Async Task Loader. It's time to note some important point form this article. We have to use AndroidViewModel instead of ViewModel as it provides the context. LiveData can be observed so that we are storing our POJO data inside LiveData class. You can also download the complete source code of How to use LiveData and ViewModel as Async Task Loader from GitHub.
LiveData and ViewModel as Async Task Loader
Here to explain the use of LiveData and ViewModel to load the data like Async Task Loader we will create an app to list all installed package on a device. Below is the sample of the app which we are going to develop in this article of How to use LiveData and ViewModel as Async Task Loader.
In this Article, I am going to create a demo app to list all app installed on your device. We will create four class and will go with each of the class one by one.
AppData.java: It is the POJO to store the details of the apps like AppName, Package Name, Icon.
AppDataModel.java: It is the main class in which we will implement the ViewModel and LiveData to retrieve and store the information of Apps.
MainActivity.java: MainActivity is our main class or only one activity of this demo App to list all apps from android device.
AppsAdapter.java: AppsAdapter class is our adapter of recyclerview.
Create POJO Class to Store the detail of the all installed app
AppData.java is the POJO class which contains four field appName, packageName, Icon, and mFile. Apart from this, we have getter and setter of the variable. Below is the full code of AppData class.
package com.nplix.applist;
import android.graphics.drawable.Drawable;
import java.io.File;
public class AppData {
private String appName;
private String packageName;
private Drawable Icon;
public File getFile() {
return mFile;
}
public void setFile(File mFile) {
this.mFile = mFile;
}
private File mFile;
public String getAppName() {
return appName;
}
public void setAppName(String appName) {
this.appName = appName;
}
public String getPackageName() {
return packageName;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public Drawable getIcon() {
return Icon;
}
public void setIcon(Drawable icon) {
Icon = icon;
}
}
There is nothing special in above POJO class it is a normal POJO that we use in our day to day task as a developer. The real magic is in our next class that is AppDataModel, so let's create the AppDataModel class.
Create AppDataModel class with ViewModel and LiveData
Let me explain more about our ViewModel and LiveData class. We have to create our AppDataModel class by extending the ViewModel.In the normal scenario, we can't store context or activity in ViewModel, but as we are going to extract the information about the installed app so we must require the context if we want to retrieve the information from our AppDataModel class. If you want to learn about the LiveData and ViewModel you can check out the article on LiveData and ViewModel which we published recently.
As we described above we can't use the context inside the ViewModel. So for the solution of this the AndroidViewModel so we will extend the AndroidViewModel instead of ViewModel as given below.
public class AppDataModel extends AndroidViewModel {
private AppLiveData appLiveData;
public AppLiveData getAppLiveData() {
return appLiveData;
}
public AppDataModel(@NonNull Application application) {
super(application);
}
}
Difference between ViewModel and AndroidViewModel
The major difference between both classes is context. In normal ViewModel, we can't use the context and activity due to the chance of memory leakage issue, but in AndroidViewModel we can use the context. For using the context its mandatory to create a default constructor with Application as the argument.
public AppDataModel(@NonNull Application application) { super(application); }
Create LiveData class to retrieve the Apps details
LiveData is the class which can be observed and notifies the bound activity and fragment in case data has been changed. Even LiveData observer can update the UI of the app on data change. If you want are not familiar with it, then check my recent article on LiveData.
Below is the full code of our LiveData class.
public class AppLiveData extends MutableLiveData<List<AppData>>{
private PackageManager packageManager;
List<AppData> appList;
private final Context context;
public AppLiveData(Context context){
this.context=context;
packageManager=context.getPackageManager();
LoadAppInfo();
}
private void LoadAppInfo() {
// Retrieve all known applications.
List<ApplicationInfo> apps = packageManager.getInstalledApplications(
PackageManager.GET_UNINSTALLED_PACKAGES);
if (apps == null) {
apps = new ArrayList<ApplicationInfo>();
}
appList = new ArrayList<AppData>(apps.size());
//Loop to get the info for all installed app and store the information in live data
for (int i=0; i<apps.size(); i++) {
AppData entry = new AppData();
entry.setPackageName(apps.get(i).packageName);
entry.setAppName(apps.get(i).loadLabel(context.getPackageManager()).toString());
entry.setFile(new File(apps.get(i).sourceDir));
//Check if file exist then get the icon and store into the POJO
if(entry.getFile().exists()){
entry.setIcon(apps.get(i).loadIcon(context.getPackageManager()));
}
//Add the indviual app information into the List
appList.add(entry);
Log.d("LiveData",entry.getAppName());
}
//Set the value into LiveData
setValue(appList);
}
}
Using below code we are declaring some variable that we required to retrieve and store the information of the installed app.
private PackageManager packageManager;
List<AppData> appList;
private final Context context;
In below code we are creating the constructor of our LiveData class and calling the LoadAppInfo() method to retrieve the app information.
public AppLiveData(Context context){
this.context=context;
packageManager=context.getPackageManager();
LoadAppInfo();
}
It is important to remind that from where context is LiveData constructor. So the answer to this is that it is coming from the ViewModel constructor call using the below code.
appLiveData=new AppLiveData(application);
You can check the code above for our LoadAppInfo() method it self-explanatory along with the comment. But still, if have any question then put in the comment section below.
Linking the ViewModel, LiveData, and Activity with help of observer
So far we have created the POJO and ViewModel for our demo app. Now we have to put the observer on LiveData so that whenever any change happens into stored data. Then observer notifies the Main Activity to update the UI.
So now we need to modify our main class. In Main class, first of all, we have to define our RecyclerView as given below. Here we are declaring the RecyclerView and assigning a simple LinearLayout.
RecyclerView recyclerView;
recyclerView=findViewById(R.id.recylerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
Declare and assign adapter to recycler view. It is important to that we have not created adapter class yet will create later.
AppsAdapter appsAdapter;
appsAdapter=new AppsAdapter(appsList,this,false,false);
recyclerView.setAdapter(appsAdapter);
Declare the AppDataModel class using the below code.
AppDataModel appDataModel;
Assign the value to declared appDataModel variable.
appDataModel= ViewModelProviders.of(this).get(AppDataModel.class);
Set observer on LiveData using code below.
appDataModel.getAppLiveData().observe(this, new Observer<List<AppData>>() {
@Override
public void onChanged(@Nullable List<AppData> appData) {
//this is not required here but just for reference we can do any thing with this data.
appsList=appData;
//Update the data to adapter
appsAdapter.setData(appData);
//Update to the UI with latest data
appsAdapter.notifyDataSetChanged();
Log.d("MainActivity:", "Data has updated");
}
});
In above code, we are retrieving the data using getAppLiveData() method declared in our ViewModel class and set the observer on same.
Adapter for RecyclerView of LiveData and ViewModel
The complete source code of the Adapter class is as given below.
package com.nplix.applist;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
/**
* Created by Pawan on 1/18/2018.
*/
public class AppsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
private static final int TYPE_FOOTER = 2;
private String TAG="LoadImage";
Context context;
private List<AppData> questionList;
private boolean mWithHeader;
private boolean mWithFooter;
private View.OnClickListener mOnClickListener;
public AppsAdapter(List<AppData> apps, Context context, boolean withHeader, boolean withFooter) {
this.questionList = apps;
this.context=context;
this.mWithHeader=withHeader;
this.mWithFooter=withFooter;
}
@Override
public int getItemViewType(int position) {
if (mWithHeader && isPositionHeader(position))
return TYPE_HEADER;
if (mWithFooter && isPositionFooter(position))
return TYPE_FOOTER;
return TYPE_ITEM;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
if(viewType==TYPE_HEADER) {
return new header(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.header, viewGroup, false));
}
else if(viewType==TYPE_FOOTER){
return new footer(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.footer, viewGroup, false));
}
else {
View itemView = LayoutInflater.
from(viewGroup.getContext()).
inflate(R.layout.appitem, viewGroup, false);
VideoViewHolder holder = new VideoViewHolder(itemView);
itemView.setTag(holder);
itemView.setOnClickListener(mOnClickListener);
return holder;
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if(holder instanceof header){
}
else if(holder instanceof footer){
((footer) holder).context = context;
}
else {
AppData appData=getItem(position);
((VideoViewHolder)holder).vName.setText(appData.getAppName());
((VideoViewHolder)holder).vPackageName.setText(appData.getPackageName());
((VideoViewHolder)holder).vImage.setImageDrawable(appData.getIcon());
}
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
}
@Override
public int getItemCount() {
int itemCount=0;
if(questionList!=null) {
itemCount = questionList.size();
if (mWithHeader)
itemCount = itemCount + 1;
if (mWithFooter)
itemCount = itemCount + 1;
// return itemCount;
}
return itemCount;
}
private boolean isPositionHeader(int position) {
return position == 0;
}
private boolean isPositionFooter(int position) {
return position == getItemCount() - 1;
}
public void setOnClickListener(View.OnClickListener lis) {
mOnClickListener = lis;
}
protected AppData getItem(int position) {
return mWithHeader ? questionList.get(position - 1) : questionList.get(position);
}
private int getItemPosition(int position){
return mWithHeader ? position - 1 : position;
}
public void setData(List<AppData> questionList) {
this.questionList=questionList;
}
public class VideoViewHolder extends RecyclerView.ViewHolder {
protected ImageView vImage;
protected TextView vName,vPackageName;
protected Context context;
public VideoViewHolder(View v) {
super(v);
vImage = (ImageView) v.findViewById(R.id.image);
vName = (TextView) v.findViewById(R.id.name);
vPackageName=(TextView) v.findViewById(R.id.packageName);
}
public void clearAnimation() {
this.clearAnimation();
}
}
public class header extends RecyclerView.ViewHolder {
protected Context context;
protected int position;
public header(View v) {
super(v);
}
}
public class footer extends RecyclerView.ViewHolder {
protected Context context;
protected int position;
public footer(View v) {
super(v);
}
}
}
The above Adapter class is sample adapter which provides the following functionality, that can be further extended.
You can set onClick listener form activity by adapter.setOnClickListener method.
Set the data to adapter using setData method.
Create the Header and Footer by just giving the true and false value in adapter declaration.
We have done with this demo and now you are ready to test your app of "LiveData and ViewModel as Async Task Loader".
Complete Source code of LiveData and ViewModel as Async Task Loader
First of all, we are putting the XML file here.
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:orientation="horizontal"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:weightSum="100">
<ImageView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/image"
android:layout_weight="30"
android:layout_gravity="left"
android:fitsSystemWindows="true"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_weight="70"
android:layout_gravity="right">
<TextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
android:textIsSelectable="true"
android:textSize="20sp"
android:textStyle="bold"
android:visibility="visible" />
<TextView
android:id="@+id/packageName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Display1"
android:textIsSelectable="true"
android:textSize="16sp"
android:visibility="visible" />
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.nplix.applist.MainActivity">
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/recylerView"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
We have already given the complete code of the POJO and Adapter class above. Here we are giving the source code of the AppViewModel and MainActivity Class.
package com.nplix.applist;
import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
AppDataModel appDataModel;
RecyclerView recyclerView;
AppsAdapter appsAdapter;
List<AppData> appsList=new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView=findViewById(R.id.recylerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
appsAdapter=new AppsAdapter(appsList,this,false,false);
recyclerView.setAdapter(appsAdapter);
appDataModel= ViewModelProviders.of(this).get(AppDataModel.class);
appDataModel.getAppLiveData().observe(this, new Observer<List<AppData>>() {
@Override
public void onChanged(@Nullable List<AppData> appData) {
//this is not required here but just for reference we can do any thing with this data.
appsList=appData;
//Update the data to adapter
appsAdapter.setData(appData);
//Update to the UI with latest data
appsAdapter.notifyDataSetChanged();
Log.d("MainActivity:", "Data has updated");
}
});
}
}
package com.nplix.applist;
import android.app.Application;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.MutableLiveData;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.support.annotation.NonNull;
import android.util.Log;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Created by PK on 1/15/2018.
*/
public class AppDataModel extends AndroidViewModel {
public AppLiveData getAppLiveData() {
return appLiveData;
}
private AppLiveData appLiveData;
public AppDataModel(@NonNull Application application) {
super(application);
appLiveData=new AppLiveData(application);
}
public class AppLiveData extends MutableLiveData<List<AppData>>{
private PackageManager packageManager;
List<AppData> appList;
private final Context context;
public AppLiveData(Context context){
this.context=context;
packageManager=context.getPackageManager();
LoadAppInfo();
}
private void LoadAppInfo() {
// Retrieve all known applications.
List<ApplicationInfo> apps = packageManager.getInstalledApplications(
PackageManager.GET_UNINSTALLED_PACKAGES);
if (apps == null) {
apps = new ArrayList<ApplicationInfo>();
}
appList = new ArrayList<AppData>(apps.size());
for (int i=0; i<apps.size(); i++) {
AppData entry = new AppData();
entry.setPackageName(apps.get(i).packageName);
entry.setAppName(apps.get(i).loadLabel(context.getPackageManager()).toString());
entry.setFile(new File(apps.get(i).sourceDir));
if(entry.getFile().exists()){
entry.setIcon(apps.get(i).loadIcon(context.getPackageManager()));
}
appList.add(entry);
Log.d("LiveData",entry.getAppName());
}
setValue(appList);
}
}
}
Conclusion
We have completed this article and you can see that it is very simple to use the LiveData and ViewModel as Async Task Loader. It's time to note some important point form this article. We have to use AndroidViewModel instead of ViewModel as it provides the context. LiveData can be observed so that we are storing our POJO data inside LiveData class. You can also download the complete source code of How to use LiveData and ViewModel as Async Task Loader from GitHub.
[…] How to use LiveData and ViewModel as Async Task Loader […]
ReplyDelete[…] Components Library for MVC app development. Architecture Components Library includes ViewModel, LiveData, RoomDatabase and Paging Library. In this article, we will create a TODO app that loads the paged […]
ReplyDeleteNo Async load found in any code !?
ReplyDelete