I am currently building an Inteval App for workout management. Basically, user can create preset with stages, and a stage can be timer-based or rep-based. Stage is like "REST", "WORKOUT, "COOLDOWN". I am currently stuck at getting all preset and theirs stages, if the stage is rep-based then get the reps too. The relation of entities are:
I asked ChatGPT about this and it suggested me to create relational entites. But I get the error like this:
The class must be either @Entity or @DatabaseView. public class StageWithReps
Here is my entities:
Preset.java
package com.example.intervalapp.entity;
import androidx.annotation.NonNull; import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.PrimaryKey;
import java.time.LocalDateTime;
@Entity(tableName = "preset") public class Preset { @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "preset_id") private int id;
@ColumnInfo(name = "preset_name") private String name;
@ColumnInfo(name = "created_date") private LocalDateTime createdDate;
@ColumnInfo(name = "modified_date") private LocalDateTime modifiedDate;
public Preset(String name, LocalDateTime createdDate, LocalDateTime modifiedDate) {
this.name = name;
this.createdDate = createdDate;
this.modifiedDate = modifiedDate; }
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 LocalDateTime getCreatedDate() {
return createdDate; }
public void setCreatedDate(LocalDateTime createdDate) {
this.createdDate = createdDate; }
public LocalDateTime getModifiedDate() {
return modifiedDate; }
public void setModifiedDate(LocalDateTime modifiedDate) {
this.modifiedDate = modifiedDate; }
@NonNull @Override public String toString() {
return "Preset{" +
"id=" + id +
", name='" + name + '\'' +
", createdDate=" + createdDate +
", modifiedDate=" + modifiedDate +
'}'; } }
Stage.java
package com.example.intervalapp.entity;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import com.example.intervalapp.enums.StageType;
import java.time.Duration;
@Entity(
tableName = "stage",
foreignKeys = @ForeignKey(
entity = Preset.class,
parentColumns = "preset_id",
childColumns = "preset_id",
onDelete = ForeignKey.CASCADE
),
indices = {@Index("preset_id")}
)
public class Stage {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "stage_id")
private int id;
@ColumnInfo(name = "preset_id")
private int presetId;
@ColumnInfo(name = "set_number")
private int setNumber;
@ColumnInfo(name = "stage_label")
private String label;
@ColumnInfo(name = "stage_type")
private String stageType; // E.g., "TIMER" or "REP"
@Nullable
@ColumnInfo(name = "duration")
private Duration duration; // Nullable for REP-based stages
@Nullable
@ColumnInfo(name = "num_reps")
private Integer numReps; // Nullable for TIMER-based stages
public Stage(int presetId, String label, int setNumber, String stageType, @Nullable Duration duration, @Nullable Integer numReps) {
this.presetId = presetId;
this.label = label;
this.setNumber = setNumber;
this.stageType = stageType;
// Assign the duration only for TIMER stages
if (StageType.TIME.toString().equals(stageType)) {
this.duration = duration;
this.numReps = null; // Ensure no reps value for TIMER stages
} else if (StageType.REPS.toString().equals(stageType)) {
this.duration = null; // Ensure no duration for REP stages
this.numReps = numReps;
}
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getPresetId() {
return presetId;
}
public void setPresetId(int presetId) {
this.presetId = presetId;
}
public int getSetNumber() {
return setNumber;
}
public void setSetNumber(int setNumber) {
this.setNumber = setNumber;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getStageType() {
return stageType;
}
public void setStageType(String stageType) {
this.stageType = stageType;
}
@Nullable
public Duration getDuration() {
return duration;
}
public void setDuration(@Nullable Duration duration) {
this.duration = duration;
}
@Nullable
public Integer getNumReps() {
return numReps;
}
public void setNumReps(@Nullable Integer numReps) {
this.numReps = numReps;
}
@NonNull
@Override
public String toString() {
return "Stage{" +
"id=" + id +
", presetId=" + presetId +
", label='" + label + '\'' +
", setNumber=" + setNumber +
", stageType='" + stageType + '\'' +
", duration=" + duration +
", numReps=" + numReps +
'}';
}
}
Rep.java
package com.example.intervalapp.entity;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import androidx.room.PrimaryKey;
@Entity(
tableName = "rep",
foreignKeys = @ForeignKey(
entity = Stage.class,
parentColumns = "stage_id",
childColumns = "stage_id",
onDelete = ForeignKey.CASCADE
),
indices = {@Index("stage_id")}
)
public class Rep {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "rep_id")
private int id;
@ColumnInfo(name = "stage_id")
private int stageId;
@ColumnInfo(name = "rep_duration")
private float repDuration;
public Rep(int stageId, float repDuration) {
this.stageId = stageId;
this.repDuration = repDuration;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getStageId() {
return stageId;
}
public void setStageId(int stageId) {
this.stageId = stageId;
}
public float getRepDuration() {
return repDuration;
}
public void setRepDuration(int repDuration) {
this.repDuration = repDuration;
}
@NonNull
@Override
public String toString() {
return "Rep{" +
"id=" + id +
", stageId=" + stageId +
", repDuration=" + repDuration +
'}';
}
}
PresetWithStages.java
package com.example.intervalapp.relation;
import androidx.room.Embedded;
import androidx.room.Relation;
import com.example.intervalapp.entity.Preset;
import java.util.List;
public class PresetWithStages {
@Embedded
private Preset preset;
@Relation(
parentColumn = "preset_id",
entityColumn = "preset_id"
)
private List<StageWithReps> stages;
// Getters and Setters
public Preset getPreset() {
return preset;
}
public void setPreset(Preset preset) {
this.preset = preset;
}
public List<StageWithReps> getStages() {
return stages;
}
public void setStages(List<StageWithReps> stages) {
this.stages = stages;
}
}
StageWithReps.java
package com.example.intervalapp.relation;
import androidx.room.Embedded;
import androidx.room.Relation;
import com.example.intervalapp.entity.Stage;
import com.example.intervalapp.entity.Rep;
import java.util.List;
public class StageWithReps {
@Embedded
private Stage stage;
@Relation(
parentColumn = "stage_id",
entityColumn = "stage_id"
)
private List<Rep> reps;
// Getters and Setters
public Stage getStage() {
return stage;
}
public void setStage(Stage stage) {
this.stage = stage;
}
public List<Rep> getReps() {
return reps;
}
public void setReps(List<Rep> reps) {
this.reps = reps;
}
}
PresetDAO.java
package com.example.intervalapp.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Transaction;
import androidx.room.Update;
import com.example.intervalapp.entity.Preset;
import com.example.intervalapp.relation.PresetWithStages;
import java.time.LocalDateTime;
import java.util.List;
@Dao
public interface PresetDAO {
@Insert
long createPreset(Preset preset);
@Transaction
@Query("SELECT * FROM preset")
LiveData<List<PresetWithStages>> getAllPresets(); // Fetch hierarchical data
}
Can I get some help 🥲. This is my first time uploading a post on stackoverflow, so if I miss something, please tell me. Thank you everyone!
Your issue is with:-
@Relation(
parentColumn = "preset_id",
entityColumn = "preset_id"
)
private List<StageWithReps> stages;
The @Relation
implies that StageWithReps is expected to be a table (and thus @Entity
annotated) from which the children (Stages) will be retrieved. If the children are an expansion then that underlying @Relation
will then be processed to build the children.
implied as the class (table), unless specified via the entity
parameter, is the class of the member/field that the @Relation
annotation applies to and hence the not an @Entity
.
The current StageWithRep will then use the overarching Stage to then retrieve the related Reps.
To get the StageWithReps you need to get the parent(@Embedded
) Stage from the Stage table by specifying the Stage class via the entity
parameter of the @Relation
annotation.
As such, you likely want:-
@Relation(
entity = Stage.class, /*<<<<<<<<<< ADDED */
parentColumn = "preset_id",
entityColumn = "preset_id"
)
private List<StageWithReps> stages;
Demo
Using the above and some modifications for simplicty:-
.allowMainThreadQueries
for brevity of ruuning on the main threadThen consider the following activity code:-
public class MainActivity extends AppCompatActivity {
private TheDatabase db;
private PresetDAO dao;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/* Get the database and the DAO */
db = TheDatabase.getInstance(this);
dao = db.getPresetDAO();
/* Insert some Presets */
long p1 = dao.createPreset(new Preset("P001"));
long p2 = dao.createPreset(new Preset("P002"));
long p3 = dao.createPreset(new Preset("P003"));
/* Insert some Stages related to the Presets (not p3 is not referenced)*/
long s1 = dao.creatStage(new Stage(((int) p1),"S001",100,"TYPEA",1000L,5));
long s2 = dao.creatStage(new Stage(((int) p1),"S002",200,"TYPEB",2000L,25));
long s3 = dao.creatStage(new Stage(((int) p2),"S003",300,"TYPEC",3000L,35));
/* Insert some Reps related to Stages */
dao.createRep(new Rep((int)s1,10.1234F));
dao.createRep(new Rep((int)s1,11.1234F));
dao.createRep(new Rep((int)s1,12.1234F));
dao.createRep(new Rep((int)s2,13.1234F));
dao.createRep(new Rep((int)s2,14.1234F));
dao.createRep(new Rep((int)s3,15.1234F));
/* Extract all of the PresetWithStages */
/* first construct empty StringBuilders to cater for a single Log per extracted Preset*/
StringBuilder stages_in_preset = new StringBuilder();
StringBuilder reps_in_stages = new StringBuilder();
/* use the extract for the core loop*/
for (PresetWithStages pws: dao.getAllPresets()) {
/* new Preset so effectively empty the StringBuilder for the stages of the current Preset */
stages_in_preset = new StringBuilder();
/* Loop through the stages of the current preset */
for (StageWithReps swr: pws.getStages()) {
/* new Stage so empty the StringBuilder for the reps of the current Stage */
reps_in_stages = new StringBuilder();
for (Rep r: swr.getReps()) {
/* Add line feed and 2 tabs and then the info for the current Rep */
reps_in_stages.append("\n\t\t");
reps_in_stages.append("Rep ID is " + r.getId());
reps_in_stages.append(" DURATION is " + r.getRepDuration());
reps_in_stages.append(" REFERENCES STAGE WITH ID " + r.getStageId());
}
/* add the current Stage info and the info for all the reps */
stages_in_preset.append("\n\tStage is " + swr.getStage().getLabel());
stages_in_preset.append(" Number of Reps is " + swr.getReps().size() + ". They are:-" + reps_in_stages);
}
/* For the current Preset Log the Preset and all the Stage info (indented) and for each Stage the Rep info (double indented) */
Log.d("DBINFO","PRESET is " + pws.getPreset().getName() + " it has " + pws.getStages().size() + " Stages. They are:-" + stages_in_preset);
}
}
}
This outputs (to the log):-
D/DBINFO: PRESET is P001 it has 2 Stages. They are:-
Stage is S001 Number of Reps is 3. They are:-
Rep ID is 1 DURATION is 10.1234 REFERENCES STAGE WITH ID 1
Rep ID is 2 DURATION is 11.1234 REFERENCES STAGE WITH ID 1
Rep ID is 3 DURATION is 12.1234 REFERENCES STAGE WITH ID 1
Stage is S002 Number of Reps is 2. They are:-
Rep ID is 4 DURATION is 13.1234 REFERENCES STAGE WITH ID 2
Rep ID is 5 DURATION is 14.1234 REFERENCES STAGE WITH ID 2
D/DBINFO: PRESET is P002 it has 1 Stages. They are:-
Stage is S003 Number of Reps is 1. They are:-
Rep ID is 6 DURATION is 15.1234 REFERENCES STAGE WITH ID 3
D/DBINFO: PRESET is P003 it has 0 Stages. They are:-