Persistence

Last updated 5 months ago

Overview

The data persistence layer primarily uses ObjectBox, a super fast mobile database that persists objects. It lets you avoid many repetitive tasks and offers a simple interface to your data. Unlike other databases, it has been built from the ground up using key-value storage instead of column storage. The resulting performance is 10x faster than the leading alternative.

Database Helper Class

The database class exists to make access to the object box instance easier and it also exposes a couple of methods for basic application CRUD operations. In case you ever need to do a custom query that is not in the scope of the helper methods you can always call on the getBoxStore(Class<S> classType) where S is the class type that has the @Entity annotation for object box, e.g.

/**
* Gets all saved genres
*/
@Override
public List<Genre> getGenreCollection() {
return getBoxStore(Genre.class).query()
.build().findLazy();
}

com.mxt.anitrend.base.interfaces.dao.BoxQuery is the base for all the helper methods implemented into the helper class. N.B. all the helper methods for Read operations may return null, so take care to do null checking prior before assuming the database contains the data you want to query.

Database Utility Classes

In the same package as our DatabaseHelper you will notice a converter package, this is a package that contains classes responsible for converting custom types which are not native database objects. Things like lists, enums, dictionaries and other objects a user created. Take MediaListOptionsConverter for example:

public class MediaListOptionsConverter implements PropertyConverter<MediaListOptions, String> {
@Override
public MediaListOptions convertToEntityProperty(String databaseValue) {
if(databaseValue == null)
return null;
return WebFactory.gson.fromJson(databaseValue, MediaListOptions.class);
}
@Override
public String convertToDatabaseValue(MediaListOptions entityProperty) {
if(entityProperty == null)
return null;
return WebFactory.gson.toJson(entityProperty);
}
}

MediaListOptionsConverter converts the MediaListOptions object into a json string to store in our database and converts it back to a java object from json database value and the usage would be like:

@Entity
public class User extends UserBase {
private String about;
@Convert(converter = UserOptionsConverter.class, dbType = String.class)
private UserOptions options;
@Convert(converter = MediaListOptionsConverter.class, dbType = String.class)
private MediaListOptions mediaListOptions;
@Convert(converter = UserStatsConverter.class, dbType = String.class)
private UserStats stats;
private int unreadNotificationCount;
}

You can find more information about converters for custom types on the ObjectBox Website

Preferences Class

Preferences can be referred as a persistence technology, just not in the same way we'd use a database for, despite preferences being secure in the device /data/data/app.package.name the values are stored in a simple and plain .xml file which can be read on a device with root access.

In the context of this application we will only use preferences for storing simple settings related values. A utility class:ApplicationPref for preferences has been created for you and can be found in com.mxt.anitrend.util

Preferably use a CommonPresenter derived class to access the preference utility class, unless your use case doesn't require a presenter, e.g. App class

For any newly created preferences make sure that your keys are either stored in strings.xml if the setting is set from a preference screen, otherwise make sure the keys are stored in the class and never hard coded strings e.g.

public class ApplicationPref {
private Context context;
/** Base Application Values */
private final String _versionCode = "_versionCode";
private final String _freshInstall = "_freshInstall";
private final String _isAuthenticated = "_isAuthenticated";
public boolean isAuthenticated() {
return sharedPreferences.getBoolean(_isAuthenticated, false);
}
public void setAuthenticated(boolean authenticated) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(_isAuthenticated, authenticated);
editor.apply();
}
public boolean isAmoledEnabled() {
return sharedPreferences.getBoolean(context.getString(R.string.pref_key_amoled_theme), false);
}
}

Notice howisAmoledEnabled() makes use of a string resource instead of a declared key field like isAuthenticated or setAuthenticated, this is because the amoled theme value is set from a preference screen in the settings activity of the application.

Always remember to make use of the annotations available for methods in your preference methods when getting values such as:

@KeyUtil.SortOrderType String getSortOrder()
// or
void saveSortOrder(@KeyUtil.SortOrderType String sortOrder)

This reduces the possibility of making mistakes by saving or applying the wrong settings which may cause unpredictable bugs