Intent Service

A Service which runs off a completely separate thread to the main. All IntentService requests are handled on a single background thread and are issued in order. Therefore IntentServices are good for tasks that need to happen in order.

Services must be registered in the AndroidManifest.xml:

        <service
            android:name=".sync.myIntentService"
            android:exported="false"
            ></service>

An Intent Service can be started in a very similar way to an Activity:

Intent myIntent = new Intent(this, myIntentService.class);
startService(myIntent);

Extra data can be attached to the Intent when starting the Service, as with Activities:

Intent myIntent = new Intent(this, myIntentService.class);
myIntent.setAction("Some specific action");
startService(myIntent);

To create the Service, extend IntentService. Override the onHandle Intent method to tell it what to do in the background:

public class MyIntentService extends IntentService {

    @Override
    protected void onHandleIntent(Intent intent) {
        String action = intent.getAction(); //Add this line if extra data attached
        //Do background work here
    }
}

The IntentService will then stop itself when it is finished.

ud851-Exercises-student\Lesson10-Hydration-Reminder\T10.01

ViewModelFactory

ViewModelFactory allows us to produce custom ViewModels that allow for the inclusion of arguments in the Constructor (standard ViewModels do not).

To implement a ViewModelFactory, first create a ViewModelFactory class (this example corresponds to the code in the @Entity, @DAO, Build a Database and LiveData posts):

public class AddContactViewModelFactory extends ViewModelProvider.NewInstanceFactory {

    private final AppDatabase mDb;
    private final int mContactId;

    public AddTaskViewModelFactory(AppDatabase mDb, int mContactId) { // Arguments to be passed in
        this.mDb = mDb;
        this.mContactId = mContactId;
    }


    // Note: This can be reused with minor modifications
    @Override
    public <T extends ViewModel> T create(Class<T> modelClass) { // When ViewModelFactory is created it returns a new ViewModel with the arguments fed in
        //noinspection unchecked
        return (T) new AddContactViewModel(mDb, mContactId);
    }
}

Then create the class declared in the ViewModelFactory's create() override:

public class AddContactViewModel extends ViewModel {

    private LiveData<ContactEntry> contactEntry;

    public AddTaskViewModel(AppDatabase database, int contactId) { // Constructor which takes the arguments
        contactEntry = database.contactDao().loadContactById(contactId); // Code which retrieves the LiveData object through the @DAO
    }

    public LiveData<ContactEntry> getContactEntry() { // Method of delivering the data to the Activity
        return contactEntry;
    }
}

In the Activity:

AddContactViewModelFactory addContactViewModelFactory = new AddContactViewModelFactory(mDb, mContactId); // Feed in arguments to the ViewModelFactory
final  AddContactViewModel viewModel = ViewModelProviders.of(this, addContactViewModelFactory).get(AddContactViewModel.class); // Create the View Model, including the ViewModelFactory in the arguments
                viewModel.getContactEntry().observe(this, new Observer<ContactEntry>() { // Observe the LiveData object which is cached in the ViewModel
    @Override
    public void onChanged(@Nullable ContactEntry contactEntry) {
        viewModel.getContactEntry().removeObserver(this); // Not quite sure why we've removed the Observer here
        populateUI(contactEntry); // Apply changes to UI
    }
});

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.

@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.

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.