Skip to content

Commit bb82e4b

Browse files
committed
Add a small lookup table for exercises
This also consolidates some search functions to use getByIdOrNull
1 parent 9122cc1 commit bb82e4b

10 files changed

Lines changed: 19 additions & 21 deletions

lib/providers/exercises_notifier.dart

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
*/
1818

19-
import 'package:collection/collection.dart';
2019
import 'package:logging/logging.dart';
2120
import 'package:riverpod_annotation/riverpod_annotation.dart';
2221
import 'package:wger/helpers/consts.dart';
@@ -35,13 +34,18 @@ part 'exercises_notifier.g.dart';
3534
class ExerciseState {
3635
final List<Exercise> exercises;
3736

38-
const ExerciseState(this.exercises);
37+
/// Id index so lookups don't scan the full catalogue.
38+
final Map<int, Exercise> _byId;
39+
40+
ExerciseState(List<Exercise> exercises)
41+
: exercises = exercises,
42+
_byId = {for (final e in exercises) e.id: e};
3943

4044
/// Returns the exercise with the given [id], throws if no match
41-
Exercise getById(int id) => exercises.firstWhere((e) => e.id == id);
45+
Exercise getById(int id) => _byId[id] ?? (throw StateError('No exercise with id $id'));
4246

4347
/// Returns the exercise with the given [id], or null if no match
44-
Exercise? getByIdOrNull(int id) => exercises.firstWhereOrNull((e) => e.id == id);
48+
Exercise? getByIdOrNull(int id) => _byId[id];
4549

4650
/// Buckets exercises by their `variationGroup` (skipping those without one)
4751
Map<String, List<Exercise>> getByVariation() {

lib/providers/routines_notifier.dart

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,7 @@ class RoutinesRiverpod extends _$RoutinesRiverpod {
148148
for (final log in session.logs) {
149149
// Fall back gracefully if the referenced exercise hasn't been
150150
// synced yet (rare but possible on a cold start).
151-
final exercise = exerciseState.exercises.firstWhereOrNull(
152-
(e) => e.id == log.exerciseId,
153-
);
151+
final exercise = exerciseState.getByIdOrNull(log.exerciseId);
154152
if (exercise != null) {
155153
log.exerciseObj = exercise;
156154
}
@@ -162,9 +160,7 @@ class RoutinesRiverpod extends _$RoutinesRiverpod {
162160
for (final slot in day.slots) {
163161
for (final entry in slot.entries) {
164162
if (exerciseState != null) {
165-
final exercise = exerciseState.exercises.firstWhereOrNull(
166-
(e) => e.id == entry.exerciseId,
167-
);
163+
final exercise = exerciseState.getByIdOrNull(entry.exerciseId);
168164
if (exercise != null) {
169165
entry.exerciseObj = exercise;
170166
}
@@ -185,9 +181,7 @@ class RoutinesRiverpod extends _$RoutinesRiverpod {
185181
for (final slot in entry.slots) {
186182
for (final setConfig in slot.setConfigs) {
187183
if (exerciseState != null) {
188-
final exercise = exerciseState.exercises.firstWhereOrNull(
189-
(e) => e.id == setConfig.exerciseId,
190-
);
184+
final exercise = exerciseState.getByIdOrNull(setConfig.exerciseId);
191185
if (exercise != null) {
192186
setConfig.exercise = exercise;
193187
}

lib/widgets/add_exercise/steps/step_2_variations.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class Step2Variations extends ConsumerWidget {
1313
Widget build(BuildContext context, WidgetRef ref) {
1414
// Reactive: rebuilds when the exercise catalogue changes. Falls back to
1515
// an empty state while the stream hasn't emitted yet.
16-
final exerciseState = ref.watch(exercisesProvider).value ?? const ExerciseState([]);
16+
final exerciseState = ref.watch(exercisesProvider).value ?? ExerciseState(const []);
1717
final byVariation = exerciseState.getByVariation();
1818

1919
return Form(

lib/widgets/exercises/detail/variations_section.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class VariationsSection extends ConsumerWidget {
3434
return const SizedBox.shrink();
3535
}
3636

37-
final exerciseState = ref.watch(exercisesProvider).value ?? const ExerciseState([]);
37+
final exerciseState = ref.watch(exercisesProvider).value ?? ExerciseState(const []);
3838
final variations = exerciseState.findByVariationGroup(
3939
exercise.variationGroup,
4040
exerciseIdToExclude: exercise.id,

test/exercises/contribute_exercise_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ void main() {
5454
mockExerciseRepository = MockExerciseRepository();
5555
when(
5656
mockExerciseRepository.watchAllDrift(),
57-
).thenAnswer((_) => Stream.value(const ExerciseState(<Exercise>[])));
57+
).thenAnswer((_) => Stream.value(ExerciseState(const <Exercise>[])));
5858
});
5959

6060
/// Stubs the account fetch so the contribution gate sees a (non-)trustworthy

test/exercises/exercises_detail_widget_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ void main() {
4444
mockExerciseRepo = MockExerciseRepository();
4545
when(
4646
mockExerciseRepo.watchAllDrift(),
47-
).thenAnswer((_) => Stream.value(const ExerciseState(<Exercise>[])));
47+
).thenAnswer((_) => Stream.value(ExerciseState(const <Exercise>[])));
4848
});
4949

5050
Widget createHomeScreen({locale = 'en'}) {

test/exercises/exercises_notifier_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ void main() {
7373
variationGroup: 'legs',
7474
);
7575
final variationExercises = [benchPress, inclineBench, squat, testCrunches];
76-
const emptyState = ExerciseState([]);
76+
final emptyState = ExerciseState(const []);
7777

7878
test('getById returns the matching exercise', () {
7979
final s = ExerciseState(exercises);

test/routine/helpers/routine_form_test_overrides.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ MockExerciseRepository _emptyExerciseRepoMock() {
5959
final mock = MockExerciseRepository();
6060
when(
6161
mock.watchAllDrift(),
62-
).thenAnswer((_) => Stream.value(const ExerciseState(<Exercise>[])));
62+
).thenAnswer((_) => Stream.value(ExerciseState(const <Exercise>[])));
6363
return mock;
6464
}
6565

test/routine/routine_form_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ void main() {
9191
mockExerciseRepo = MockExerciseRepository();
9292
when(
9393
mockExerciseRepo.watchAllDrift(),
94-
).thenAnswer((_) => Stream.value(const ExerciseState(<Exercise>[])));
94+
).thenAnswer((_) => Stream.value(ExerciseState(const <Exercise>[])));
9595
});
9696

9797
tearDown(() {

test/routine/routines_provider_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ void main() {
6060
mockExerciseRepo = MockExerciseRepository();
6161
when(
6262
mockExerciseRepo.watchAllDrift(),
63-
).thenAnswer((_) => Stream.value(const ExerciseState(<Exercise>[])));
63+
).thenAnswer((_) => Stream.value(ExerciseState(const <Exercise>[])));
6464
testDay = Day(
6565
id: 15,
6666
routineId: 101,

0 commit comments

Comments
 (0)