It helps us to solve two critical points:
- Manage our UI components lifecycle
- Persist data over configuration changes
- Takes some input from the user(LiveData)
- Save into the local database(Room)
- Show it on the screen(ViewModel)
Note : Use Android Studio 3.0 or later version.
Adding Dependencies (Pre AndroidX)
Room is basically a Database object mapping library use to access the database. Lifecycle, this has some good set of classes like ViewModel and LiveData which we will use to manage the lifecycle of our app. Add these libraries to build.gradle (Module: app) file, at the end of the dependencies block.
dependencies {
// Room components
implementation 'android.arch.persistence.room:runtime:2.0.0'
annotationProcessor 'android.arch.persistence.room:compiler:2.0.0'
// Lifecycle components
implementation 'android.arch.lifecycle:extensions:2.0.0'
annotationProcessor 'android.arch.lifecycle:compiler:2.0.0'
}
Refer here for AndroidX Dependencies.
dependencies {
// Room components
implementation 'android.arch.persistence.room:runtime:2.0.0'
annotationProcessor 'android.arch.persistence.room:compiler:2.0.0'
// Lifecycle components
implementation 'android.arch.lifecycle:extensions:2.0.0'
annotationProcessor 'android.arch.lifecycle:compiler:2.0.0'
}
Refer here for AndroidX Dependencies.
Room (Local database)
We can query your data without having to deal with cursors or loaders. We can define our database by adding annotations in your Model class. So get rid of third-party ORMs like Sugar, In fact, when the official Android libraries give you an equal, or if not, better solution. The library helps you create a cache of your app's data on a device that's running your app. If user does not have internet connection, Just chill ! No problem! This cache allows users to view a consistent copy of key information within your app.
@Entity - We just have to annotate “@Entity” to a class and the name of the class becomes table name and, data members becomes the name of the columns. “@Entity” class represent an entity in a table.
Here, we have class StudentModel, and name of table is also same. We had made a column itemName, personName, and addedDate.
@Dao (Data Access Object) - An Interface where we put all our SQL queries. No need to write whole quires now, we just need to make a method and annotate with specific annotations like “@Insert”, “@Delete”, “@Query(SELECT FROM *)”
Here, we have an interface StudentModelDao. To insert the data we annotated “@Insert” to insert method. Room doesn’t gives us annotations which can help us in selecting everything so we have “@Query” to do some custom queries.
@Database - We need to create an abstract class (Room class) which extends RoomDatabase. It is a database layer over the SQLite database; this helps us in all the work which we use to do in SQLiteOpenHelper class. We need only a single instance for the whole app.
Here, we have a Room class AppRoomDatabase in which we declare all our entities and version of the database. getDatabase() method will return the room database instance. If we want to access the database lets deep dive into ViewModel and LiveData.
3 key concepts of Room
Here, we have class StudentModel, and name of table is also same. We had made a column itemName, personName, and addedDate.
@Dao (Data Access Object) - An Interface where we put all our SQL queries. No need to write whole quires now, we just need to make a method and annotate with specific annotations like “@Insert”, “@Delete”, “@Query(SELECT FROM *)”
Here, we have an interface StudentModelDao. To insert the data we annotated “@Insert” to insert method. Room doesn’t gives us annotations which can help us in selecting everything so we have “@Query” to do some custom queries.
@Database - We need to create an abstract class (Room class) which extends RoomDatabase. It is a database layer over the SQLite database; this helps us in all the work which we use to do in SQLiteOpenHelper class. We need only a single instance for the whole app.
Here, we have a Room class AppRoomDatabase in which we declare all our entities and version of the database. getDatabase() method will return the room database instance. If we want to access the database lets deep dive into ViewModel and LiveData.
ViewModel (Data-handling business logic)
ViewModel is the new class provided by lifecycle. It can be termed as the bridge between model and UI but quite intelligent one in the sense that it can be automatically retained in the case of orientation change .
Generally what happens during orientation change?
For instance we are in one activity and the activity loads some list and add it to the RecyclerView. Now when the orientation change has not been handled then new instance of activity will be created and the loading of list takes place and is then again added to RecyclerView.
How does ViewModel solve this problem?
If you have used ViewModel along with LiveData class to store data then during orientation change new instance of activity will be created but the data won't be downloaded again. ViewModel will provide the most recent available data.
Note: Don’t think that ViewModel will hold data forever or for every case. If you close or activity is destroyed the ViewModel will also be destroyed or cleared.
StudentListViewModel class must extend the ViewModel class. If the ViewModel needs the application context, then it must extend the AndroidViewModel class. In our ViewModel, we first get an instance of our database using AppRoomDatabase.getDatabase(this.getApplication())
We need to load the list of student items from the database. For that, we should use the query we defined in the DAO class, getAllStudItems(). Next, call the abstract method we created for DAO and then call the query method.
The ViewModel will contain all the data needed for our Activity. In our example, we are using something called LiveData.
MutableLiveData extends LiveData internally and also the two methods of LiveData available,
1. setValue() : Set the value and dispatch the value to all the active observers. It cannot be done in background thread it must be done in the main thread only.
2. postValue() : Post a task to main thread to override value set by setvalue. As setvalue cannot be called from background thread so post value must be used to set value from background thread.
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.RecyclerViewHolder> {
private List<StudentModel> studentModelList;
private View.OnLongClickListener longClickListener;
Generally what happens during orientation change?
For instance we are in one activity and the activity loads some list and add it to the RecyclerView. Now when the orientation change has not been handled then new instance of activity will be created and the loading of list takes place and is then again added to RecyclerView.
How does ViewModel solve this problem?
If you have used ViewModel along with LiveData class to store data then during orientation change new instance of activity will be created but the data won't be downloaded again. ViewModel will provide the most recent available data.
Note: Don’t think that ViewModel will hold data forever or for every case. If you close or activity is destroyed the ViewModel will also be destroyed or cleared.
StudentListViewModel class must extend the ViewModel class. If the ViewModel needs the application context, then it must extend the AndroidViewModel class. In our ViewModel, we first get an instance of our database using AppRoomDatabase.getDatabase(this.getApplication())
We need to load the list of student items from the database. For that, we should use the query we defined in the DAO class, getAllStudItems(). Next, call the abstract method we created for DAO and then call the query method.
appDatabase.itemAndStudentModel().getAllStudItems();
The ViewModel will contain all the data needed for our Activity. In our example, we are using something called LiveData.
LiveData
LiveData is an observable data holder. It can only be observed in the context of a lifecycle, more precisely in the context of an Activity or Fragment lifecycle. By passing the reference of an Activity or Fragment, it can understand whether your UI onScreen, offScreen or Destroyed. After passing the UI object to LiveData, whenever the data in the live data changes. It notifies the lifecycle owner with updates and then the UI redraw itself with updates.MutableLiveData extends LiveData internally and also the two methods of LiveData available,
1. setValue() : Set the value and dispatch the value to all the active observers. It cannot be done in background thread it must be done in the main thread only.
2. postValue() : Post a task to main thread to override value set by setvalue. As setvalue cannot be called from background thread so post value must be used to set value from background thread.
How LiveData solved developer`s major Headaches ?
- No more manual life-cycling handle - Observers just observe relevant data and don’t stop or resume observation. LiveData manages all of this under control.
- Proper configuration changes - If an observer is recreated due to a configuration change, like device rotation, it immediately receives the latest available data.
- Ensures your UI matches the data state -Instead of updating the UI every-time when the data changes, your observer can update the UI every time there’s a change. It receives the latest data upon becoming active again.
- No memory leaks - Observers are bound to Lifecycle objects and clean up after themselves when their associated life cycle destroyed.
- No crashes due to stopped activities - It means if an activity is in the back stack, then it doesn’t receive any LiveData stream.
- Sharing resources - You can extend LiveData object using the singleton pattern to wrap system services so that they can be shared in your app.
- We wrap our list of student items inside LiveData so that the Activity can observe changes in the data and update the UI.
Framing RecyclerView Adapter
Now since we will be displaying a list of student items, we need a RecyclerView. So first, let’s create an adapter for the same.public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.RecyclerViewHolder> {
private List<StudentModel> studentModelList;
private View.OnLongClickListener longClickListener;
public RecyclerViewAdapter(List<StudentModel> studentModelList, View.OnLongClickListener longClickListener) {
this.studentModelList = studentModelList;
this.longClickListener = longClickListener;
}
@Override
public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new RecyclerViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.recycler_item, parent, false));
}
@Override
public void onBindViewHolder(final RecyclerViewHolder holder, int position) {
StudentModel borrowModel = studentModelList.get(position);
holder.itemTextView.setText(borrowModel.getItemName());
holder.nameTextView.setText(borrowModel.getPersonName());
holder.dateTextView.setText(borrowModel.getAddedDate().toLocaleString().substring(0, 11));
holder.itemView.setTag(borrowModel);
holder.itemView.setOnLongClickListener(longClickListener);
}
@Override
public int getItemCount() {
return studentModelList.size();
}
public void addItems(List<StudentModel> borrowModelList) {
this.studentModelList = borrowModelList;
notifyDataSetChanged();
}
static class RecyclerViewHolder extends RecyclerView.ViewHolder {
private TextView itemTextView;
private TextView nameTextView;
private TextView dateTextView;
RecyclerViewHolder(View view) {
super(view);
itemTextView = view.findViewById(R.id.itemTextView);
nameTextView = view.findViewById(R.id.nameTextView);
dateTextView = view.findViewById(R.id.dateTextView);
}
}
}
Creating the Android LifecycleObserver & LifecycleOwner
The basic idea here is that there is a class named LifecycleOwner which emits various lifecycle events (such as ON_CREATE, ON_PAUSE, etc) which you as an Android Developer are already familiar with. Now you can implement an interface name LifecycleObserver which listens to these events and respond accordingly.
public class AppLifeCycleObserver implements LifecycleObserver {
private String LOG_TAG = "AppLifeCycleObserver";
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void onResume() {
Log.i(LOG_TAG, "onResume");
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void onPause() {
Log.i(LOG_TAG, "onPause");
}
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
public void onCreate() {
Log.i(LOG_TAG, "onCreate");
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onStart() {
Log.i(LOG_TAG, "onStart");
}
public void onPause() {
Log.i(LOG_TAG, "onPause");
}
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
public void onCreate() {
Log.i(LOG_TAG, "onCreate");
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onStart() {
Log.i(LOG_TAG, "onStart");
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void onStop() {
Log.i(LOG_TAG, "onStop");
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
public void onDestroy() {
Log.i(LOG_TAG, "onDestroy");
}
}
public void onStop() {
Log.i(LOG_TAG, "onStop");
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
public void onDestroy() {
Log.i(LOG_TAG, "onDestroy");
}
}
Adding the Observer
Observers are added to lifecycle owners via calls to the addObserver() method of owner’s Lifecycle object, a reference to which is obtained via a call to the getLifecycle() method. Edit the HomeActivity.java class file and add code to the onCreate() method to add the observer:
Creating a Lifecycle Owner
By creating a custom lifecycle owner class and demonstrate how to trigger events and modify the lifecycle state from within that class. The class is going to need a LifecycleRegistry instance initialized with a reference to itself, and a getLifecycle() method configured to return the LifecycleRegistry instance. Declare a variable to store the LifecycleRegistry reference, a constructor to initialize the LifecycleRegistry instance and add the getLifecycle() method:Next, the class will need to notify the registry of lifecycle state changes. By triggering lifecycle events using the handleLifecycleEvent() method. For this example, we will add some methods that simply trigger lifecycle events when called. Finally change within the AppLifecycleOwner class is to add the AppLifeCycleObserver class as an observer.
In HomeActivity.java, create an instance of the AppLifecycleOwner class and to call the startLifecycleOwner() and stopLifecycleOwner() methods.
We must remove observer in onDestory() method of calling activity to avoid memory leaks.
@Override
protected void onDestroy() {
super.onDestroy();
getLifecycle().removeObserver(new AppLifeCycleObserver());
}
I have uploaded the latest source code in GitHub for your reference. Kindly raise your queries in the command section.
References :
https://developer.android.com/jetpack/arch/lifecyclehttps://medium.com/mindorks/android-architecture-components-room-viewmodel-and-livedata-50611793e4a9
https://blog.iamsuleiman.com/android-architecture-components-tutorial-room-livedata-viewmodel/
https://codinginfinite.com/android-livedata-example/
Happy coding!!!
Cheers!!!