Personal Project — Java Desktop App

Gym Bro

A Swing workout tracker with a layered architecture, an Observer-driven UI, and JSON persistence — built solo to replace a Notes-app habit that wasn't cutting it.

16Classes
26Unit tests
3Layers
~1.5kLines of Java

Why I built it

A few months into training, I was logging sets and reps in my phone's Notes app — duration, equipment, weight, sets, scrawled across a wall of unstructured text. It was tedious enough that I'd skip logging altogether, which defeated the point. Every fitness app I tried was either paywalled or buried in ads for what should be a simple structured record. Gym Bro is the tool I actually wanted: log a session, break it into cardio or lifting exercises, and see the totals.
25

Architecture & design patterns

foundation

Three packages, each with one job: model owns the domain and knows nothing about Swing; persistence knows how to turn that domain into JSON and back; ui is the only layer allowed to import javax.swing.

ui

  • GymBroGUI implements Observer
  • GymBroApp console variant

model

  • WorkoutRecords subject
  • WorkoutRecordsObserver interface
  • WorkoutSession
  • Exercise interface
  • Cardio / LiftWeight
  • EventLog singleton

persistence

  • Writable interface
  • JsonReader
  • JsonWriter
GymBroGUI registers itself on WorkoutRecords · WorkoutRecords notifies on every mutation

The original design manually refreshed the UI after every mutation — a classic tight-coupling smell where the model can't change without every call site remembering to babysit a label. I replaced that with an Observer: the model owns notification, the UI just reacts.

Before — UI babysits itself
workoutRecords.addWorkoutSession(session);
updateTotalWorkoutLabel();   // easy to forget
addWorkoutFrame.dispose();
After — model notifies observers
// WorkoutRecords.addWorkoutSession(session)
workoutSessions.add(session);
EventLog.getInstance().logEvent(event);
notifyObservers();  // GUI refreshes itself
20

Data & persistence

round-tripped

Every model class that needs to survive a restart implements a single Writable interface (toJson()), so JsonWriter and JsonReader don't need to know about cardio vs. weight exercises — polymorphism does the branching.

data/testWriterGeneralWorkoutRecords.json
{"workoutRecords": [{
    "totalDuration": 300,
    "listOfExercise": [
        { "duration": 100, "exerciseType": "model.Cardio", "cardioType": "ELLIPTICAL" },
        { "liftType": "BENCH_PRESS", "sets": 4, "exerciseType": "model.LiftWeight", "weight": 300 }
    ]
}]}
15

Testing & quality

verified

26 JUnit 5 tests across 9 classes cover domain logic and JSON round-trips; Checkstyle enforces style on every class.

WorkoutRecordsTest
WorkoutSessionTest
CardioTest
CardioExercisesTest
LiftWeightTest
WeightExercisesTest
JsonTest
JsonReaderTest
JsonWriterTest
10

Interface

Swing, recreated to spec

A small design system inside GymBroGUI — one accent color, one type scale, one button style — replaces what was originally bare GridLayout and default-look buttons. Click through it below: it runs the same total-hours math, session filtering, and event log as the real model classes, not just static screenshots.

Gym Bro - Workout Tracker

Gym Bro

Track and view your workout effortlessly.

You did workout 0 hours in total
How long did you spend on this session (in minutes)?
No sessions yet.
You have total 0 workout sessions
Events logged since app started:

This is the actual menu tree, button copy, and window titles from GymBroGUI.java — wired up in JS for this page.