Loading...
Android

Great Android App Architecture [ Part 4 -Room Persistence Library ]

Continuing from last article, we need to implement some mechanism for caching the quote offline so that whenever user will open the app, he will see last quote loaded without waiting to load new quote and in background we will request server for new quote and update the UI whenever data is available. We are going to use Room Persistence Library for that!

 

  • Single source of Truth:
    From Wikipedia, In Information systems design and theory, single source of truth (SSOT), is the practice of structuring information models and associated schemata such that every data element is stored exactly once. Suppose you will be using same quote at multiple places in your app, you don’t want this quote to come from REST endpoint at every place. Inconsistencies may occur in data if data is changed in between 2 REST calls. Because of this, in our case SSOT will be our DB. Response of REST API will alter the DB and all the parts of application will access the data from DB. LiveData will help notify all the active listener for changes in data!

You might have used SQLiteOpenHelper class for manually storing data in DB, if not you can learn how to do that here. But we are not going to use that approach here, we will be using Room Persistence Library for that. Why? because it will help us remove the boiler plate code and make our code look much cleaner. Who don’t want that?

  • Room Persistence Library
    There are 3 major components in Room1. Entity:  This component represents a class that holds a database row. For each entity, a database table is created to hold the items. You must reference the entity class through the entities array in the Database class. Each field of the entity is persisted in the database unless you annotate it with @Ignore.2. Data Access Object (DAO): This component represents a class or interface as a Data Access Object (DAO). DAOs are the main component of Room and are responsible for defining the methods that access the database. The class that is annotated with @Database must contain an abstract method that has 0 arguments and returns the class that is annotated with @Dao. When generating the code at compile time, Room creates an implementation of this.3. Database: You can use this component to create a database holder. The annotation defines the list of entities, and the class’s content defines the list of data access objects (DAOs) in the database. It is also the main access point for the underlying connection.You can read more about Room here. If you did not understand the room components yet, don’t worry, code examples will make sure you understand it well.Without wasting much time, we will dive into code now!


We have to create those 3 components mentioned above. We will make those step by step.

  • Entity:
    This class represent the row which we will be storing in our table. We want to store a quote in db as a row. So we are going to make little modification to our Quote class to make it as entity as shown below.

    @Entity
    public class Quote{
    
        @PrimaryKey
        private int id=0;
    
        private String quote;
        private String author;
        private String category;
    
    
        public String getQuote() {
            return quote;
        }
    
        public void setQuote(String quote) {
            this.quote = quote;
        }
        // setters and getters required for all the fields
        // they are skipped for brevity.
    }
    

    @Entity : When a class is annotated with @Entity and is referenced in the entities property of a @Database annotation, Room creates a database table for that entity in the database. By default, room will create column for each field in table. To store the fields in table, room must have access to that field. You can either make the field public or provide public getter and setter for the field.

    @PrimaryKey: Each entity must define at least 1 field as a primary key. As we currently have only 1 quote to store in table, we will create on int field with constant value and make it as primary key. It will make sure we have only 1 quote at a time in table!


  • DAO:
    We will create DAO interface for defining methods which will actually be used for data retrieval and data storage for our db.

    @Dao
    public interface QuoteDAO {
    
        @Insert(onConflict = REPLACE)
        void save(Quote quote);
    
        @Update
        void update(Quote quote);
    
        @Query("SELECT * FROM quote")
        LiveData<Quote> getQuote();
    }
    

    @Dao: Marks the class as a Data Access Object. Data Access Objects are the main classes where you define your database interactions. They can include a variety of query methods. The class marked with @Dao should either be an interface or an abstract class. At compile time, Room will generate an implementation of this class when it is referenced by a Database.

    @Insert: Marks a method in a Dao annotated class as an insert method.

    @Update: Marks a method in a Dao annotated class as an update method.

    @Query: The value of the annotation includes the query that will be run when this method is called. This query is verified at compile time by Room to ensure that it compiles fine against the database. As we have only 1 entry as of now in table, we are using the query  “SELECT * FROM quote” to fetch the only record from table. Query is returning LiveData object, so whenever changes in data is made in db, all the observers will be notified of change and will update UI accordingly.


  • Database:

    @Database(entities = {Quote.class}, version = 1)
    public abstract class MyQuoteDB extends RoomDatabase {
    
        public abstract QuoteDAO quoteDAO();
    
    }
    



    @Database: Marks a class as a RoomDatabase. We have to mention entities along with this annotation. We have only 1 entity as of now which we have mentioned above along with the version number for db. Every time you make changes to db schema by changing fields in entity class (Quote.java), version number needs to be increased.

     

    Database class has only one method which will return instance of our DAO object which we will use to interact with actual db.


Now our all 2 components for Room are ready and we can modify QuoteRepository class to include offline caching feature of our app. Modified QuoteRepository class will look something like below.

class QuoteRepository {
    private QuoteAPI quoteAPI;
    private QuoteDAO quoteDAO;
    private  Executor executor;

    QuoteRepository(){

        this.executor = Executors.newSingleThreadExecutor();
        MyQuoteDB db = Room.databaseBuilder(context,
                MyQuoteDB.class, "database-name").build();
        quoteDAO=db.quoteDAO();

    }

    // ...
    LiveData<Quote> getQuote(String category, int count) {
        refreshQuote(category);
        return quoteDAO.getQuote();
    }

    private void refreshQuote(String category){
        
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();

        Retrofit.Builder builder = new Retrofit.Builder()
                .baseUrl("https://andruxnet-random-famous-quotes.p.mashape.com/")
                .client(client)
                .addConverterFactory(GsonConverterFactory.create());

        Retrofit retrofit = builder.build();

        quoteAPI = retrofit.create(QuoteAPI.class);

        quoteAPI.getQuote(category, 1).enqueue(new Callback<Quote> () {
            @Override
            public void onResponse(Call<Quote>  call, final Response<Quote>  response) {
                //update the db once fresh data is available
                Executors.newSingleThreadExecutor().execute(new Runnable() {
                    @Override
                    public void run() {
                        quoteDAO.save(response.body());
                    }
                });
            }

            @Override
            public void onFailure(Call<Quote>  call, Throwable t) {
                Log.v(Constants.TAG, t.getMessage());
            }
        });
    }

}

 

As you can see, QuoteRepository is same as our previous implementation for the most part but with the minor change. Instead of returning data from web service call, we are returning data (Quote object) from db and making change to db via web service call in background. Whenever data in db will change, observers will be notified to make changes in UI.

As we cannot access db in main thread, we need to run the db access operations in new thread (Executor).

Now after implementing this much part, our app will open with quote already loaded on screen and web service call being done in background. We have skipped exposing network status i.e. showing user that data is being loaded in background. That is topic for all together different article. You can know more about it here.

If you are following your development of app with article, it looks pretty basic (kind which is developed by noobies), and kind of useless. So was I wasting your time this whole time? No, I will demonstrate you in next article, how we can easily transform this app into something beautiful. Next article is going to be a bit lengthy so make sure you dive in with a coffee.

Cheers!

 

I will be writing more articles about tips and tricks for making most of your android application in future, subscribe our newsletter for getting the articles directly in your inbox. Subscription box can be found on top right side of this article of you are on PC and at the bottom of this article if you are on mobile device.

Sharing is caring!

3 comments
Leave a Reply

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