-
Notifications
You must be signed in to change notification settings - Fork 1
Data model
In the application, there are two sources of data: local and remote. The local database is used to store the recipes of the current user, whilst the remote one is used to get more recipes from an API. In this document, we are going to describe how to access the data inside the application.
The local database is used to store the user's recipes and favourite ones. ***MySQL *** manages it, and the application uses Room library to perform operations on it.
The classes represented in the application have an Entity, representing a table in the database. By convention, all properties from Kotlin classes have to be written in camel case; however, attributes and table names from databases should be written in lowercase with underscores.
Each entity is a data class annotated with @Entity
and has a name passed as an argument to the annotation.
@Entity(tableName = "recipes")
data class Recipe(
// ...
)
Next we need to define the attributes of the entity. In order to specify the name of the attribute in the table we have to annotate the Kotlin properties with @ColumnInfo
and passing the name as a parameter. The name of the attribute will be the property written in lowercase and with underscores.
@Entity(tableName = "recipes")
data class Recipe(
@ColumnInfo(name = "recipe_id")
val recipeId: Long,
@ColumnInfo(name = "name")
val name: String,
@ColumnInfo(name = "type")
val type: RecipeType,
@ColumnInfo(name = "description")
val description: String,
@ColumnInfo(name = "time")
val time: Int,
@ColumnInfo(name = "steps")
val steps: List<String>
)
After defining the properties of the class, we need to specify the primary key of the entity, which is the property that identifies every instance. It is annotated with @PrimaryKey
and might be autogenerated. In this case, it must always be a Long
, and when creating the instance, the value 0L
should be passed.
@Entity(tableName = "recipes")
data class Recipe(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "recipe_id")
val recipeId: Long,
// ...
)
Note that there are some type converters defined in Converters.kt, which are used to deal with enums and collections in database.
@TypeConverter
fun toRecipeType(value: String) = enumValueOf<RecipeType>(value)
@TypeConverter
fun fromRecipeType(value: RecipeType) = value.name
In addition, some relations have been implemented as classes due to Room requirements. These are located in the relations package and might get information from the database since there are entities. Suppose we want to create a relation between Recipe
and Ingredient
since every recipe has at least one ingredient, and every ingredient can belong to some recipes. On the one hand, we define the cross-reference, which will be called RecipeIngredientCrossRef
. The name order does not matter, but it is strongly recommended to put first the entity from which we will access the relation more frequently. The class has to be annotated with @Entity
and has a property for each primary key in the relation. The primary key of these entities will be the composition of both properties. In addition, you might add as many additional attributes as you wish.
@Entity(tableName = "recipe_ingredient", primaryKeys = ["recipe_id", "ingredient_name"])
data class RecipeIngredientCrossRef(
@ColumnInfo(name = "recipe_id")
val recipeId: Long,
@ColumnInfo(name = "ingredient_name")
val ingredientName: String,
@ColumnInfo(name = "quantity")
val quantity: Double
)
On the other hand, we need to define the relation itself. The class will be called RecipeWithIngredient
since we want to get all ingredients from it. The Recipe
instance has to be annotated with @Embedded
since it is the starting point of the relation. Since we want to know the ingredients of a recipe, we have to add a list of IngredientEntity
.
data class RecipeWithIngredient(
@Embedded val recipeEntity: RecipeEntity,
@Relation(
parentColumn = "recipe_id",
entityColumn = "ingredient_name",
associateBy = Junction(RecipeIngredientCrossRef::class)
)
val ingredientEntities: List<IngredientEntity>,
)
In order to improve readability, there has been defined a set of typealises to use the cross references.
typealias UserRecipe = UserRecipeCrossRef
typealias RecipeIngredient = RecipeIngredientCrossRef
typealias UserFavoriteRemoteRecipe = UserFavoriteRemoteRecipeCrossRef
To get the remote recipes, we are using the Fuel library. We have created a DAO to use the Edamam API so that if we decide to change the API in the future, we will only have to create a class that implements the RemoteRecipeDao
interface.
Table of contents
Updates