PROJECT: FitBiz

Overview

FitBiz is a fitness business management application, specially made for fitness coaches to manage their clients. It is primarily a desktop application where the user interacts via the Command Line Interface (CLI), and views data via the Graphical User Interface (GUI). This project is written in Java 11, packaged using Gradle, and uses JavaFX for the GUI.

Summary of Contributions

  • Major enhancement: developed the graph feature, which allows users to conveniently plot and display the progress made by a client for a certain exercise over time. Users can immediately see if the client has improved for that exercise over the months.

    • What it does: allows users to plot and display the graph of the specified exercise and specified y-axis from the stipulated start to end dates. Users have a choice of y-axis so that they can focus on one of the different attributes of an exercise, like the reps done or the weights lifted.

    • Justification: visualisation is always better than looking through a long list of exercises to check if a client has improved for an exercise. It is also much faster and easier to look at a graph to check for progress. The flexibility in axis type and dates also allows users to customise their graph to their needs.

    • Highlights: developed a Graph model that has an axis type, exercise name, start and end dates. Checks are also in place to make sure that there are data points to be plotted, else there will be an error message. Sanity checks like the check that the start date cannot be later then the end date are also implemented.

  • Major enhancement: developed the personal best feature, which allows users to view the personal best records of their exercises immediately after keying in an exercise session.

    • What it does: allows users to efficiently and conveniently access their clients' personal best while updating the personal best table whenever changes to the exercise table is made.

    • Justification: instead of having to manually look through the entire exercise table to find a client’s personal best, the personal best will be immediately displayed when the user views a certain client. Changes made to the client’s exercise list will also be updated and displayed immediately.

    • Highlights: this feature was made to provide convenience to the user so that they do not have to go through extra commands or look through the entire exercise list to find their client’s personal best. This feature is also related to the graph feature as users will be able to see the progress of how a client achieves a personal best from the graph.

  • Minor enhancement: hooked the view-c command to FitBiz and added relevant tests to FitBiz.

  • Minor enhancement: added the height attribute to the Client class.

  • Overall contributions: 1.5K+ LOC | 90+ commits | 18 PRs merged | 50+ PRs reviewed

  • Other contributions:

    • Project management:

      • Managed labels and issues on the GitHub issue tracker

    • Enhancements to existing/teammates' features:

      • Fixed command parameters and error messages of general features after the practical dry run #204

      • Fixed default value of an attribute a team member created for consistency #88

    • Documentation:

      • Added the Personal Best Implementation, Graph Implementation, Use Cases and Manual Testing documentation to the Developer Guide #16, #203, #247, #276, #284

      • Edited Quick Start and added the View Command, Delete Client Command and Graph Command to the User Guide #75, #150, #270

    • Summary:

Contributions to the User Guide

Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users.

Delete a client: delete-c --- Li Zi Ying

You find that your client list contains a client who is no longer working with you and you would like to remove his details from your client list. delete-c allows you to delete the client at the specified index from your list of clients.

Format: delete-c INDEX

Parameters

This section acts as a summary of the important things to note when using delete-c including their parameters.

Parameters Important points to note

INDEX

INDEX refers to the index number shown in the displayed client list

INDEX must be a positive integer (ie. 1, 2, 3, …​)

Examples

Let’s say that you want to delete the client named "Alex Yeoh" in client list. You can simply use the delete-c command as shown.

  1. Type delete-c 1 into the Command Box and press Enter to execute it.

    ug delete 1
  2. Result Box will display the message of the details of the deleted client. You can now see that the Client List no longer contains the deleted client.

    ug delete 2

Common errors/problems

You might face some errors or difficulties when you use delete-c. In this section, you will be able to understand these errors and resolve them.

No index specified

If you type in delete-c without specifying the INDEX, no client will be deleted and an error message will be shown.

ug delete 3

This happens because there is no INDEX specified. To correct this, you have to enter a valid INDEX after delete-c.

Index out of range

If you key in an INDEX that is out of range, that is if there are only 4 clients in the list but you try to key in delete-c 5, no client will be deleted and an error message will be shown.

ug delete 4

To correct this, you have to enter a valid INDEX that is not more than the number of clients in the client list.

View a client profile: view-c --- Li Zi Ying

You might want to view the complete information of a certain client in your client list. This complete information includes the client’s full details, their exercise table and personal best table. view-c INDEX shows all available information of the client at the specified index.

Format: view-c INDEX

Parameters

This section acts as a summary of the important things to note when using view-c including their parameters.

Parameters Important points to note

INDEX

INDEX refers to the index number shown in the displayed client list

INDEX must be a positive integer (ie. 1, 2, 3, …​)

Examples

Let’s say that you want to view the details of the client named "Alex Yeoh" in client list. You can simply use the view-c command as shown.

  1. Type view-c 1 into the Command Box and press Enter to execute it.

    ug view 1
  2. Result Box will display the message telling you the client currently in view. You can now see that the Client View is now populated with the client’s details, the exercise table and the personal best table.

    ug view 2

Common errors/problems

You might face some errors or difficulties when you use view-c. In this section, you will be able to understand these errors and resolve them.

No index specified

If you type in view-c without specifying the INDEX, no client will be viewed and an error message will be shown.

ug view 3

This happens because there is no INDEX specified. To correct this, you have to enter a valid INDEX after view-c.

Index out of range

If you key in an INDEX that is out of range, that is if there are only 5 clients in the list but you try to key in view-c 10, no client will be viewed and an error message will be shown.

ug view 4

To correct this, you have to enter a valid INDEX that is not more than the number of clients in the client list.

Display visualisations of training progress: graph --- Li Zi Ying

graph allows you to see a graphical visualisation of a client’s exercise progress within a specified timeframe, so that you can easily track your client’s progress and improvement.

Format: n/EXERCISE_NAME a/Y_AXIS sd/START_DATE ed/END_DATE

Parameters

This section acts as a summary of important things to note when using graph including their parameters.

Parameters Important points to note

n/EXERCISE_NAME

Substitute EXERCISE_NAME with the name of the exercise.

n/EXERCISE_NAME is compulsory and you have to specify it.

n/EXERCISE_NAME is case sensitive.
e.g. An exercise pushup will be considered to be different from the exercise Pushup.

a/Y_AXIS

Substitute Y_AXIS with the y-axis of your choice.

Y_AXIS is compulsory and you have to specify it.

Y_AXIS must either be reps or weight (case insensitive), allowing you to specify the exercise attribute you wish to focus on
e.g. Reps, weigHt and WEIGHT will be valid inputs for Y_AXIS.

sd/START_DATE

Substitute START_DATE with the start date of your graph.

sd/START_DATE is compulsory and you have to specify it.

START_DATE must be of the form DD-MM-YYYY (ie. 02-07-2020 for 2nd July 2020)

The range of START_DATE is from one year before the current date to the current date (inclusive).

START_DATE cannot be chronologically after END_DATE

ed/END_DATE

Substitute END_DATE with the end date of your graph

ed/END_DATE is compulsory and you have to specify it.

END_DATE must be of the form DD-MM-YYYY (ie. 02-07-2020 for 2nd July 2020)

The range of END_DATE is from one year before the current date to the current date (inclusive).

END_DATE cannot be chronologically before START_DATE

Example

Let’s say that you wish to view the exercise graph of Alex Yeoh for the exercise Sumo Deadlift from the date 01-01-2020 to 13-04-2020 and you want to focus on the weight he lifted during the exercise.

You can use the graph command to view the graph of the exercise following the steps as shown:

  1. First view the client that you want to view the exercise graph of. For information on how to view the client, you can refer to the view-c section. For this example, we will view the graph of Alex Yeoh, who is the first client in the client list. After entering view-c 1, you will see the full detailed information and a table of recorded exercises of client Alex Yeoh.

    ug graph 1
  2. Next, simply type the graph command graph n/Sumo Deadlift a/weight sd/01-01-2020 ed/13-04-2020 into the Command Box.

    ug graph 2
  3. The graph will appear separately in a window.

    ug graph 3
  4. You will see the Result Box informing you of the current graph displayed. Note that any changes made using add-e, edit-e, delete-e will not be reflected in the graph.

    ug graph 4

Common errors/problems

You might face some errors or difficulties when you use graph. In this section, you will be able to understand these errors and resolve them. You will also get a better understanding of the reply from the Result Box when using graph.

No exercise within stipulated timeframe

If you have keyed in the graph command in the correct format but the graph is not appearing, you might want to check the start and end dates in your command. In the example below, the exercise clearly exists in the exercise list, but not within the stipuated timeframe. Therefore the graph will not be displayed.

  1. The exercise Sumo Deadlift clearly exists in the exercise list. However, there are no records of Sumo Deadlifts from 01-01-2020 to 01-02-2020.

    ug graph 5
  2. The graph will not appear and an error message will be shown.

    ug graph 6

To correct this, change the timeframe to one where there is at least one existing record of the specified exercise in the current exercise list.

No exercise for stipulated axis

If you have keyed in the graph command in the correct format but the graph is not appearing, you might want to check the y-axis in your command. In the example below, the exercise clearly exists in the exercise list, but there are no inputs for the stipulated y-axis. Therefore the graph will not be displayed.

  1. The exercise Push Up clearly exists in the given timeframe. However, there are no inputs for weight as seen in the exercise table.

    ug graph 7
  2. The graph will not appear and an error message will be shown.

    ug graph 8

To correct this, change the axis to one where there is at least one non-empty input of the exercise in the current exercise list. You could also choose to view another exercise’s graph instead.

Invalid time frame

If you have keyed in the graph command in the command box but the graph is not appearing, you might want to check the start and end dates. In the example below, the command format looks correct but the start date is later than the end date. This is not allowed and therefore the graph will not be displayed.

  1. The start date 01-04-2020 is chronologically later than the end date 01-02-2020.

    ug graph 9
  2. The graph will not appear and an error message will be shown.

    ug graph 10

To correct this, the start date has to be earlier or the same as the end date.

Contributions to the Developer Guide

Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project.

Personal Best --- Li Zi Ying

This feature allows the users (ie. gym managers) to view the personal bests of exercises done by a client. This information is displayed in a table form, after the command view-c INDEX is called.

Implementation

The personal best feature is facilitated by the model PersonalBest, and the logic behind it is in PersonalBestFinder. The behaviour of this feature determines the personal best of each exercise done by the client based on these considerations:

  1. If the ExerciseWeight attribute is recorded in the Exercise, then the ExerciseWeight is used as comparison

  2. If there is no ExerciseWeight recorded in the Exercise, then ExerciseReps will be used as comparison

  3. If neither of ExerciseWeight and ExerciseReps are recorded into the Exercise, then this particular exercise will not be put into the Exercise Personal Best table

    1. However, if the another Exercise of the same name is added in the future with ExerciseWeight and/or ExerciseReps specified, then the personal best of this exercise will still be calculated and shown in the Exercise Personal Best table

  4. Note that ExerciseSets, although an attribute of the Exercise model, is not considered when checking for PersonalBest as the number of sets of an exercise does not contribute to a personal best record

A simplified class diagram of the classes involved in this feature is given below:

PersonalBestClassDiagram
Figure 1. Simplified Class Diagram for Personal Best

In the following sequence diagram, we trace the execution for when the user decides to enter the command view-c into FitBiz:

PersonalBestSequenceDiagram
Figure 2. Sequence Diagram of the View#execute Method for Personal Best

The explanation for the sequence diagram is as follows: when the user inputs view-c, add-e, edit-c or delete-c, PersonalBestFinder#generateAndSetPersonalBest is called, taking the client currently in view as the parameter. PersonalBestFinder#generateAndSetPersonalBest then retrieves client’s list of exercises using Client#getExerciseList and creates a new HashMap, where the key is ExerciseName and the value is Exercise. Then the personal bests of each exercise of the client in view are generated using the above considerations. Finally the list of personal bests is set using PersonalBest#setPersonalBest.

Design Considerations

In designing this feature, we had to decide on the placement of the PersonalBest class in the model to comply with the OOP standards. Currently, the PersonalBest model has a whole-part relationship with Client, with Client being the whole and PersonalBest being a part of Client. The alternative is to consider PersonalBest as a part of Exercise instead.

Table 1. Table of Design Considerations
Put PersonalBest as a part of Client(Chosen) Put PersonalBest as a part of Exercise

Adhering to OOP standards (Coupling and Cohesion)

Increases cohesion as it logically makes more sense, currently each client has a list of exercises to themselves, and thus each client should also have a list of PersonalBest of each of these exercises to themselves

Increases coupling between the logic and model as every time the commands view-c, add-e, edit-e and delete-e are called, the personal best table has to be updated, a new PersonalBest object has to be created. Then the Client will have to be dependent on this PersonalBest object created in the logic component, which causes unnecessary dependencies and higher coupling

Ease of Implementation

Might have significant conflicts as the Client model is changed to include one more attribute

Easier to implemention as methods related to PersonalBest is kept under Exercise model and separate from Client data and methods, so no refactoring is needed

We decided to use the first approach of placing PersonalBest as a part of Client instead of Exercise. There are multiple reasons for our choice as mentioned below.

We want to maintain the OOP structure of the program. Logically, the personal best should belong to the client as the list of exercises belongs to the client. As the list of exercises is unique to every client, the personal best should also be so. We also do not want to increase coupling of the program as mentioned in the table above.

Moreover, even though personal best is generated using the list of exercises in the client, it can be instantiated even without an exercise list. Therefore it does not require the exercise class to exist and does not have a whole-part relationship with exercise. Coupling will also be increased as the client will be relying on the exercise class to generate the personal best. Therefore, the final choice was to place the personal best under client, with every client having their own personal best attribute.

This personal best feature also leads into the Graph feature, which will be discussed in the next section, where we plot a graph of the client’s progress of a specified exercise.

Graph --- Li Zi Ying

This feature allows users to see the progress graph of the current client in view. The user has to specify the exercise name, the y-axis (either weights or reps), the start date and the end date. There has to be existing exercises in the client’s exercise list for the specified axis and time period for the graph to be plotted, if no graph can be plotted, an error will be thrown.

Implementation

The graph mechanism is faciliated by the model class Graph, which contains the details of the graph. These include ExerciseName, Axis, StartDate and EndDate. The figure below is a UML class diagram to illustrate the Graph model.

GraphClassDiagram
Figure 3. Simplified Class Diagram for Graph

These attributes are bounded by these characteristics: . ExerciseName can only be alphanumeric characters . Axis can either be reps or weight only, case insensitive (sets are not considered due to the same reasoning in the above section) . Earliest StartDate possible can only be one year before the current date and cannot be after EndDate. StartDate also cannot be a future date . Earliest EndDate possible can only be one year before the current date and cannot be before StartDate. EndDate also cannot be a future date

Here is an activity diagram displaying the steps taken when FitBiz receives a user input for the graph command:

GraphCommandActivityDiagram
Figure 4. Activity Diagram for Graph Command

The behaviour of this feature determines the graph plotted of the exercise specified based on these considerations:

  1. If there is no such exercise with the matching ExerciseName in the client’s exercise list from the specified StartDate to EndDate, then the graph cannot be plotted

  2. If the Axis input is reps and the exercise specified does not have any reps input withint the StartDate to EndDate, then the graph cannot be plot

  3. If the Axis input is weight and the exercise specified does not have any weight input within the StartDate to EndDate, then the graph cannot be plot

  4. If all of the above are fulfilled (ie. there is at least one valid exercise with the matching ExerciseName and has weight/reps input depending on the Axis specified), then the graph will be plotted, with each exercise in chronological order

The flow of the program is illustrated using the sequence diagram below:

GraphSequenceDiagram
Figure 5. Sequence Diagram for Graph Command

The explanation is as follows: when the user inputs graph with all relevant arguments input correctly, a new GraphCommand() is created, taking the newly created Graph object as parameter.

GraphCommand#execute() then retrieves the exercise list from the client currently in view and checks if there is at least one exercise with a matching exercise name. If there is no exercise to plot, then an error GraphCommand.MESSAGE_EXERCISE_NOT_IN_LIST will be thrown. Next, the list of exercises to be plot will be generated using Graph#generateGraphList(). Once again, there will be a sanity check to see if the list size is zero, which means that no graph cannot be plotted.

Design Considerations

In designing this feature, we had to decide on the implementation of certain classes like Axis to comply with the OOP standards of Abstraction.

Table 2. Table of Design Considerations
Create enum class Axis Type(Chosen) Check for Axis value using raw types

Adhering to OOP standards (Abstraction)

Increases level of abstraction as there are only two different types of axis that can be chosen

Less abstraction and increases complexity as we will have to check for the equality of the axis type using the equality check for the String raw type

Ease of Implementation

Requires some refactoring to include AxisType class and the relevant getter methods

Easier to implemention as no extra classes or methods are needed, so no refactoring is needed

We decided to use the approach of abstracting the axis types away into AxisType enum class. As the graph implementation will require a substantial amount of equality checks, especially for the attributes of Graph to make sure that we are drawing the correct graph for the user. As such equality checks are made, it makes it difficult to keep checking String equality as regular data types like String would allow invalid values to be assigned to a variable.

As our axis values can only be REPS, WEIGHT or NA, we can check for each case using the switch case method instead of checking for equality using raw types. This is also much more efficient than using multiple if-else statements. For example, in the code snippet below, the method fillSeries() uses switch case statements to add data values depending on the AxisType.

Code snippet
    private void fillSeries() {
        switch (axisType) {
        case REPS:
            fillRepsSeries();
            yAxis.setLabel("Reps");
            break;
        case WEIGHT:
            fillWeightSeries();
            yAxis.setLabel("Weight");
            break;
        default:
        }
    }

Moreover, to keep in line with the OOP standards, we decided that it will be better to abstract away data types like AxisType into its separate class instead of storing it as a raw type in Axis. This ensures the code quality of our program and reduces complexity (especially in terms of equality checking as mentioned above) by abstracting away the more complex details into classes of a lower level. The consideration of abstracting details away is also used for creating StartDate and EndDate classes as attributes of Graph, instead of using the Java in-built LocalDate.

By considering the above two factors, despite having to put in the extra effort to create a new AxisType class and thus requiring extra methods like getters and setters, we decided to move with the approach of creating the AxisType enum class and refactor to accomodate for the additional data type.