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
    }
});

ViewModel

ViewModel preserves data beyond the lifecycle of individual Activities i.e. it is lifecycle-aware – it will hold data for the UI even if e.g. the screen is rotated, causing the Activity to be remade. It persists from onCreate() until onCleared() (after finish() is applied to the Activity), thus helping to avoid re-querying a data source repeatedly upon Activity changes. This goes beyond the savedInstanceState which is limited to small amounts of data which can be easily serialised and de-serialised.

It also helps to avoid zombie threads (memory leaks), which were begun but not terminated as the Activity which created them was destroyed before they completed. When the Activity or Fragment is finished any Observers are closed, so no orphaned processes are left running.

Works in collaboration with LiveData. For more information see here. If your ViewModel needs access to the application context then extend AndroidViewModel rather than ViewModel (as below).

Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.

To add the dependencies for ViewModel, see here.

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

public class MainViewModel extends AndroidViewModel {

    LiveData<List<ContactEntry>> contacts;

    public MainViewModel(@NonNull Application application) {
        super(application);
        contacts = AppDatabase.getInstance(this.getApplication()).contactDao().loadAllContacts(); // Gets the List of contact entries from the instance of the database (see @DAO post)
    }

    public LiveData<List<ContactEntry>> getContacts() { // Create a getter for the LivaData List above
        return contacts;
    }
}

In your Activity, set an Observer on the getContacts() method in the ViewModel, and tell it what to do if the data changes:

MainViewModel viewModel = ViewModelProviders.of(this).get(MainViewModel.class);
viewModel.getContacts().observe(this, new Observer<List<ContactEntry>>() {
            @Override
            public void onChanged(@Nullable List<ContactEntry> contactEntries) {
                mAdapter.setContacts(contactEntries); // Code to run if data changes
            }
        });

For applications which require variables to be passed to the ViewModel refer to the ViewModelFactory post. The Factory holds a template for creating ViewModels that you can customise to allow passing arguments into the ViewModel constructor.

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
            }
        });

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.

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.