I am trying to save a model that has IsarLinks which also each have an IsarLink. Saving IsarLinks (sets as WorkoutSets) works but the IsarLink(exercise as Exercise) inside is not persisted. Following is the method to save the models as well as the models themselves.
///contained in a separate class
static saveWorkout(Workout workout) async {
await isar!.writeTxn(() async {
List<Exercise> exercises = workout.workoutExercises.toList();
await isar!.workouts.put(workout);
for (Exercise exercise in exercises) {
await isar!.exercises.put(exercise);
workout.exercises.add(exercise);
}
for (Exercise exercise in exercises) {
List<WorkoutSet> sets = exercise.sets.toList();
for (WorkoutSet set in sets) {
await isar!.workoutSets.put(set); // Save set with link
set.exercise.value = exercise; // Link set to exercise
await set.exercise.save();
workout.sets.add(set); // Prepare to link set to workout
}
}
await workout.sets.save();
await workout.exercises.save();
});
}
@Collection(inheritance: false)
class Workout extends Equatable {
final String id;
Id get isarId => Crypto.fastHash(id);
IsarLink<WorkoutTemplate> template = IsarLink<WorkoutTemplate>();
@override
@ignore
List<Object?> get props => [
id,
performedOn,
durationSeconds,
sets,
workoutExercises,
template,
];
@Index()
DateTime performedOn;
int durationSeconds = 0;
Workout({
required this.id,
required this.performedOn,
List<Exercise>? workoutExercises, // Allow passing exercises to constructor
}) : workoutExercises =
workoutExercises ?? List<Exercise>.empty(growable: true);
final sets = IsarLinks<WorkoutSet>();
///used only for finding workout by exercises used in the workout e.g. for workout history
final exercises = IsarLinks<Exercise>();
@ignore
final List<Exercise> workoutExercises;
@ignore
get totalWeightLifted {
return sets.fold<double>(
0, (previousValue, element) => previousValue + (element.weight ?? 0));
}
void prepareForSaving() {
template.value?.exercises = workoutExercises;
}
Workout clone() {
var templateClone = IsarLink<WorkoutTemplate>();
if (template.value != null) {
templateClone.value = template.value; // Adjust according to your needs
}
List<Exercise> exercisesClone =
workoutExercises.map((e) => e.clone()).toList();
return Workout(
id: id,
performedOn: DateTime.fromMillisecondsSinceEpoch(
performedOn.millisecondsSinceEpoch),
workoutExercises: exercisesClone,
)
..durationSeconds = durationSeconds
..template = templateClone;
}
addExercises(List<Exercise> exercises) async {
for (var exercise in exercises) {
var newExercise = exercise.clone();
newExercise.sets.add(WorkoutSet(performedAs: 0, exercise: exercise));
workoutExercises.add(newExercise);
}
}
addSet(int exerciseIndex) async {
workoutExercises[exerciseIndex].sets.add(WorkoutSet(
performedAs: workoutExercises[exerciseIndex].sets.length,
exercise: workoutExercises[exerciseIndex]));
}
removeSet(int exerciseIndex, int setIndex) {
workoutExercises[exerciseIndex].sets.removeAt(setIndex);
for (final (index, set) in workoutExercises[exerciseIndex].sets.indexed) {
set.performedAs = index;
}
}
Exercise? getExercise(int index) {
var id = template.value?.exerciseIds.elementAtOrNull(index);
if (id == null) {
return null;
}
return Database.isar!.exercises
.where()
.idEqualTo(int.parse(id))
.findFirstSync();
}
factory Workout.empty() {
return Workout(
id: Uuid().v4(),
performedOn: DateTime.now(),
);
}
}
@Collection(inheritance: false)
class WorkoutSet extends Equatable {
WorkoutSet({
this.performedOn,
this.weight,
this.reps,
this.type = SetType.work,
required this.performedAs,
this.completed = false,
this.note,
this.isChecked = false,
this.rpe,
this.restTimer,
this.autoRestTimer,
Exercise? exercise,
}) : uuid = Uuid().v4() {
this.exercise.value = exercise;
}
@override
@ignore
List<Object?> get props => [
performedOn,
weight,
reps,
type,
performedAs,
completed,
note,
isChecked,
];
WorkoutSet clone() {
return WorkoutSet(
performedOn:
performedOn, // DateTime is immutable in Dart, so direct assignment is okay
weight: weight,
reps: reps,
type: type, // Enums are immutable
performedAs: performedAs,
// Add the rest of the properties following the same pattern
rpe: rpe,
restTimer: restTimer,
autoRestTimer: autoRestTimer,
completed: completed,
note: note,
isChecked: isChecked,
exercise: exercise.value,
)
..id = id
// ..exercise = exercise
..uuid = uuid;
}
// LINK PR
Id id = Isar.autoIncrement;
String uuid;
IsarLink<Exercise> exercise = IsarLink<Exercise>();
@enumerated
SetType type;
int? weight;
int? reps;
int? rpe; // e.g. 8.5 = 85
int? restTimer; // in seconds e.g. 300 = 5 Minutes
int? autoRestTimer;
bool completed = false;
String? note;
bool isChecked = false;
DateTime? performedOn;
int performedAs; // represents, when - during a number of sets of the same exercise - this set was performed
}
enum SetType {
work,
warmup,
dropSet,
failure,
}
extension Utils on SetType {
Color color(BuildContext context) {
switch (this) {
case SetType.work:
return Theme.of(context).colorScheme.secondaryContainer;
case SetType.warmup:
return Colors.yellow.shade200;
case SetType.dropSet:
return Colors.purple.shade200;
case SetType.failure:
return Colors.redAccent.shade100;
default:
return Theme.of(context).colorScheme.secondaryContainer;
}
}
Color textColor(BuildContext context) {
switch (this) {
case SetType.work:
return Theme.of(context).colorScheme.onSurface;
case SetType.warmup:
return Colors.yellow.shade800;
case SetType.dropSet:
return Colors.purple.shade800;
case SetType.failure:
return Colors.redAccent.shade700;
default:
return Theme.of(context).colorScheme.secondaryContainer;
}
}
}
extension Formatting on SetType {
String format() {
return name.toUpperCase();
}
}
enum BarType {
olympic,
short,
ez,
hex,
none,
}
enum UnitType {
metric,
imperial,
}
I have tried putting the workout again after saving, but this doesn't change anything.
Tried a lot of variations, none of which worked. Ended up simply storing the ID of the desired Exercise and implementing a getter for it instead of using a Link.