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.