LiveData

LiveData is an observable data holder class. This means it sits between the database and UI, and monitors changes in the database. On observing a change, the LiveData object (will have its setValue method called and) will notify the Observers which will reflect the new data in the UI. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state.

Runs by default outside of the main thread.

Often used in conjunction with ViewModel.

To add the dependencies for LiveData, see here.

To observe data in a database, simply wrap the return type in a LiveData<> tag (this example corresponds to the code in the @Entity, @DAO and Build a Database posts). In the @DAO file:

@Dao
public interface ContactDao {

    @Query("SELECT * FROM contacts ORDER BY age")
    LiveData<List<ContactEntry>> loadAllContacts();

    @Insert
    void insertContact(ContactEntry contactEntry);
 
    @Update(onConflict = OnConflictStrategy.REPLACE)
    void updateContact(ContactEntry contactEntry);
 
    @Delete
    void deleteContact(ContactEntry contactEntry);
 
    @Query("SELECT * FROM contacts WHERE name = :name")
    ContactEntry loadEntryByName(String name);
}

Any references/calls to this method will need to have their type wrapped in LiveData<> also.
We set up an Observer in our Activity (do this just once at onCreate and not e.g. in onResume so that the data will only be refreshed when it is changed):

        final LiveData<List<ContactEntry>> contacts = mDb.contactDao().loadAllContacts(); // mDb is instance of database (extends RoomDatabase). contactDao is the @DAO for the database (see above)
        contacts.observe(this, new Observer<List<ContactEntry>>() { // LivecycleOwner is set to 'this' in this example
            @Override
            public void onChanged(@Nullable List<ContactEntry> contactEntries) {
                // Do something with the new data
            }
        });

@TypeConverters – SQL/Room

When storing data in SQLite databases it must conform to the accepted data types e.g. a date must be stored in TEXT, REAL or INTEGER format. Therefore we can specify code to convert a date from a timestamp to a Date object (and back), and Room will use these when reading or writing data. TypeConverters must be declared when Building A Database.

public class DateConverter {
@TypeConverter
public static Date toDate(Long timestamp) { // Room will use this when reading from the database
return timestamp == null ? null : new Date(timestamp);
}

@TypeConverter
public static Long toTimestamp(Date date) { { // Room will use this when writing to the database
return date == null ? null : date.getTime();
}
}

Build a database – Room

Code to create a Room database in correspondence with the @Entity and @DAO entries.

Create an abstract class to outline the creation of your table

@Database(entities = {ContactEntry.class}, version = 1, exportSchema = false) // Add @Database annotation and declare class(es) containing @Entity
@TypeConverters(DateConverter.class) // See the @TypeConverters post: https://an.aldezu.com/archives/454 - not in use in this example but included for context
public abstract class AppDatabase extends RoomDatabase { // Extend RoomDatabase

    private static final String LOG_TAG = AppDatabase.class.getSimpleName();
    private static final Object LOCK = new Object();
    private static final String DATABASE_NAME = "contacts"; // Define database name
    private static AppDatabase sInstance;

    public static AppDatabase getInstance(Context context) {
        if (sInstance == null) { // This will only ever generate ONE instance of the AppDatabase (Singleton pattern)
            synchronized (LOCK) {
                Log.d(LOG_TAG, "Creating new database instance");
                sInstance = Room.databaseBuilder(context.getApplicationContext(), // Requires current context, the database class and database name
                        AppDatabase.class, AppDatabase.DATABASE_NAME)
                        .build();
            }
        }
        Log.d(LOG_TAG, "Getting the database instance");
        return sInstance; // Will return new instance if null, old instance if not
    }

    public abstract ContactDao contactDao(); // Create an instance of the DAO for this database

}

To interact with the table you will need a @DAO. To define the table you will need to use @Entity.

@DAO (Data Access Object) – Room

An interface (API) which allows us to interact with our SQLite database tables. This example corresponds to the @Entity and Build A Database entries. Once these have been coded use LiveData to observe the data.

Create a custom class to define your table, using Entity to annotate it

@Dao // Required
public interface ContactDao {

    @Query("SELECT * FROM contacts ORDER BY age") // Database query
    List<ContactEntry> loadAllContacts(); // When loadAllContacts() is  called it will return a List<ContactEntry> containing the results of the query

    @Insert
    void insertContact(ContactEntry contactEntry); // Inserts a new ContactEntry into the table

    @Update(onConflict = OnConflictStrategy.REPLACE) // Set to replace (other options: Abort, Fail, Ignore, Rollback)
    void updateContact(ContactEntry contactEntry); // Update an existing Contact entry

    @Delete
    void deleteContact(ContactEntry contactEntry); // Delete an existing Contact entry

    @Query("SELECT * FROM contacts WHERE name = :name") // We can use the 'name' variable in the query by prefixing it with :
    ContactEntry loadEntryByName(String name); // Returns the ContactEntry which matches the name input by the user
}

To generate the table you will need to Build A Database. To define the table you will need to use @Entity.

@Entity – Room

Defines our SQLite database tables. This example corresponds to the @DAO and Build A Database entries.

Create a custom class to define your table, using Entity to annotate it

@Entity(tableName = "contacts") // Name of table specified as 'contacts'. It is common for database tables and their corresponding classes to not have same name
public class ContactEntry {

    @PrimaryKey(autoGenerate = true) // States that the next variable will be the table's primary key (and therefore unique)
    private int id; // Each member variable corresponds to a column in the table
    private String name; // Each member variable corresponds to a column in the table
    @ColumnInfo(name = "contactAge") // If you wish to specifically state the name of the column use this annotation
    private int age; // Each member variable corresponds to a column in the table

    @Ignore // If you do not want a variable to appear in the table use the @ignore annotation
    private String address;

    @Ignore // Tell Room to use the other constructor (which includes the id field) using the @Ignore annotation, as Room can only use one constructor. This constructor will be used when the user adds a new entry, as they will not know the id of the new entry.
    public ContactEntry (String name, int age) {
        this.description = name;
        this.age = age;
    }

    public ContactEntry (int id, String name, int age) {
        this.id = id;
        this.description = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name= name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

To create the table you will need to Build A Database. To interact with the table you will need to create a @DAO.

Add Android Architecture Component Dependencies

To add the desired elements of Android Jetpack to your app. These will need to be updated as the version numbers increment.

Use the following in your build.gradle: Futures

dependencies {
    def futures_version = "1.0.0-alpha02"

    implementation "androidx.concurrent:concurrent-futures:$futures_version"
}

Lifecycle AndroidX

dependencies {
    def lifecycle_version = "2.0.0"

    // ViewModel and LiveData
    implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
    // alternatively - just ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" // use -ktx for Kotlin
    // alternatively - just LiveData
    implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
    // alternatively - Lifecycles only (no ViewModel or LiveData). Some UI
    //     AndroidX libraries use this lightweight import for Lifecycle
    implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"

    annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" // use kapt for Kotlin
    // alternately - if using Java8, use the following instead of lifecycle-compiler
    implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"

    // optional - ReactiveStreams support for LiveData
    implementation "androidx.lifecycle:lifecycle-reactivestreams:$lifecycle_version" // use -ktx for Kotlin

    // optional - Test helpers for LiveData
    testImplementation "androidx.arch.core:core-testing:$lifecycle_version"
}

Lifecycle pre-AndroidX

dependencies {
    def lifecycle_version = "1.1.1"

    // ViewModel and LiveData
    implementation "android.arch.lifecycle:extensions:$lifecycle_version"
    // alternatively - just ViewModel
    implementation "android.arch.lifecycle:viewmodel:$lifecycle_version" // use -ktx for Kotlin
    // alternatively - just LiveData
    implementation "android.arch.lifecycle:livedata:$lifecycle_version"
    // alternatively - Lifecycles only (no ViewModel or LiveData).
    //     Support library depends on this lightweight import
    implementation "android.arch.lifecycle:runtime:$lifecycle_version"

    annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version" // use kapt for Kotlin
    // alternately - if using Java8, use the following instead of compiler
    implementation "android.arch.lifecycle:common-java8:$lifecycle_version"

    // optional - ReactiveStreams support for LiveData
    implementation "android.arch.lifecycle:reactivestreams:$lifecycle_version"

    // optional - Test helpers for LiveData
    testImplementation "android.arch.core:core-testing:$lifecycle_version"
}

Room AndroidX

dependencies {
    def room_version = "2.1.0-alpha03"

    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version" // use kapt for Kotlin

    // optional - RxJava support for Room
    implementation "androidx.room:room-rxjava2:$room_version"

    // optional - Guava support for Room, including Optional and ListenableFuture
    implementation "androidx.room:room-guava:$room_version"

    // optional - Coroutines support for Room
    implementation "androidx.room:room-coroutines:$room_version"

    // Test helpers
    testImplementation "androidx.room:room-testing:$room_version"
}

Room pre-AndroidX

dependencies {
    def room_version = "1.1.1"

    implementation "android.arch.persistence.room:runtime:$room_version"
    annotationProcessor "android.arch.persistence.room:compiler:$room_version" // use kapt for Kotlin

    // optional - RxJava support for Room
    implementation "android.arch.persistence.room:rxjava2:$room_version"

    // optional - Guava support for Room, including Optional and ListenableFuture
    implementation "android.arch.persistence.room:guava:$room_version"

    // Test helpers
    testImplementation "android.arch.persistence.room:testing:$room_version"
}

Paging AndroidX

dependencies {
    def paging_version = "2.1.0-rc01"

    implementation "androidx.paging:paging-runtime:$paging_version" // use -ktx for Kotlin

    // alternatively - without Android dependencies for testing
    testImplementation "androidx.paging:paging-common:$paging_version" // use -ktx for Kotlin

    // optional - RxJava support
    implementation "androidx.paging:paging-rxjava2:$paging_version" // use -ktx for Kotlin
}

Paging pre-AndroidX

dependencies {
    def paging_version = "1.0.0"

    implementation "android.arch.paging:runtime:$paging_version"

    // alternatively - without Android dependencies for testing
    testImplementation "android.arch.paging:common:$paging_version"

    // optional - RxJava support
    implementation "android.arch.paging:rxjava2:$paging_version"
}

Navigation

dependencies {
    def nav_version = "1.0.0-alpha09"

    implementation "android.arch.navigation:navigation-fragment:$nav_version" // use -ktx for Kotlin
    implementation "android.arch.navigation:navigation-ui:$nav_version" // use -ktx for Kotlin
}

Work Manager

dependencies {
    def work_version = "1.0.0-beta02"

    implementation "android.arch.work:work-runtime:$work_version" // use -ktx for Kotlin+Coroutines

    // optional - RxJava2 support
    implementation "android.arch.work:work-rxjava2:$work_version"

    // optional - Test helpers
    androidTestImplementation "android.arch.work:work-testing:$work_version"
}

See more here

Room

Room is one of the Android App Architecture Components. The Room persistence library (Object Relational Mapping library) provides an abstraction layer over SQLite to allow for more robust database access while harnessing the full power of SQLite. It will map our database objects to Java objects.

Its features include:

  • Less boilerplate code
  • SQL Validation at compile time – incorrect SQL statements will be picked up
  • Built to work with LiveData and RxJava for data observation

Room uses Annotations and has 3 main components:

  • @Entity – to define our database tables
  • @DAO – provides an API for reading and writing data
  • @Database – represents a database holder. Includes a list of @Entity and @DAO annotations and allow us to create a new database or acquire a connection to our db at runtime

To add the dependencies for Room, see here.