Closed raffaelfoidl closed 2 years ago
In GitLab by @11712616 on Oct 15, 2021, 20:51
Commented on client/lib/widgets/exercises/editing/exercise_create.dart line 8
Should I move this file to the model
folder?
In GitLab by @11712616 on Oct 15, 2021, 20:52
Commented on client/lib/widgets/exercises/editing/create_exercise_form.dart line 169
Should this error message be localized somehow?
In GitLab by @11712616 on Oct 15, 2021, 20:53
requested review from @11775820
In GitLab by @11712616 on Oct 15, 2021, 22:32
added 1 commit
In GitLab by @11775820 on Oct 16, 2021, 15:32
Commented on server/src/main/resources/application-setup.yml line 45
Superfluous space before Place
.
In GitLab by @11775820 on Oct 16, 2021, 15:32
Commented on client/lib/widgets/exercises/exercises_screen.dart line 100
As I cannot comment on images, I'm writing here.
Did you check if the license of dumbbell.png
allows use for non-commercial as well as commercial projects without attribution or license/copyright notice? If not, please do so or find a dumbbell image that satisfies these criteria.
Furthermore, can't we now remove the flutter_logo.png
file as well as the other resolutions in the directories 2.0x
and 3.0x
?
In GitLab by @11775820 on Oct 16, 2021, 15:32
Commented on client/lib/extensions/enum_extensions.dart line 79
Couldn't the whole method body of toUpperCaseString
replaced to something like this?
String toUpperCaseString() {
return toUiString().toUpperCase();
}
This way you do not have to change string literals in two places and overall simply avoid duplication. Overhead induced by two method calls should be negligible.
In GitLab by @11775820 on Oct 16, 2021, 15:32
Commented on client/lib/localization/app_en.arb line 73
Save
instead of save
?
In GitLab by @11775820 on Oct 16, 2021, 15:32
Commented on client/lib/localization/app_en.arb line 113
logging types
instead of loggin types
?
In GitLab by @11775820 on Oct 16, 2021, 15:32
Commented on client/lib/models/training_programs/exercise_set.dart line 3
This class stems from myself when we didn't have a domain model yet. Which concept does this class correspond to in our domain model? Is my assumption that this class is not yet "really" used because it will be needed only for training programs correct?
In GitLab by @11775820 on Oct 16, 2021, 15:32
Commented on client/lib/providers/exercise_provider.dart line 31
_auth
is not unused anymore, the linter suppression can and should be removed
In GitLab by @11775820 on Oct 16, 2021, 15:32
Commented on client/lib/providers/exercise_provider.dart line 39
Why make it nullable? In my opinion, the consumer should not have the responsibility of checking whether the list is null
or empty. This is unnecessary downstream complexity. We can simply return an empty list otherwise.
Apart from that, this nullable list does not make much sense because the type of _exercises
is Map<MuscleGroup, List<Exercise>>
. Therefore, the list of exercises can never be null, making the ?
redundant. Please remove it.
Even when setting _exercises
in fetchExercisesByMuscleGroup
, what you are getting is a List<Exercise>
, so it cannot be null
either. The reason you still have to return a Future<List<Exercise>?>
is a mistake that is explained in the comment below (or above, whatever GitLab is doing).
In GitLab by @11775820 on Oct 16, 2021, 15:32
Commented on client/lib/providers/exercise_provider.dart line 53
Since freaking GitLab seems to be failing all the time with comments that are longer than 3 characters and is not able to display a diff...this comment addresses fetchExercisesByMuscleGroup
in exercise_provider
.
This method is written a bit confusingly. First of all, the fact that you are returning a List<Exercise>?
wrapped in a Future
instead of List<Exercise>
wrapped in a Future
stems from the fact that you are never returning anything. In dart, if no value is explicitly returned using the return
statement, null
is returned.
Since you do not return anything in the nested future, i.e. within the then(...)
, you always return null
. You would need to add return fetchedExercises;
below the log statement Received ...
in order for the signature Future<List<Exercise>> fetchExercisesByMuscleGroup(...)
to be valid and correct.
Nevertheless, another problem with this method is the usage auf .then
and having a non-void
return type at all. fetchExercisesByMuscleGroup
is a method that, on the UI side, is used with a FutureBuilder
and a Consumer
(see exercises_by_muscle_group_screen.dart
). That means: Calling the method "starts" the future, FutureBuilder
wraps a widget around its value and the nested Consumer<ExerciseProvider
allows to access the provider state (i.e. the data fetched by the future). This usage is important because it avoids making separate HTTP requests (because the provider invokes the service) for each widget tree build lifecycle.
In other words, the function's return value is never used (as you can also see by the way it is invoked: await Provider.of<ExerciseProvider>(context, listen: false).fetchExercisesByMuscleGroup(group);
in exercises_by_muscle_group_screen.dart
).
Above paragraphs explained why the method does not need a return value. The following sentences aim to elaborate when the use Future(...).then(..)
syntax vs. async/await
.
The second "problem" I mentioned above was the usage of then
. In this case, it is not necessarily wrong, but just no leveraging much more easily readable syntax - which would make the method body more concise and better comprehensible.
If you are in a method that is already declared async
(or is "allowed to be declared async
), then I highly encourage the use of async/await
for readability and maintenance reasons. It is completely equivalent to then
, the compiler/preprocessor simply allows us to have it look a bit nicer.
More generally, please always use the async/await
syntax if possible. There is only one (but "hard") exception to this: If you are in a synchronous
(i.e. non-async
) method that must stay synchronous (e.g. a UI build method, especially if it uses a BuildContext
object; corresponding linter rule). I have needed to do this, for example, in the _deleteTrainingProgram
method where I access the build context across an async gap. The method needs to be synchronous, but is also waiting for the completion of a Future
. Therefore, I was "forced" to use the then
syntax.
However, there is no such contra-indication in this method (fetchExercisesByMuscleGroup
), which is a "plain" (i.e. decoupled from UI logic) async
method. Therefore, I strongly recommend rewriting it to async/await
. This also makes the return type more clear (async
methods automatically wrap the returned value in a Future
) - a "mistake" as the one described above (with the accidental always null
return value) would be spotted more easily. My approach to rewrite it would be like this:
Future<void> fetchExercisesByMuscleGroup(final MuscleGroup group) async {
_logger.i('Fetching exercises that affect muscle group "${group.toUiString()}"');
final fetchedExercises = await _exerciseService.getExercisesByMuscleGroup(group, await _auth?.getToken());
_exercises[group] = fetchedExercises;
notifyListeners();
_logger.i('Received ${fetchedExercises.length} exercises');
}
Another note: In you current proposal, you call notifyListeners()
before you change the internal state (_exercises[group] = fetchedExercises
). We should aspire to avoid this because that instructs clients to redraw their widget screen based on not-yet-updated provider data. So, please, always change the internal state before notifying potential listeners.
In GitLab by @11712616 on Oct 16, 2021, 17:33
approved this merge request
In GitLab by @11712616 on Oct 16, 2021, 17:33
unapproved this merge request
In GitLab by @11775820 on Oct 16, 2021, 21:05
Commented on client/lib/services/exercise_service.dart line 21
This and other occurrences of Uri
constructions as well as query parameter appending will be adjusted after merging #49.
In GitLab by @11775820 on Oct 16, 2021, 21:05
Commented on client/lib/services/exercise_service.dart line 26
Suggestion:
final responseList = json.decode(response.body) as List<dynamic>;
return responseList.map((final dynamic e) => Exercise.fromJson(e as Map<String, dynamic>)).toList();
toList()
is faster and, because generic types are inferred easier to read imo.
see also List.from is slower than toList on github for reasons.
The alternative presented above is, in my opinion easier to read. The performance is possibly not very relevant for us, but maybe a factor in favor of this one. In any case, please rename responseMap
to responseList
.
In GitLab by @11775820 on Oct 16, 2021, 21:05
Commented on client/lib/widgets/exercises/details/exercise_detail_screen.dart line 17
VoidCallback
is just a type alias for void Function()
. As far as I know, there are unfortunately not many more useful callback aliases for different types of Function
s we could use.
If you would like to stay with VoidCallback
here, then please also replace the occurrences of void Function()
in training_log_screen and
app_drawerby
VoidCallback` to stay consistent.
In GitLab by @11775820 on Oct 16, 2021, 21:05
Commented on client/lib/widgets/exercises/editing/create_exercise_form.dart line 13
Please use auto-format (CTRL+ALT+L
) which also tidies up the imports. Maybe dartfmt
doesn't complain in the CI build, but imports like this are still inconsistent with the rest and auto-format would fix it.
In GitLab by @11775820 on Oct 16, 2021, 21:05
Commented on client/lib/widgets/exercises/editing/create_exercise_form.dart line 53
The way you are using bangs (!
) (more officially, the null-check operator) is, unfortunately, not very beneficial for null-safety. If you have a look hat how I have been using the null-check operator, you will notice that I only employ it when I have an assertion that the respective variable is not null. For example, I have many constructs like var1 == null ? doSomethingThatCanHandleNull(var1) : doTheThingThatDoesNotCareAboutNull(var1!)
.
These bangs should only be used when you can absolutely be certain that the variable is NOT null (i.e. via null-checks). For local variables, a preceding (if (x != null)
) is sufficient due to dart's type promotion. For global variables, however, this does not work (which is why I often declare methods taking non-nullable parameters which are just banged versions of the nullable global variable -- so that I do not have the complexity of handling null in those methods).
What you are doing here, i.e. putting bangs after the variables without having any assertion (from yourself, the docs, ...), is essentially turning off darts sound null-safety. This completely removes all the advantages we get with compiler-guaranteed null-safety and re-introduces nullpointer exception during runtime. You can compare it to using dynamic
to turn off static type checks by the compiler. So, please don't do it like this. The same goes for late
variables - bangs and late
are not to be used as ways to turn off the annoying compiler errors; they must not be used unless we have asserted them not to bear the risk of runtime NPEs.
The documentation of onSaved
tells you in which cases value
could be null. You can circumvent this with the initialValue
attribute of TextFormField
(read docs please what's applicable, initialValue
is ok in this case since you dont have it hooked up to a Controller
).
(non-) Type promotion: https://dart.dev/tools/non-promotion-reasons
sound Null-Safety in general: https://dart.dev/null-safety/understanding-null-safety
In GitLab by @11775820 on Oct 16, 2021, 21:05
Commented on client/lib/widgets/exercises/editing/create_exercise_form.dart line 54
As per the style guide in the wiki, please add ,
after the last list element and after the closing bracket ]
and then re-auto-format.
In GitLab by @11775820 on Oct 16, 2021, 21:05
Commented on client/lib/widgets/exercises/editing/create_exercise_form.dart line 61
,],),)
and then auto-format. Furthemore, insert a comma ,
after the final argument because decoration constructs a new widget (i.e. .... _formInput.description = value,)
, then auto-format. The same goes for various places in the files touched by this PR, I will not comment on every instance. Please try to stick to the style guide described in the wiki.
In GitLab by @11775820 on Oct 16, 2021, 21:05
Commented on client/lib/widgets/exercises/editing/create_exercise_form.dart line 67
Please use more descriptive lambda variable names than v
.
In GitLab by @11775820 on Oct 16, 2021, 21:06
Commented on client/lib/widgets/exercises/editing/create_exercise_form.dart line 70
here it should be more or less ok I think because all muscle groups are in the map. one needs to re-check if muscle groups are added, though
In GitLab by @11775820 on Oct 16, 2021, 21:06
Commented on client/lib/widgets/exercises/editing/create_exercise_form.dart line 86
same as with muscle groups, I think this bang is ok. but please choose a bettername than v
In GitLab by @11775820 on Oct 16, 2021, 21:06
Commented on client/lib/widgets/exercises/editing/create_exercise_form.dart line 117
I think a full-width button would look a bit nicer:
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => _submitForm(context),
child: Text(uiStrings.createExerciseScreen_body_save_buttonText),
),
)
In GitLab by @11775820 on Oct 16, 2021, 21:06
Commented on client/lib/widgets/exercises/editing/create_exercise_form.dart line 152
See comments regarding null safety. See login_card.submit
for an example on how to assert that _formKey.currentState
is not null. (make the if
disjunctive with _formKey.currentState == null
being the first operand - due to short-circuit semantics, the bang in the second operator and within the if
body will then be ok). However, I'd prefer inverted if
to reduce nesting:
if (_formkey.currentState == null || !_formKey.currentState!.validater()) {
return;
}
...save(); ....
In GitLab by @11775820 on Oct 16, 2021, 21:06
Commented on client/lib/widgets/exercises/editing/create_exercise_form.dart line 201
NOTE (2):
Note number 2 is on top because it should have occurred to me right at the beginning and not only after making screenshots and writing everything out below...
I don't quite understand why you are doing the navigation like this at all. I mean, why are you passing a navigation callback to the detail screen with the newly introduced back button just in order to go back to the "main exercise screen" (by muscle group) after going back from the newly created exercise?
From what I have tested, you would achieve exactly the same thing if you replaced Navigator.of(context).pushNamed(ExerciseDetailScreen.routeName, ...)
by pushNamedReplacement
. Then, the create_exercise_form
is popped off the screen stack (making the "by muscle group" screen the top screen again), after whcih the new exercise detail screen is pushed onto the stack. So when you go back, you are back at the "by muscle group screen" - in my eyes exactly the same behaviour as we have here right now, just without any additional changes. So, in my opinion, you don't need the changes in the routing and the new backButtonNavigation
field as well as leading: BackButton()
since the default behaviour should suffice (and also no list in the arguments
when navigating to the exercise detail screen).
Am I missing something? Ff my comment here should be correct, please also read the notes below.
The navigation with the backButtonNavigation
only works by coincidence here. You are asking for a VoidCallback
, which would have the form () => Navigator.of(context).pushReplacementNamed(ExerciseScreen.routeName)
. What you are passing, however, is the return value of pushReplacementNamed
, which is of type Future
.
In app_routing
, you then try to convert it with castOrNull
, which will always be null
because a Future
cannot be cast to a void Function()
. Here a picture providing evidence:
So why does the navigation still work as intended? I honestly don't want to dig too deep, but I think it's got something to do with the fact that the pushReplacement
returns Future<void
in this case which is almost the same as void
. However, as the screenshot shows, there is definitely something fishy here.
The correct way to invoke would be like this (please also note the formatting):
provider.postUserExercise(exerciseCreate).then(
(final value) => Navigator.of(context).pushNamed(
ExerciseDetailScreen.routeName,
arguments: [
value,
() => Navigator.of(context).pushReplacementNamed(ExercisesScreen.routeName),
],
),
onError: (final Object error) => _showError(error.toString()));
Which works as intended with the routing:
NOTE (1)
However, I would also like to suggest another way of doing it. Since you already called it backButtonNavigation
, I would like to reduce the complexity of routing by simply passing a route to nevigate to upon pressing the back button instead of the callback. I.e.
ExerciseDetailScreen:
final String? _backButtonTargetRoute
...
BackButton(onPressed: _backButtonTargetRoute != null ? () => Navigator.of(context).pushReplacement(_backButtonTargetRoute!) : null
app_routing:
return ExerciseDetailScreen(args?[0].castOrNull<Exercise>(), args?[1].castOrNull<String?>());
create_exercise_form:
arguments: [value, ExercisesScreen.routeName]
In my opinion, this approach would decrease the overall complexity since the nasty stuff is hidden in exercise_detail_screen
. If you don't like this, in any case, you need to resolve the error in the routing parameter (add () =>
in order to actually pass a callback).
In GitLab by @11775820 on Oct 16, 2021, 21:06
Commented on client/lib/widgets/exercises/editing/create_exercise_form.dart line 218
that's how I like my !
s :)
In GitLab by @11775820 on Oct 16, 2021, 21:06
Commented on client/lib/widgets/exercises/editing/create_exercise_form.dart line 169
Yes, please. In general, I like dynamic error messages like this. However, that doesn't really translate well to other languages. Since the number of actual error messages (the possible combinations) that can be produced by this method is rather limited, I'd propose storing exactly those error messages in the .arb
file and - based on which condition is actually present - return the correct one.
In GitLab by @11775820 on Oct 16, 2021, 21:07
Commented on client/lib/widgets/exercises/editing/edit_exercise_screen.dart line 36
That's an example of what I was taking about before in create_exercise_form
regarding (my interpretation of) proper utiliztation of dart's nullable types.
We know that _exercise
is not null when we are building the "real" exercise screen in using _buildEditScreen
. So why should we not alleviate ourselves from the hassle of supporting or handling the Exercise?
type in that method? What you are doing here is saying Hey, this a method that can handle null values gracefully, but then you just turn off the compiler checks with the bang !
, effectively making the code nun-null-afe and inviting runtime NPEs because the method does not handle null gracefully.
Therefore, please change the _buildEditScreen
as follows (note the changed formatting, I inserted a ,
after the child Text
according to the style guide - but it was already incorrectly formatted by myself, my bad):
Widget _buildEditScreen(final Exercise exercise) {
return Center(
child: Text('Editing Exercise "${exercise.name}"'),
);
}
Then, we are allowed to change this line to _exercise == null ? _buildCreateScreen() : _buildEditScreen(_exercise!)
because we know __exericse_exercise
to be non-null.
In GitLab by @11775820 on Oct 16, 2021, 21:07
Commented on client/lib/widgets/exercises/editing/exercise_create.dart line 8
Yes, absolutely, please.
In GitLab by @11775820 on Oct 16, 2021, 21:07
Commented on client/lib/widgets/exercises/exercises_by_muscle_group_screen.dart line 56
If you have removed this UI element entirely (and not moved to somewhere, I couldn't find it), then please also remove the localized string exerciseByMuscleGroupScreen_createNew_tooltip
in the arb
file.
In GitLab by @11775820 on Oct 16, 2021, 21:07
Commented on client/lib/widgets/exercises/exercises_by_muscle_group_screen.dart line 137
As already elaborated above, I do not think this change to a list argument is required.
In GitLab by @11775820 on Oct 16, 2021, 21:07
Commented on client/lib/widgets/app_routing.dart line 55
As already explained somewhere above, I don't think this change is required.
In GitLab by @11775820 on Oct 16, 2021, 21:07
Commented on server/src/main/java/com/witness/server/dto/UserExerciseDto.java line 10
Maybe remove this blank line? ExerciseDto
doesn't have it either.
In GitLab by @11775820 on Oct 16, 2021, 21:08
Commented on server/src/main/java/com/witness/server/entity/Exercise.java line 64
Superfluous blank line?
In GitLab by @11775820 on Oct 16, 2021, 21:08
Commented on server/src/main/java/com/witness/server/entity/Exercise.java line 72
var
In GitLab by @11775820 on Oct 16, 2021, 21:08
Commented on server/src/main/java/com/witness/server/entity/User.java line 89
var
And why remove height
from equals
condition?
In GitLab by @11775820 on Oct 16, 2021, 21:08
Commented on server/src/main/java/com/witness/server/entity/UserExercise.java line 29
Is there a reason why the @Builder
annotation is not placed at the class level? And what's the reasoning behind the specified builder method name, i.e. why does the name generated by Lombok by default not suffice? Does MapStruct need it like this?
In GitLab by @11775820 on Oct 16, 2021, 21:08
Commented on server/src/main/java/com/witness/server/enumeration/LoggingType.java line 3
For consistency reasons, please modify Sex
such that each lement is on a separate line.
In GitLab by @11775820 on Oct 16, 2021, 21:08
Commented on server/src/main/java/com/witness/server/exception/InvalidRequestException.java line 9
This empty line is not present in the other derived Exception
classes. Please either everywhere or nowhere.
In GitLab by @11775820 on Oct 16, 2021, 21:08
Commented on server/src/main/java/com/witness/server/mapper/ExerciseMapper.java line 8
ArrayList
is an unused import, please remove
In GitLab by @11775820 on Oct 16, 2021, 21:08
Commented on server/src/main/java/com/witness/server/mapper/ExerciseMapper.java line 18
Why are manually specifying target and source for properties that are mapped automatically? Doesn't that kinda defeat the purpose of the mapper? Now we have boilerplate @Mapping
annotations instead of boilerplate A.setX(B.getX)
.
Those properties will be mapped anyways since they have the same names. I highly suggest that we only explicitly specify mappings that cannot be automatically inferred (e.g. when DTO and entity property have different names). That way we have less to read and less mental load when comprehending a mapper definition.
In GitLab by @11775820 on Oct 16, 2021, 21:09
Commented on server/src/main/java/com/witness/server/repository/ExerciseRepository.java line 12
I have some thougts here.
SELECT
, ...) is beneficial.+
here is that it is not a constant expression anymore, therefore making the Spring Data/JPL integration basically useless (as soon as it cannot match variable names anymore etc.). These problems are all solved with text blocks.At a later point, we will have to think about whether the queries could be formulated more concise (easier to understand) or if we want to design it differently. For example, one could debate whether existsByName
could simply return the count (a long
) and the service checks count > 0
. This would get rid of proprietary or hibernate-specific CASE WHEN END
statements and, I claim, a bit easier to understand. For this MR right now, it's also okay as you have proposed.
As we have rather complex queries here, I would love to see repository tests. I want to emphasize that I don't expect repository tests for "standard" operations or query methods generated by Spring (e.g. findByFirebaseIdEquals
in UserRepository
). However, what we have here are rather complicated queries. Especially considering we might change them in the future (making them easier etc.), I would like to have repository tests verifying their correct behaviour - tests that assert that correct elements are part of the result set and other elementes are not part of the result set. It doesn't have to be too much effort, the base cases should be covered. In the expander below you find a sketch of how I would like those tests to look like. Te @DataJpaTest
annotation makes that the unit tests are executed on an in-memory database (which is desired, in the best case, only integration tests should rellay create a database).
In GitLab by @11775820 on Oct 16, 2021, 21:09
Commented on server/src/main/java/com/witness/server/repository/UserExerciseRepository.java line 8
That's an example of a repository method that does not need a test. However, please remove the blank line.
In GitLab by @11775820 on Oct 16, 2021, 21:16
Commented on client/lib/widgets/exercises/exercises_by_muscle_group_screen.dart line 67
Nested Text
element => comma after closing parenthesis of Text
and then re-format to make it look like this (rule 4):
return Center(
child: Text('Could not fetch exercises: ' + snapshot.error.toString()),
);
In GitLab by @11775820 on Oct 16, 2021, 21:16
Commented on client/lib/widgets/exercises/exercises_screen.dart line 24
Now it looks like this (which is definitely not nice):
Please wrap the Listview.builder
with an Expanded()
.
I'd even prefer having the Listview.builder
wrapped in a ScrollBar
, which in turn is wrapped in an Expanded
:
return Expanded(
child: Scrollbar(
isAlwaysShown: true,
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: MuscleGroup.values.length,
itemBuilder: (final _, final index) {
final muscleGroup = MuscleGroup.values[index];
return Column(
children: [
_ExerciseOverviewItem(muscleGroup),
const Divider(),
],
);
},
),
),
);
This yields a scrollbar (if resolution/item count warrants it):
And no scrollbar if resolution/item doesn't warrant it, but the "waves" are at the bottom (i.e. listview fills vertical space):
In GitLab by @11775820 on Oct 16, 2021, 21:23
Commented on server/src/main/java/com/witness/server/service/impl/ExerciseServiceImpl.java line 28
Would you like to keep ExerciseRepository exerciseRepository
on the previous line? There is enough space and it would be more consistent with other classes.
In GitLab by @11775820 on Oct 16, 2021, 21:23
Commented on server/src/main/java/com/witness/server/service/impl/ExerciseServiceImpl.java line 51
var
In GitLab by @11712616 on Oct 15, 2021, 20:50
Merges 9-create-new-exercise -> develop
Closes #9