Android Architecture Components Tutorial – Room, LiveData and ViewModel

During the concluded Google I/O 2017, there were some amazing announcements about Android. Say hello to Android Architecture Components. For once, these are announcements that ease our lives as Android Developers.

Android Architecture Components

A new collection of libraries that help you design robust, testable, and maintainable apps. – developer.android.com

It helps us developers address two pain points:

  1. Manage our UI components lifecycle
  2. Persist data over configuration changes

In fact, these are the two biggest problems we Android Developers face. Period.

Maintaining data over orientation changes and handling our objects with lifecycle is hard. That’s why, to avoid the hassle, you lock your apps in Portrait mode, don’t you? Don’t lie. Even I’ve done it.

But don’t worry, Android Architecture Components will help alleviate both our fears.

There are 3 main architecture components:

  1. Room
  2. LiveData
  3. ViewModel

So first, let’s find out what these components actually are. Then, we’ll learn how we can use them.

We’ll even make a handy app that keeps track of what people borrow. This will help us learn better about how all these 3 components work together.

Room

Remember the amount of boilerplate code you had to write to create and manipulate even a very small database? You had to define the database structure, create an SQLiteHelper class etc.

Room is a library that saves you all such trouble. Now you can query your data without having to deal with cursors or loaders. You can define your database by adding annotations in your Model class. Yes, it’s that simple.

If you’ve used third-party ORMs like Sugar, you’ll feel right at home here. In fact, from now on, I wouldn’t even want to use one. Room is that brilliant! Why would you want to use a third-party library, when the official Android libraries give you an equal, or if not, better solution.

Architecture

In this app, we will follow an architecture called MVVM – Model View ViewModel.

In MVVM, the ViewModel exposes the required data and interested parties can listen to it.

But you don’t have to worry. We’ll do a simple implementation for this article. You’ll have no problem following.

So in our case, the Activity will listen on the data and make changes in the UI.


Getting started

Create a new project in Android Studio.

First, Add Google’s maven repository to your project-level build.gradle file.

//...
allprojects {
    repositories {
        jcenter()
        maven {
            url "https://maven.google.com"
        }
    }
}
//...

Next, add the following dependencies for Room in your app-level build.gradle file.

compile "android.arch.lifecycle:extensions:1.0.0"
compile "android.arch.persistence.room:runtime:1.0.0"
annotationProcessor "android.arch.lifecycle:compiler:1.0.0"
annotationProcessor "android.arch.persistence.room:compiler:1.0.0"

Creating the Model

Create a class called BorrowModel.

import java.util.Date;
@Entity
public class BorrowModel {

    @PrimaryKey(autoGenerate = true)
    public int id;
    private String itemName;
    private String personName;
    @TypeConverters(DateConverter.class)
    private Date borrowDate;

    public BorrowModel(String itemName, String personName, Date borrowDate) {
        this.itemName = itemName;
        this.personName = personName;
        this.borrowDate = borrowDate;
    }

    public String getItemName() {
        return itemName;
    }

    public String getPersonName() {
        return personName;
    }

    public Date getBorrowDate() {
        return borrowDate;
    }
}

You might see an error at DateConverter.class. But don’t panic, we’ll create that next. So for now, pay attention to the Annotations used here.

We use the @Entity annotation to tell Room to use the current class as a database table.

Any attribute preceded by the @PrimaryKey annotation will serve as a primary key for the table. Here we use 'autoGenerate = true' so that the key is automatically generated every time an entry is made.

SQL cannot store data types like Date by default. That’s why we need a way to convert it into a compatible data type to store it in the database. We use the @TypeConverters to specify the converter for the borrowDate attribute. So to help us with this conversion, we’ll create a class called DateConverter.

NOTE:
Make sure to import java.util.Date;

import java.util.Date;
class DateConverter {

    @TypeConverter
    public static Date toDate(Long timestamp) {
        return timestamp == null ? null : new Date(timestamp);
    }

    @TypeConverter
    public static Long toTimestamp(Date date) {
        return date == null ? null : date.getTime();
    }
}

As you can see, the class just converts Date to Long and vice versa.

Data Access Object

Next up, we need to create a DAO – Data Access Object class. This class will be used to define all the queries we will perform on our database.

@Dao
@TypeConverters(DateConverter.class)
public interface BorrowModelDao {

    @Query("select * from BorrowModel")
    LiveData<List<BorrowModel>> getAllBorrowedItems();

    @Query("select * from BorrowModel where id = :id")
    BorrowModel getItembyId(String id);

    @Insert(onConflict = REPLACE)
    void addBorrow(BorrowModel borrowModel);

    @Delete
    void deleteBorrow(BorrowModel borrowModel);

}

We use @Dao to tell Room that this is a DAO class.

We define our queries as strings and pass them as a parameter to @Query. Each @Query annotation is paired with a method. When the paired method is called, the query gets executed.

Next, we use the @Insert annotation for methods that insert entries into the table. We can similarly use @Delete and @Update for deletion and update methods respectively.

In case there are conflicts during such manipulation operations, we have to specify a conflict strategy too. In our example, we are using REPLACE. It means that the conflicting entry will be replaced by the current entry.

Make sure you import REPLACE correctly using import static android.arch.persistence.room.OnConflictStrategy.REPLACE;

Creating the database

Now, all we need to do is create a RoomDatabase class. So create an abstract class called AppDatabase.

@Database(entities = {BorrowModel.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {

    private static AppDatabase INSTANCE;

    public static AppDatabase getDatabase(Context context) {
        if (INSTANCE == null) {
            INSTANCE =
                    Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "borrow_db")
                            .build();
        }
        return INSTANCE;
    }

    public abstract BorrowModelDao itemAndPersonModel();

}

We annotate the class with @Database which takes two arguments:

  1. An array of the Entity classes(the tables)
  2. The database version which is just an integer.

This class is used to create the database and get an instance of it. We create the database using

Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "borrow_db")
.build();

The arguments are:

  1. Context
  2. Your database class
  3. Name to given to the database

TIP:
We have to create an abstract method for every DAO class that we create. This is really important.

And that’s it. Our database is ready to roll.


ViewModel

Earlier in the post, we mentioned ViewModel. ViewModels are entities that are free of the Activity/Fragment lifecycle. For example, they can retain their state/data even during an orientation change.

ViewModels do not contain code related to the UI. This helps in the decoupling of our app components.

In Room, the database instance should ideally be contained in a ViewModel rather than on the Activity/Fragment.

Create the AndroidViewModel

We create a ViewModel for our borrowed items.

public class BorrowedListViewModel extends AndroidViewModel {

    private final LiveData<List<BorrowModel>> itemAndPersonList;

    private AppDatabase appDatabase;

    public BorrowedListViewModel(Application application) {
        super(application);

        appDatabase = AppDatabase.getDatabase(this.getApplication());

        itemAndPersonList = appDatabase.itemAndPersonModel().getAllBorrowedItems();
    }


    public LiveData<List<BorrowModel>> getItemAndPersonList() {
        return itemAndPersonList;
    }

    public void deleteItem(BorrowModel borrowModel) {
        new deleteAsyncTask(appDatabase).execute(borrowModel);
    }

    private static class deleteAsyncTask extends AsyncTask<BorrowModel, Void, Void> {

        private AppDatabase db;

        deleteAsyncTask(AppDatabase appDatabase) {
            db = appDatabase;
        }

        @Override
        protected Void doInBackground(final BorrowModel... params) {
            db.itemAndPersonModel().deleteBorrow(params[0]);
            return null;
        }

    }

}

Every ViewModel class must extend the ViewModel class. If  the ViewModel needs the application context, then it must extend the AndroidViewModel class. The ViewModel will contain all the data needed for our Activity. In our example, we are using something called LiveData.

LiveData is a wrapper that lets interested classes observe changes in the data inside the wrapper.

We wrap our list of borrowed items inside LiveData so that the Activity can observe changes in the data and update the UI.

In our ViewModel, we first get an instance of our database using AppDatabase.getDatabase(this.getApplication())

First, we need to load the list of borrowed items from the database. For that, we should use the query we defined in the DAO class, getAllBorrowedItems().

Next, call the abstract method we created for DAO and then call the query method. Refer to this snippet in the BorrowedListViewModel class.

appDatabase.itemAndPersonModel().getAllBorrowedItems();

Now since we will be displaying a list of 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<BorrowModel> borrowModelList;
    private View.OnLongClickListener longClickListener;

    public RecyclerViewAdapter(List<BorrowModel> borrowModelList, View.OnLongClickListener longClickListener) {
        this.borrowModelList = borrowModelList;
        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) {
        BorrowModel borrowModel = borrowModelList.get(position);
        holder.itemTextView.setText(borrowModel.getItemName());
        holder.nameTextView.setText(borrowModel.getPersonName());
        holder.dateTextView.setText(borrowModel.getBorrowDate().toLocaleString().substring(0, 11));
        holder.itemView.setTag(borrowModel);
        holder.itemView.setOnLongClickListener(longClickListener);
    }

    @Override
    public int getItemCount() {
        return borrowModelList.size();
    }

    public void addItems(List<BorrowModel> borrowModelList) {
        this.borrowModelList = borrowModelList;
        notifyDataSetChanged();
    }

    static class RecyclerViewHolder extends RecyclerView.ViewHolder {
        private TextView itemTextView;
        private TextView nameTextView;
        private TextView dateTextView;

        RecyclerViewHolder(View view) {
            super(view);
            itemTextView = (TextView) view.findViewById(R.id.itemTextView);
            nameTextView = (TextView) view.findViewById(R.id.nameTextView);
            dateTextView = (TextView) view.findViewById(R.id.dateTextView);
        }
    }
}

Creating the Android LifecycleActivity

It’s a pretty straightforward adapter. So I won’t be getting into the details of it. But if you’re interested in how RecyclerView works, this Android tutorial tells you how to create a RecyclerView.Adapter.

Now create an Activity that extends LifecycleActivity to display a list of all the borrowed items.

 LifecycleActivity is a class that provides us with the state of the lifecycle.

public class MainActivity extends LifecycleActivity implements View.OnLongClickListener {

    private BorrowedListViewModel viewModel;
    private RecyclerViewAdapter recyclerViewAdapter;
    private RecyclerView recyclerView;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startActivity(new Intent(MainActivity.this, AddActivity.class));
            }
        });


        recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        recyclerViewAdapter = new RecyclerViewAdapter(new ArrayList<BorrowModel>(), this);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        recyclerView.setAdapter(recyclerViewAdapter);

        viewModel = ViewModelProviders.of(this).get(BorrowedListViewModel.class);

        viewModel.getItemAndPersonList().observe(MainActivity.this, new Observer<List<BorrowModel>>() {
            @Override
            public void onChanged(@Nullable List<BorrowModel> itemAndPeople) {
                recyclerViewAdapter.addItems(itemAndPeople);
            }
        });

    }

    @Override
    public boolean onLongClick(View v) {
        BorrowModel borrowModel = (BorrowModel) v.getTag();
        viewModel.deleteItem(borrowModel);
        return true;
    }
}

NOTE:
Whenever we need to use ViewModels inside our Activity, the Activity must extend LifecycleActivity.

Creating a ViewModel is simple.

viewModel = ViewModelProviders.of(this).get(BorrowedListViewModel.class);

The two parameters are:

  1. Context
  2. The ViewModel class

Now we need to make our Activity observe the changes in the ViewModel. So first, get a reference to the LiveData inside the ViewModel. Then, add an observe() method to the reference.

The observe() method takes 2 parameters:

  1. The owner of the Lifecycle. In our case, it is the Activity.
  2. An Observer.

Whenever there is a change in the data, the onChanged() callback is executed and we get the new data. We can update the UI accordingly.

Simple Exercise – Create the ‘Add Item Screen’

Now we have an Activity that can display a list of borrowed items. But how do we add items to the database?

It’s simple. You need to create an AddItemActivity.

The entire procedure is exactly the same as before. But in this case, you need to call the addBorrow() DAO method and pass a Borrow object as a parameter. And that is it. Our app is ready.

I encourage you to try this screen on your own. But in case you get stuck, you can refer to my files on GitHub.


Output

So go ahead and run your app. You should get an output similar to this.

Room demo GIF

Room demo GIF

The Android Architecture Components give us great advantage and relief. Hence, we don’t have to worry about

  • lifecycle changes
  • memory leaks
  • data retention across configuration changes

If you recount, these are the biggest challenges faced by even veteran Android developers.

Source Code:
As always, the code from the post can be found on GitHub.


Wrap up

In the post, we got introduced to Android Architecture Components. Then, we learnt about Room and used it to create databases in a fast and easy way. Next, we also learnt how to make our UI respond to changes in data. Also, by using LiveData and ViewModels, we did not have to use a lot of callbacks.

Additionally, we even learnt a bit about the MVVM architecture.

We have barely scratched the surface in terms of what’s possible with these new components. So I hope to discover more as I continue to tinker with Android Architecture Components.

So are you going to use these components? Would you consider Room in any of your apps? Or maybe ViewModels to simplify your code? I’d love to hear your comments.

Subhrajyoti Sen

I am an Android developer and an active Mozillian. In my free time, I contribute to Open Source and watch lots of anime.

You may also like...

3 Responses

  1. webserveis says:

    Thanks, great start tutorial

  2. Zaphod Beeblebroks says:

    Hi,

    I know article is a bit old but… Can you please check once again explanation with LifecycleActivity cause seems like this is not needed and it might confuse people. I somehow couldn’t find it in your github repo so I guess you corrected it there but here on blog is forgotten. Cheers

  3. Great article ! It helped me a lot.

Leave a Reply

Your email address will not be published. Required fields are marked *