Using Room in CryptoOracle

In the Android application CryptoOracle the data is persisted in SQLite database. The reasons are two:
  • The application can still work and show persisted data when there is no internet connection.
  • The network traffic is reduced because requests are sent from the application only when the data is outdated and new data is available to be persisted.
For storing data locally is used the library Room. If you are not familiar with the principles of Room, please read the previous article. The object which is persisted is called Coin and it represents daily information for a particular cryptocurrency. This information consists of:
  • name of the cryptocurrency
  • date for which the other information in the object is
  • value representing the real close price for the day
  • value representing the prediction of the close price for this day
For this purpose we create a simple POJO Coin. It has properties for each of the above attributes. The class looks like this.
public class Coin {

    private String cryptoCurrency;
    private double realValue;
    private double predictedValue;
    private String date;

}
The next step is to tell Room that this class is going to be used as a table schema. To do this we annotate the class with the @Entity annotation.
@Entity
public class Coin {

    private String cryptoCurrency;
    private double realValue;
    private double predictedValue;
    private String date;

}
Now the Coin POJO corresponds to a table in the database with the same name. If we want our table to have a different name we can add the tableName parameter to the @Entity annotation as follows – @Entity(tableName = “CryptoCoinTable”).
Now we must tell Room which property of our POJO to use as a primary key when persisting data in the database. First we need to add an id field and then annotate it with the @PrimaryKey annotation. The process is shown below.
@Entity
public class Coin{

    @PrimaryKey
    @NonNull
    private String id;
    private String cryptoCurrency;
    private double realValue;
    private double predictedValue;
    private String date;

}
The @NonNull annotation tells Room that the id property can not have a null value. Because the Coin objects come as a result of a HTTP GET request to the REST API, they already have id assigned to them by the MongoDB. That’s why we don’t set the @PrimaryKey’s autoGenerate property to true. Another thing we can do is add the @Column(name=”someName”) annotation to our properties to explicitly specify the names of the columns in the database. By not doing this, Room uses the names of the properties as default names for the corresponding columns.
Finally we need to add an all arguments constructor to our POJO and getters and setters for our fields. This is required by Room to do its work correctly. With this the Coin class is finished.
@Entity
public class Coin{

    @PrimaryKey
    @NonNull
    private String id;
    private String cryptoCurrency;
    private double realValue;
    private double predictedValue;
    private String date;


    public Coin(String id, String cryptoCurrency, double realValue, double predictedValue, String date) {
        this.id = id;
        this.cryptoCurrency = cryptoCurrency;
        this.realValue = realValue;
        this.predictedValue = predictedValue;
        this.date = date;
    }

    @NonNull
    public String getId() {
        return id;
    }

    public void setId(@NonNull String id) {
        this.id = id;
    }

    public String getCryptoCurrency() {
        return cryptoCurrency;
    }

    public void setCryptoCurrency(String cryptoCurrency) {
        this.cryptoCurrency = cryptoCurrency;
    }

    public double getRealValue() {
        return realValue;
    }

    public void setRealValue(double realValue) {
        this.realValue = realValue;
    }

    public double getPredictedValue() {
        return predictedValue;
    }

    public void setPredictedValue(double predictedValue) {
        this.predictedValue = predictedValue;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }
}
 
The next step is to create a DAO. DAO stands for data access object. It contains all the methods used for accessing the database. To begin with, we create an interface as shown below. We can also use abstract class instead of interface.
public interface CoinDao {

}
Then we annotate the interface with the @Dao annotation. This way we tell Room to use it to generate an implementation of this interface at compile time when it is referenced by a @Database annotated class.
@CoinDao
public interface CoinDao {

}
Finally we add all the methods used for database interactions. Room comes with some out-of-the-box methods of the basic CRUD operations like save, delete, etc. We can also create custom complex queries as shown below. To do so, we use the @Query annotation and pass a String SQL statement as its parameter. Then we create a method definition corresponding to the SQL statement. Each time we call a DAO’s method the corresponding SQL statement is executed and a result is returned as the defined Java structure. The final two queries return a LiveData object for which we will talk more on a next chapter. But for now, it is an observable holder and it is part of the Android Architecture Components.
@Dao
public interface CoinDao {

    @Insert(onConflict = REPLACE)
    void save(Coin coin);

    @Insert(onConflict = REPLACE)
    void saveAll(List<Coin> coins);

    @Delete
    void delete(Coin coin);

    @Query("DELETE FROM Coin Where cryptoCurrency = :cryptoCurrency")
    void deleteAll(String cryptoCurrency);

    @Query("SELECT * FROM Coin WHERE date = :date")
    LiveData<List<Coin>> getPredictionsForToday(String date);

    @Query("SELECT * FROM Coin WHERE cryptoCurrency = :cryptoCurrency")
    LiveData<List<Coin>> getHistoricalData(String cryptoCurrency);

}

The very final thing we need to do is create a Database class responsible for the database creation.
@Database(entities = {Coin.class}, version = 1, exportSchema = false)
public abstract class CryptoDatabase extends RoomDatabase {

    public abstract CoinDao coinDao();

    private static CryptoDatabase INSTANCE;

    public static CryptoDatabase getDatabaseInstance() {
        if(INSTANCE == null) {
            INSTANCE = Room.databaseBuilder(App.getAppContext(), CryptoDatabase.class, "crypto_mind_database").build();
        }
        return INSTANCE;
    }

    public static void destroyDatabase() {
        INSTANCE = null;
    }
}
The class is annotated with the @Database annotation which makes it responsible for the creation of the tables. The annotation has the following properties:
  • entities – a list of all the POJOs used as table schemas
  • version of the database
  • exportSchema, in this case the property is set to false
The class must extend the RoomDatabase class which is part of the Room library. The CryptoDatabase class consists of abstract method that has 0 arguments and returns the class which is annotated with @Dao. It must contain as many abstract methods as the count of the Data Access Objects for the database. It also has a static variable called INSTANCE and a getDatabaseInstance() method. The getDatabaseInstance() method provides a Singleton instance of the class whenever the method is invoked in the application by using a lazy initialization. The database instance is provided by a Builder pattern – Room.databaseBuilder(). The builder takes the following parameters – context, class used for database creation (in this case CryptoDatabase) and name for the database specified as String. The class has one additional method called destroyDatabase() used for destruction of the database.